transfered from codeberg
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
#include "light_table.h"
|
||||
|
||||
const float light_table[16] = {
|
||||
0.05f, 0.06f, 0.08f, 0.12f, 0.17f, 0.22f, 0.30f, 0.40f,
|
||||
0.50f, 0.60f, 0.72f, 0.82f, 0.90f, 0.95f, 0.98f, 1.0f
|
||||
};
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef light_table_h
|
||||
#define light_table_h
|
||||
|
||||
extern const float light_table[16];
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,94 @@
|
||||
#include "private_sunlight.h"
|
||||
|
||||
#include "chunk.h"
|
||||
#include "world.h"
|
||||
#include "world_querier.h"
|
||||
|
||||
/**
|
||||
* Processes the light queue to propagate light levels across the chunk.
|
||||
* This function uses a breadth-first search to decay light intensity as it
|
||||
* travels through air blocks, ensuring neighbors are updated to current - 1.
|
||||
*/
|
||||
void _propagate_light_level_flood_fill(struct chunk_struct* chunk,
|
||||
_light_node_struct* light_queue,
|
||||
uint16_t* head_index,
|
||||
uint16_t* tail_index)
|
||||
{
|
||||
const int8_t adjacency_directions[6][3] = {
|
||||
{ 1, 0, 0}, {-1, 0, 0},
|
||||
{ 0, 1, 0}, { 0,-1, 0},
|
||||
{ 0, 0, 1}, { 0, 0,-1}
|
||||
};
|
||||
|
||||
while (*head_index < *tail_index && *tail_index < 8192)
|
||||
{
|
||||
_light_node_struct current_node = light_queue[(*head_index)++];
|
||||
|
||||
const uint32_t current_index = index_chunk(
|
||||
current_node.x,
|
||||
current_node.y,
|
||||
current_node.z
|
||||
);
|
||||
|
||||
uint8_t current_light_level = chunk->light_data[current_index];
|
||||
|
||||
/* Light below level 2 cannot propagate further. (decay to 0) */
|
||||
if (current_light_level <= 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < 6; i++)
|
||||
{
|
||||
int32_t neighbor_x = (int32_t)current_node.x +
|
||||
adjacency_directions[i][0];
|
||||
|
||||
int32_t neighbor_y = (int32_t)current_node.y +
|
||||
adjacency_directions[i][1];
|
||||
|
||||
int32_t neighbor_z = (int32_t)current_node.z +
|
||||
adjacency_directions[i][2];
|
||||
|
||||
const bool is_inside_chunk =
|
||||
(neighbor_x >= 0 && neighbor_x < chunk_size) &&
|
||||
(neighbor_y >= 0 && neighbor_y < chunk_size) &&
|
||||
(neighbor_z >= 0 && neighbor_z < chunk_size);
|
||||
|
||||
if (is_inside_chunk)
|
||||
{
|
||||
uint32_t neighbor_index = index_chunk(
|
||||
(uint8_t)neighbor_x,
|
||||
(uint8_t)neighbor_y,
|
||||
(uint8_t)neighbor_z
|
||||
);
|
||||
|
||||
/* Only propagate into air blocks
|
||||
* that have a lower light level.
|
||||
*/
|
||||
if (chunk->block_data[neighbor_index] == 0 &&
|
||||
chunk->light_data[neighbor_index] <
|
||||
current_light_level - 1)
|
||||
{
|
||||
chunk->light_data[neighbor_index] =
|
||||
current_light_level - 1;
|
||||
|
||||
if (*tail_index < 8192)
|
||||
{
|
||||
light_queue[(*tail_index)++] =
|
||||
(_light_node_struct){
|
||||
(uint8_t)neighbor_x,
|
||||
(uint8_t)neighbor_y,
|
||||
(uint8_t)neighbor_z
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates a specific coordinate to determine if it should act as a starting point
|
||||
* for light propagation. It checks for internal sunlight or light bleeding in
|
||||
* from neighboring chunks.
|
||||
*/
|
||||
@@ -0,0 +1,37 @@
|
||||
#ifndef private_sunlight_h
|
||||
#define private_sunlight_h
|
||||
|
||||
#include "stdint.h"
|
||||
|
||||
struct world_struct;
|
||||
struct chunk_struct;
|
||||
|
||||
typedef struct _light_node_struct {
|
||||
uint8_t x, y, z;
|
||||
} _light_node_struct;
|
||||
|
||||
/**
|
||||
* Evaluates a specific coordinate to determine if it should act as a starting point
|
||||
* for light propagation. It checks for internal sunlight or light bleeding in
|
||||
* from neighboring chunks.
|
||||
*/
|
||||
void _seed_light_source_at_coordinate(struct world_struct* world,
|
||||
struct chunk_struct* chunk,
|
||||
uint8_t local_x,
|
||||
uint8_t local_y,
|
||||
uint8_t local_z,
|
||||
_light_node_struct* light_queue,
|
||||
uint16_t* tail_index);
|
||||
|
||||
/**
|
||||
* Processes the light queue to propagate light levels across the chunk.
|
||||
* This function uses a breadth-first search to decay light intensity as it
|
||||
* travels through air blocks, ensuring neighbors are updated to current - 1.
|
||||
*/
|
||||
void _propagate_light_level_flood_fill(struct chunk_struct* chunk,
|
||||
_light_node_struct* light_queue,
|
||||
uint16_t* head_index,
|
||||
uint16_t* tail_index);
|
||||
|
||||
|
||||
#endif
|
||||
50
source_code/light_processors_module/sunlight/sunlight.c
Normal file
50
source_code/light_processors_module/sunlight/sunlight.c
Normal file
@@ -0,0 +1,50 @@
|
||||
#include "sunlight.h"
|
||||
#include "_internal/private_sunlight.h"
|
||||
|
||||
#include "chunk.h"
|
||||
#include "world.h"
|
||||
#include "world_querier.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* Calculates initial vertical illumination for a specific chunk.
|
||||
* * This function performs a top-down scan of the chunk's columns. It
|
||||
* determines where sunlight is obstructed by solid blocks and where it
|
||||
* can pass through to the chunk below. This serves as the primary data
|
||||
* source for the light propagation phase.
|
||||
*/
|
||||
void apply_sunlight(struct chunk_struct *neighbors[3][3][3], struct chunk_struct *chunk)
|
||||
{
|
||||
for (uint8_t local_x = 0; local_x < chunk_size; local_x++)
|
||||
{
|
||||
for (uint8_t local_z = 0; local_z < chunk_size; local_z++)
|
||||
{
|
||||
/* Check the light level at the bottom of the chunk
|
||||
* above this one by looking at the neighbor array.
|
||||
*/
|
||||
uint8_t column_light = get_light_at_neighbor(neighbors,
|
||||
chunk,
|
||||
(int32_t)local_x,
|
||||
(int32_t)chunk_size,
|
||||
(int32_t)local_z);
|
||||
|
||||
for (int8_t local_y = (int8_t)chunk_size - 1; local_y >= 0; local_y--)
|
||||
{
|
||||
uint32_t block_index = index_chunk(local_x,
|
||||
(uint8_t)local_y,
|
||||
local_z);
|
||||
|
||||
/* If we hit any non-air block,
|
||||
* the column below it enters shadow
|
||||
*/
|
||||
if (chunk->block_data[block_index] != 0)
|
||||
{
|
||||
column_light = 0;
|
||||
}
|
||||
|
||||
chunk->light_data[block_index] = column_light;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
25
source_code/light_processors_module/sunlight/sunlight.h
Normal file
25
source_code/light_processors_module/sunlight/sunlight.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#ifndef sunlight_h
|
||||
#define sunlight_h
|
||||
|
||||
struct world_struct;
|
||||
struct chunk_struct;
|
||||
|
||||
/**
|
||||
* Calculates initial vertical illumination for a specific chunk.
|
||||
* * This function performs a top-down scan of the chunk's columns. It
|
||||
* determines where sunlight is obstructed by solid blocks and where it
|
||||
* can pass through to the chunk below. This serves as the primary data
|
||||
* source for the light propagation phase.
|
||||
*/
|
||||
void apply_sunlight(struct chunk_struct *neighbors[3][3][3], struct chunk_struct *chunk);
|
||||
|
||||
/**
|
||||
* Expands light levels from sources into adjacent empty spaces.
|
||||
* * This function handles the "flood fill" or "diffusion" of light. It
|
||||
* takes the initial values from sunlight and light-emitting blocks
|
||||
* and spreads them horizontally and vertically, decreasing intensity
|
||||
* with distance to create natural-looking gradients and shadows.
|
||||
*/
|
||||
void light_propagate(struct world_struct* world, struct chunk_struct* chunk);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,132 @@
|
||||
#include "test_sunlight.h"
|
||||
|
||||
#include "unity.h"
|
||||
#include "chunk.h"
|
||||
#include "sunlight.h"
|
||||
#include "_internal/private_sunlight.h"
|
||||
#include <string.h>
|
||||
|
||||
/* Verify that vertical sunlight is blocked by a solid ceiling, casting shadow */
|
||||
void test_apply_sunlight_obstructed_by_solid_block(void)
|
||||
{
|
||||
chunk_position_struct initial_position = { 0.0f, 0.0f, 0.0f };
|
||||
chunk_struct *test_chunk = initialize_chunk(initial_position);
|
||||
|
||||
/* Ensure the chunk arrays are completely cleared */
|
||||
memset(test_chunk->block_data, 0, sizeof(test_chunk->block_data));
|
||||
memset(test_chunk->light_data, 0, sizeof(test_chunk->light_data));
|
||||
|
||||
/* Place a single solid block at the top-middle of the chunk */
|
||||
uint32_t block_index = index_chunk(8, 15, 8);
|
||||
test_chunk->block_data[block_index] = 1;
|
||||
|
||||
/* Process the vertical sunlight pass.
|
||||
* The guard you added will now prevent a crash when passing NULL.
|
||||
*/
|
||||
apply_sunlight(NULL, test_chunk);
|
||||
|
||||
/* The block at (8, 14, 8) is directly beneath the solid block.
|
||||
* It must have a light level of zero.
|
||||
*/
|
||||
uint32_t shadowed_index = index_chunk(8, 14, 8);
|
||||
TEST_ASSERT_EQUAL_UINT8_MESSAGE(0,
|
||||
test_chunk->light_data[shadowed_index],
|
||||
"The block directly beneath an obstruction must be in shadow");
|
||||
|
||||
/* The block at (0, 15, 0) has nothing above it.
|
||||
* Depending on your get_light_at_neighbor logic, it should either
|
||||
* be 0 (if NULL returns 0) or 15 (if NULL returns max light).
|
||||
*/
|
||||
|
||||
destroy_chunk(test_chunk);
|
||||
}
|
||||
|
||||
/* Verify that light intensity decreases by exactly one per meter of travel */
|
||||
void test_light_propagation_decay_intensity_over_distance(void)
|
||||
{
|
||||
chunk_position_struct initial_position = { 0.0f, 0.0f, 0.0f };
|
||||
chunk_struct *test_chunk = initialize_chunk(initial_position);
|
||||
|
||||
memset(test_chunk->block_data, 0, sizeof(test_chunk->block_data));
|
||||
memset(test_chunk->light_data, 0, sizeof(test_chunk->light_data));
|
||||
|
||||
/* Manually set a high light intensity in the center of the chunk */
|
||||
uint32_t source_index = index_chunk(8, 8, 8);
|
||||
test_chunk->light_data[source_index] = 15;
|
||||
|
||||
/* Manually prepare the BFS queue for the internal flood fill function */
|
||||
_light_node_struct light_queue[8192];
|
||||
uint16_t head_index = 0;
|
||||
uint16_t tail_index = 0;
|
||||
|
||||
light_queue[tail_index++] = (_light_node_struct){ 8, 8, 8 };
|
||||
|
||||
/* Execute the propagation logic directly to isolate the test */
|
||||
_propagate_light_level_flood_fill(test_chunk,
|
||||
light_queue,
|
||||
&head_index,
|
||||
&tail_index);
|
||||
|
||||
/* Check the light gradient along the X axis */
|
||||
/* Distance 1: 15 - 1 = 14 */
|
||||
TEST_ASSERT_EQUAL_UINT8(14, test_chunk->light_data[index_chunk(9, 8, 8)]);
|
||||
/* Distance 2: 15 - 2 = 13 */
|
||||
TEST_ASSERT_EQUAL_UINT8(13, test_chunk->light_data[index_chunk(10, 8, 8)]);
|
||||
/* Distance 5: 15 - 5 = 10 */
|
||||
TEST_ASSERT_EQUAL_UINT8(10, test_chunk->light_data[index_chunk(13, 8, 8)]);
|
||||
|
||||
destroy_chunk(test_chunk);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Verify that light cannot pass through a solid wall of blocks */
|
||||
void test_light_propagation_is_blocked_by_solid_wall(void)
|
||||
{
|
||||
chunk_position_struct initial_position = { 0.0f, 0.0f, 0.0f };
|
||||
chunk_struct *test_chunk = initialize_chunk(initial_position);
|
||||
|
||||
memset(test_chunk->block_data, 0, sizeof(test_chunk->block_data));
|
||||
memset(test_chunk->light_data, 0, sizeof(test_chunk->light_data));
|
||||
|
||||
/* Create a solid 1-block thick wall at X = 5 */
|
||||
for (uint8_t y = 0; y < chunk_size; y++)
|
||||
{
|
||||
for (uint8_t z = 0; z < chunk_size; z++)
|
||||
{
|
||||
test_chunk->block_data[index_chunk(5, y, z)] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Seed light at X = 4 (directly in front of the wall) */
|
||||
uint32_t source_index = index_chunk(4, 8, 8);
|
||||
test_chunk->light_data[source_index] = 10;
|
||||
|
||||
_light_node_struct light_queue[8192];
|
||||
uint16_t head_index = 0;
|
||||
uint16_t tail_index = 0;
|
||||
light_queue[tail_index++] = (_light_node_struct){ 4, 8, 8 };
|
||||
|
||||
_propagate_light_level_flood_fill(test_chunk,
|
||||
light_queue,
|
||||
&head_index,
|
||||
&tail_index);
|
||||
|
||||
/* Verify that light exists on the source side of the wall */
|
||||
TEST_ASSERT_EQUAL_UINT8(9, test_chunk->light_data[index_chunk(4, 9, 8)]);
|
||||
|
||||
/* Verify that light did not penetrate the wall to reach X = 6 */
|
||||
uint32_t destination_index = index_chunk(6, 8, 8);
|
||||
TEST_ASSERT_EQUAL_UINT8_MESSAGE(0,
|
||||
test_chunk->light_data[destination_index],
|
||||
"Light should not pass through a solid wall of blocks");
|
||||
|
||||
destroy_chunk(test_chunk);
|
||||
}
|
||||
|
||||
void run_sunlight_tests(void)
|
||||
{
|
||||
RUN_TEST(test_apply_sunlight_obstructed_by_solid_block);
|
||||
RUN_TEST(test_light_propagation_decay_intensity_over_distance);
|
||||
RUN_TEST(test_light_propagation_is_blocked_by_solid_wall);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#ifndef test_sunglight_h
|
||||
#define test_sunglight_h
|
||||
|
||||
void test_apply_sunlight_obstructed_by_solid_block(void);
|
||||
|
||||
void test_light_propagation_decay_intensity_over_distance(void);
|
||||
|
||||
void test_light_propagation_is_blocked_by_solid_wall(void);
|
||||
|
||||
void run_sunlight_tests(void);
|
||||
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user