transfered from codeberg
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
#include "private_terrain.h"
|
||||
|
||||
#include "perlin.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define base_noise_scale 0.01f
|
||||
#define detail_noise_scale 0.05f
|
||||
#define base_noise_amplitude 12.0f
|
||||
#define detail_noise_amplitude 4.0f
|
||||
#define global_ground_level 0.0f
|
||||
|
||||
/**
|
||||
* Calculates the noise-based height value for the terrain surface
|
||||
* at the given global world coordinates.
|
||||
*/
|
||||
float _get_terrain_height_at_coordinates(int32_t world_coordinate_x,
|
||||
int32_t world_coordinate_z)
|
||||
{
|
||||
float noise_input_x = (float)world_coordinate_x;
|
||||
float noise_input_z = (float)world_coordinate_z;
|
||||
float base_elevation = stb_perlin_noise3(noise_input_x * base_noise_scale,
|
||||
0.0f,
|
||||
noise_input_z * base_noise_scale,
|
||||
0,
|
||||
0,
|
||||
0);
|
||||
|
||||
float detail_variation = stb_perlin_noise3(
|
||||
noise_input_x * detail_noise_scale,
|
||||
100.0f,
|
||||
noise_input_z * detail_noise_scale,
|
||||
0, 0, 0
|
||||
);
|
||||
|
||||
float calculated_final_height =
|
||||
base_elevation * base_noise_amplitude +
|
||||
detail_variation * detail_noise_amplitude +
|
||||
global_ground_level;
|
||||
|
||||
return floorf(calculated_final_height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a specific 3D coordinate should be empty (air) based
|
||||
* on 3D noise patterns to create subterranean cave systems.
|
||||
*/
|
||||
bool _is_cave_present_at_coordinates(int32_t world_coordinate_x,
|
||||
int32_t world_coordinate_y,
|
||||
int32_t world_coordinate_z,
|
||||
int32_t surface_height_at_location)
|
||||
{
|
||||
if (world_coordinate_y >= surface_height_at_location + 5)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
float cave_noise_scale = 0.06f;
|
||||
|
||||
float noise_density_value = stb_perlin_noise3(
|
||||
(float)world_coordinate_x * cave_noise_scale,
|
||||
(float)world_coordinate_y * cave_noise_scale,
|
||||
(float)world_coordinate_z * cave_noise_scale,
|
||||
0, 0, 0
|
||||
);
|
||||
|
||||
return noise_density_value > 0.3f;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef private_terrain_h
|
||||
#define private_terrain_h
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* Calculates the noise-based height value for the terrain surface
|
||||
* at the given global world coordinates.
|
||||
*/
|
||||
float _get_terrain_height_at_coordinates(int32_t world_coordinate_x,
|
||||
int32_t world_coordinate_z);
|
||||
|
||||
/**
|
||||
* Determines if a specific 3D coordinate should be empty (air) based
|
||||
* on 3D noise patterns to create subterranean cave systems.
|
||||
*/
|
||||
bool _is_cave_present_at_coordinates(int32_t world_coordinate_x,
|
||||
int32_t world_coordinate_y,
|
||||
int32_t world_coordinate_z,
|
||||
int32_t surface_height_at_location);
|
||||
|
||||
#endif
|
||||
67
source_code/terrain_module/terrain/terrain.c
Normal file
67
source_code/terrain_module/terrain/terrain.c
Normal file
@@ -0,0 +1,67 @@
|
||||
#include "terrain.h"
|
||||
#include "_internal/private_terrain.h"
|
||||
|
||||
#include "block.h"
|
||||
#include "chunk.h"
|
||||
|
||||
#include "raylib.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
/**
|
||||
* Iterates through every coordinate in a chunk, calculating terrain height
|
||||
* and cave presence to populate the block data array.
|
||||
*/
|
||||
void generate_terrain(struct chunk_struct *chunk, struct chunk_position_struct* chunks_world_position)
|
||||
{
|
||||
// Align world position to integers once
|
||||
int32_t base_x = chunks_world_position->x;
|
||||
int32_t base_y = chunks_world_position->y;
|
||||
int32_t base_z = chunks_world_position->z;
|
||||
|
||||
for (uint8_t x = 0; x < chunk_size; x++)
|
||||
{
|
||||
for (uint8_t z = 0; z < chunk_size; z++)
|
||||
{
|
||||
int32_t world_coordinate_x = base_x + x;
|
||||
int32_t world_coordinate_z = base_z + z;
|
||||
int32_t surface_height =
|
||||
(int32_t)floorf(
|
||||
_get_terrain_height_at_coordinates(
|
||||
world_coordinate_x,
|
||||
world_coordinate_z));
|
||||
|
||||
for (uint8_t y = 0; y < chunk_size; y++)
|
||||
{
|
||||
int32_t world_coordinate_y = base_y + y;
|
||||
uint8_t resulting_block_id = block_air;
|
||||
|
||||
if (world_coordinate_y <= surface_height)
|
||||
{
|
||||
resulting_block_id = block_dirt;
|
||||
|
||||
if (world_coordinate_y < surface_height - 3)
|
||||
{
|
||||
resulting_block_id = block_stone;
|
||||
}
|
||||
|
||||
if (world_coordinate_y > surface_height - 1)
|
||||
{
|
||||
resulting_block_id = block_grass;
|
||||
}
|
||||
|
||||
if (_is_cave_present_at_coordinates(
|
||||
world_coordinate_x,
|
||||
world_coordinate_y,
|
||||
world_coordinate_z,
|
||||
surface_height))
|
||||
{
|
||||
resulting_block_id = block_air;
|
||||
}
|
||||
}
|
||||
|
||||
chunk->block_data[index_chunk(x, y, z)] = resulting_block_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
source_code/terrain_module/terrain/terrain.h
Normal file
16
source_code/terrain_module/terrain/terrain.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef terrain_h
|
||||
#define terrain_h
|
||||
|
||||
struct chunk_struct;
|
||||
struct chunk_position_struct;
|
||||
|
||||
#define grass_height -1
|
||||
|
||||
/**
|
||||
* Iterates through every coordinate in a chunk, calculating terrain height
|
||||
* and cave presence to populate the block data array.
|
||||
*/
|
||||
void generate_terrain(struct chunk_struct *chunk,
|
||||
struct chunk_position_struct* chunks_world_position);
|
||||
|
||||
#endif
|
||||
120
source_code/terrain_module/terrain/tests/test_terrain.c
Normal file
120
source_code/terrain_module/terrain/tests/test_terrain.c
Normal file
@@ -0,0 +1,120 @@
|
||||
#include "test_terrain.h"
|
||||
|
||||
#include "unity.h"
|
||||
#include "../terrain.h"
|
||||
#include "../_internal/private_terrain.h"
|
||||
#include "block.h"
|
||||
#include "chunk.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
|
||||
//#define terrain_c_test_debug
|
||||
|
||||
/* Definition of the missing global the scheduler is looking for */
|
||||
struct world_struct *world;
|
||||
|
||||
/* Checks to see if _get_terrain_hight_at_coordinates is consistant. */
|
||||
void test_terrain_height_is_consistent(void)
|
||||
{
|
||||
float height_one = _get_terrain_height_at_coordinates(10, 10);
|
||||
float height_two = _get_terrain_height_at_coordinates(10, 10);
|
||||
|
||||
TEST_ASSERT_EQUAL_FLOAT(height_one, height_two);
|
||||
}
|
||||
|
||||
void test_cave_probability_at_high_altitude(void)
|
||||
{
|
||||
bool has_cave = _is_cave_present_at_coordinates(0, 100, 0, 10);
|
||||
|
||||
TEST_ASSERT_FALSE(has_cave);
|
||||
}
|
||||
|
||||
/* Step 1: initialize a chunk with location 0, 0, 0.
|
||||
* Step 2: run the terrain generator over the chunk.
|
||||
* Step 3: verify that the surface of the chunk IS NOT made of air.
|
||||
* Step 4: veridy that blocks 1 unit above the surface (within the same chunk) IS air.
|
||||
* Step 5: clean up.
|
||||
*/
|
||||
void test_generate_terrain_fuzzer(void)
|
||||
{
|
||||
/* Step 1: Set up logging and randomness */
|
||||
unsigned int seed = (unsigned int)time(NULL);
|
||||
|
||||
FILE *log = fopen("test_logs/last_test_random_vars.txt", "w");
|
||||
fprintf(log, "Seed: %u\n", seed);
|
||||
srand(seed);
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
/* Step 2: Generate a random world position aligned to 16x grid */
|
||||
// We multiply by 16 to ensure the chunk is grid-aligned
|
||||
chunk_position_struct world_pos = {
|
||||
.x = (rand() % 2000 - 1000) * chunk_size,
|
||||
.y = (rand() % 10 - 5) * chunk_size, // Focus near sea level
|
||||
.z = (rand() % 2000 - 1000) * chunk_size
|
||||
};
|
||||
|
||||
fprintf(log, "Chunk %d: x:%" PRId32 " y:%" PRId32 " z:%" PRId32 "\n",
|
||||
i,
|
||||
(int32_t)world_pos.x,
|
||||
(int32_t)world_pos.y,
|
||||
(int32_t)world_pos.z);
|
||||
|
||||
struct chunk_struct *chunk = initialize_chunk(world_pos);
|
||||
generate_terrain(chunk, &world_pos);
|
||||
|
||||
/* Step 3: Verify the internal data */
|
||||
for (uint8_t x = 0; x < chunk_size; x++)
|
||||
{
|
||||
for (uint8_t z = 0; z < chunk_size; z++)
|
||||
{
|
||||
int32_t world_x = (int32_t)world_pos.x + x;
|
||||
int32_t world_z = (int32_t)world_pos.z + z;
|
||||
|
||||
int32_t surface_y =
|
||||
(int32_t)floorf(
|
||||
_get_terrain_height_at_coordinates(world_x,
|
||||
world_z));
|
||||
|
||||
// Convert world surface to local chunk space
|
||||
int32_t local_y = surface_y - (int32_t)world_pos.y;
|
||||
|
||||
if (local_y >= 0 && local_y < chunk_size)
|
||||
{
|
||||
uint16_t block =
|
||||
chunk->block_data[index_chunk(x, local_y, z)];
|
||||
|
||||
if (block == block_air)
|
||||
{
|
||||
bool is_cave =
|
||||
_is_cave_present_at_coordinates
|
||||
(world_x,
|
||||
local_y + (int32_t)world_pos.y,
|
||||
world_z,
|
||||
surface_y
|
||||
);
|
||||
if (!is_cave)
|
||||
{
|
||||
fclose(log);
|
||||
TEST_FAIL_MESSAGE("Found AIR at s"
|
||||
"urface where no "
|
||||
"cave exists!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
destroy_chunk(chunk);
|
||||
}
|
||||
fclose(log);
|
||||
}
|
||||
|
||||
void run_terrain_tests(void)
|
||||
{
|
||||
RUN_TEST(test_terrain_height_is_consistent);
|
||||
RUN_TEST(test_cave_probability_at_high_altitude);
|
||||
RUN_TEST(test_generate_terrain_fuzzer);
|
||||
}
|
||||
13
source_code/terrain_module/terrain/tests/test_terrain.h
Normal file
13
source_code/terrain_module/terrain/tests/test_terrain.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef test_terrain_h
|
||||
#define test_terrain_h
|
||||
|
||||
void test_terrain_height_is_consistent(void);
|
||||
|
||||
void test_cave_probability_at_high_altitude(void);
|
||||
|
||||
void test_generate_terrain_fuzzer(void);
|
||||
|
||||
void run_terrain_tests(void);
|
||||
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user