diff --git a/TODO b/TODO index 038debc..04ff7e9 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,5 @@ Technology Preview 2 : +- Implement perspective correction for coordinate mapping of rasterized polygons. - Finalize terrain editor. => Add a generation dialog for base noise (overwriting changes). - Get rid of noise dialogs, for simpler settings. @@ -15,6 +16,7 @@ Technology Preview 2 : Technlogy Preview 3 : - Start an undo/redo system ? +- Alter aerial perspective using estimation of the amount of light left after cloud layers traversal. - Add a map preview to terrain editor. - Better time selection widget for atmosphere. - Clouds should keep distance to ground. diff --git a/src/rendering/clouds/clo_rendering.c b/src/rendering/clouds/clo_rendering.c index e0e827a..62a7984 100644 --- a/src/rendering/clouds/clo_rendering.c +++ b/src/rendering/clouds/clo_rendering.c @@ -1,5 +1,6 @@ #include "private.h" +#include #include #include "../tools.h" #include "../renderer.h" @@ -26,32 +27,117 @@ static Color _fakeGetColor(Renderer* renderer, Color base, Vector3 start, Vector } /******************** Real ********************/ +typedef struct +{ + double light_power; + double out_scattering; /* Amount of light scattered away by heavy particles */ +} AccumulatedLightData; + +static void _walkerFilterCallback(CloudsWalker* walker) +{ + CloudWalkerStepInfo* segment = cloudsWalkerGetLastSegment(walker); + AccumulatedLightData* data = (AccumulatedLightData*)segment->data; + + assert(data != NULL); + + double density_integral = segment->length * (segment->start.global_density + segment->end.global_density) / 2.0; + + data->out_scattering += 0.3 * density_integral; + + if (data->out_scattering > data->light_power) + { + cloudsWalkerOrderStop(walker); + } +} + static int _alterLight(Renderer* renderer, LightDefinition* light, Vector3 location) { -#if 0 CloudsDefinition* definition = renderer->clouds->definition; int i, n; + AccumulatedLightData data; + data.out_scattering = 0.0; + data.light_power = colorGetPower(&light->color); + /* TODO Iter layers in sorted order */ n = layersCount(definition->layers); for (i = 0; i < n; i++) { - light->color = cloudsLayerFilterLight(layersGetLayer(definition->layers, i), renderer, light->color, location, v3Add(location, v3Scale(light->direction, -10000.0)), v3Scale(light->direction, -1.0)); - /* TODO Reduce light->reflection too */ + CloudsLayerDefinition* layer = (CloudsLayerDefinition*)layersGetLayer(renderer->clouds->definition->layers, i); + Vector3 ostart, oend; + + ostart = location; + oend = v3Add(location, v3Scale(light->direction, -10000.0)); + if (!cloudsOptimizeWalkingBounds(layer, &ostart, &oend)) + { + continue; + } + else + { + CloudsWalker* walker; + + walker = cloudsCreateWalker(renderer, layer, ostart, oend); + cloudsWalkerSetStepSize(walker, -1.0); + cloudsStartWalking(walker, _walkerFilterCallback, &data); + cloudsDeleteWalker(walker); + } } - return n > 0; -#endif - return 0; + + double max_power = colorGetPower(&light->color) - data.out_scattering; + if (max_power < 0.0) + { + light->color = COLOR_BLACK; + } + else + { + colorLimitPower(&light->color, max_power); + } + + return data.out_scattering > 0.0; } typedef struct { - Color result; + double out_scattering; /* Amount of light scattered away by heavy particles */ + Color in_scattering; /* Amount of light redirected toward the viewer */ } AccumulatedMaterialData; +static inline void _applyOutScattering(Color* col, double out_scattering) +{ + if (out_scattering >= 1.0) + { + col->r = col->g = col->b = 0.0; + } + else + { + col->r *= (1.0 - out_scattering); + col->g *= (1.0 - out_scattering); + col->b *= (1.0 - out_scattering); + } +} + static void _walkerMaterialCallback(CloudsWalker* walker) { - /*AccumulatedMaterialData* data = (AccumulatedMaterialData*)segment->data;*/ + CloudWalkerStepInfo* segment = cloudsWalkerGetLastSegment(walker); + AccumulatedMaterialData* data = (AccumulatedMaterialData*)segment->data; + Renderer* renderer = segment->renderer; + CloudsLayerDefinition* layer = segment->layer; + + assert(data != NULL); + + double density_integral = segment->length * (segment->start.global_density + segment->end.global_density) / 2.0; + + data->out_scattering += 0.5 * density_integral; + + Color in_scattering = renderer->applyLightingToSurface(renderer, segment->start.location, VECTOR_UP, &layer->material); + in_scattering.r *= density_integral * 5.0; + in_scattering.g *= density_integral * 5.0; + in_scattering.b *= density_integral * 5.0; + _applyOutScattering(&in_scattering, data->out_scattering); + + data->in_scattering.r += in_scattering.r; + data->in_scattering.g += in_scattering.g; + data->in_scattering.b += in_scattering.b; } static Color _getColor(Renderer* renderer, Color base, Vector3 start, Vector3 end) @@ -81,13 +167,25 @@ static Color _getColor(Renderer* renderer, Color base, Vector3 start, Vector3 en { CloudsWalker* walker; AccumulatedMaterialData data; - data.result = COLOR_TRANSPARENT; + data.out_scattering = 0.0; + data.in_scattering = COLOR_BLACK; - walker = cloudsCreateWalker(renderer, layer, start, end); + walker = cloudsCreateWalker(renderer, layer, ostart, oend); + cloudsWalkerSetStepSize(walker, -1.0); cloudsStartWalking(walker, _walkerMaterialCallback, &data); cloudsDeleteWalker(walker); - colorMask(&base, &data.result); + /* Apply final out_scattering to base */ + _applyOutScattering(&base, data.out_scattering); + + /* Apply in_scattering */ + base.r += data.in_scattering.r; + base.g += data.in_scattering.g; + base.b += data.in_scattering.b; + + /* Apply aerial perspective approximation */ + /* TODO This should be done at cloud entry */ + base = renderer->applyMediumTraversal(renderer, ostart, base); } } diff --git a/src/rendering/clouds/clo_walking.c b/src/rendering/clouds/clo_walking.c index 42846c5..5a84b4c 100644 --- a/src/rendering/clouds/clo_walking.c +++ b/src/rendering/clouds/clo_walking.c @@ -122,10 +122,17 @@ void cloudsDeleteWalker(CloudsWalker* walker) free(walker); } -void cloudsSetStepSize(CloudsWalker* walker, double step) +void cloudsWalkerSetStepSize(CloudsWalker* walker, double step) { - /* TODO Negative step => automatic */ - walker->step_size = step; + if (step > 0.0) + { + walker->step_size = step; + } + else + { + /* TODO Automatic settings (using rendering quality and cloud feature size) */ + walker->step_size = 1.0; + } } static void _getPoint(CloudsWalker* walker, double cursor, CloudWalkerPoint* out_point) @@ -147,33 +154,33 @@ static void _refineSegment(CloudsWalker* walker, double start_cursor, double sta if (start_density == 0.0) { /* Looking for entry */ - if (middle.global_density == 0.0) + if (middle.distance_from_start - start_cursor < precision) + { + *result = middle; + } + else if (middle.global_density == 0.0) { _refineSegment(walker, middle.distance_from_start, middle.global_density, end_cursor, end_density, precision, result); } - else if (middle.distance_from_start - start_cursor > precision) - { - _refineSegment(walker, start_cursor, start_density, middle.distance_from_start, middle.global_density, precision, result); - } else { - *result = middle; + _refineSegment(walker, start_cursor, start_density, middle.distance_from_start, middle.global_density, precision, result); } } else { /* Looking for exit */ - if (middle.global_density == 0.0) + if (end_cursor - middle.distance_from_start < precision) + { + *result = middle; + } + else if (middle.global_density == 0.0) { _refineSegment(walker, start_cursor, start_density, middle.distance_from_start, middle.global_density, precision, result); } - else if (end_cursor - middle.distance_from_start > precision) - { - _refineSegment(walker, middle.distance_from_start, middle.global_density, end_cursor, end_density, precision, result); - } else { - *result = middle; + _refineSegment(walker, middle.distance_from_start, middle.global_density, end_cursor, end_density, precision, result); } } } @@ -200,6 +207,7 @@ int cloudsWalkerPerformStep(CloudsWalker* walker) _getPoint(walker, walker->cursor, &walker->last_segment.end); walker->last_segment.length = walker->step_size; + walker->last_segment.refined = 0; return 1; } @@ -212,9 +220,12 @@ int cloudsWalkerPerformStep(CloudsWalker* walker) walker->last_segment.end.distance_from_start, walker->last_segment.end.global_density, walker->next_action.precision, - &walker->last_segment.start); + (walker->last_segment.start.global_density == 0.0) ? (&walker->last_segment.start) : (&walker->last_segment.end)); walker->last_segment.length = walker->last_segment.end.distance_from_start - walker->last_segment.start.distance_from_start; + walker->last_segment.refined = 1; + walker->next_action.order = CLOUD_WALKING_CONTINUE; + return 1; } else @@ -248,6 +259,7 @@ CloudWalkerStepInfo* cloudsWalkerGetLastSegment(CloudsWalker* walker) void cloudsStartWalking(CloudsWalker* walker, FuncCloudsWalkingCallback callback, void* data) { + walker->last_segment.data = data; while (cloudsWalkerPerformStep(walker)) { callback(walker); diff --git a/src/rendering/clouds/clo_walking.h b/src/rendering/clouds/clo_walking.h index 5aaa812..1bd31fc 100644 --- a/src/rendering/clouds/clo_walking.h +++ b/src/rendering/clouds/clo_walking.h @@ -32,8 +32,8 @@ typedef struct CloudWalkerPoint end; double length; - /*int refined; - int subdivision_level; + int refined; + /*int subdivision_level; double precision_asked;*/ void* data; @@ -77,7 +77,7 @@ void cloudsDeleteWalker(CloudsWalker* walker); * @param walker The walker to configure * @param step The step length, negative for automatic */ -void cloudsSetStepSize(CloudsWalker* walker, double step); +void cloudsWalkerSetStepSize(CloudsWalker* walker, double step); /** * Perform a single step. diff --git a/src/testing/common.h b/src/testing/common.h index 0a7a39f..27a5655 100644 --- a/src/testing/common.h +++ b/src/testing/common.h @@ -27,6 +27,11 @@ static inline void _add_methods_to_case(TCase* tc, ...) suite_add_tcase(s, tc); \ } +/***** Boolean assertions *****/ +#define ck_assert_true(_X_) ck_assert_int_ne((_X_), 0) +#define ck_assert_false(_X_) ck_assert_int_eq((_X_), 0) + +/***** Floating point assertions *****/ static inline int _double_equals(double x, double y) { return fabs(x - y) < 0.00000000001; diff --git a/src/testing/test_clouds.c b/src/testing/test_clouds.c index d00de74..4d255c3 100644 --- a/src/testing/test_clouds.c +++ b/src/testing/test_clouds.c @@ -220,10 +220,11 @@ START_TEST(test_clouds_walking) int result; /* First step */ - cloudsSetStepSize(walker, 0.3); + cloudsWalkerSetStepSize(walker, 0.3); result = cloudsWalkerPerformStep(walker); segment = cloudsWalkerGetLastSegment(walker); ck_assert_int_eq(result, 1); + ck_assert_false(segment->refined); ck_assert_double_eq(segment->length, 0.3); ck_assert_double_eq(segment->start.distance_from_start, 0.0); ck_assert_vector_values(segment->start.location, -0.4, 0.0, 0.0); @@ -236,6 +237,7 @@ START_TEST(test_clouds_walking) result = cloudsWalkerPerformStep(walker); segment = cloudsWalkerGetLastSegment(walker); ck_assert_int_eq(result, 1); + ck_assert_false(segment->refined); ck_assert_double_eq(segment->length, 0.3); ck_assert_double_eq(segment->start.distance_from_start, 0.3); ck_assert_vector_values(segment->start.location, -0.1, 0.0, 0.0); @@ -249,14 +251,43 @@ START_TEST(test_clouds_walking) result = cloudsWalkerPerformStep(walker); segment = cloudsWalkerGetLastSegment(walker); ck_assert_int_eq(result, 1); - ck_assert_double_in_range(segment->length, 0.19, 0.21); - ck_assert_double_in_range(segment->start.distance_from_start, 0.39, 0.41); - ck_assert_double_in_range(segment->start.location.x, -0.01, 0.01); - /* TODO Check segment->start.global_density */ + ck_assert_true(segment->refined); + ck_assert_double_in_range(segment->length, 0.19, 0.20); + ck_assert_double_in_range(segment->start.distance_from_start, 0.40, 0.41); + ck_assert_double_in_range(segment->start.location.x, 0.0, 0.01); + ck_assert_double_gt(segment->start.global_density, 0.0); ck_assert_double_eq(segment->end.distance_from_start, 0.6); ck_assert_vector_values(segment->end.location, 0.2, 0.0, 0.0); ck_assert_double_gt(segment->end.global_density, 0.9); + /* Third step, change step size */ + cloudsWalkerSetStepSize(walker, 0.4); + result = cloudsWalkerPerformStep(walker); + segment = cloudsWalkerGetLastSegment(walker); + ck_assert_int_eq(result, 1); + ck_assert_false(segment->refined); + ck_assert_double_eq(segment->length, 0.4); + ck_assert_double_eq(segment->start.distance_from_start, 0.6); + ck_assert_vector_values(segment->start.location, 0.2, 0.0, 0.0); + ck_assert_double_gt(segment->start.global_density, 0.9); + ck_assert_double_eq(segment->end.distance_from_start, 1.0); + ck_assert_vector_values(segment->end.location, 0.6, 0.0, 0.0); + ck_assert_double_eq(segment->end.global_density, 0.0); + + /* Refine exit point */ + cloudsWalkerOrderRefine(walker, 0.001); + result = cloudsWalkerPerformStep(walker); + segment = cloudsWalkerGetLastSegment(walker); + ck_assert_int_eq(result, 1); + ck_assert_true(segment->refined); + ck_assert_double_in_range(segment->length, 0.3, 0.301); + ck_assert_double_eq(segment->start.distance_from_start, 0.6); + ck_assert_vector_values(segment->start.location, 0.2, 0.0, 0.0); + ck_assert_double_gt(segment->start.global_density, 0.9); + ck_assert_double_in_range(segment->end.distance_from_start, 0.9, 0.901); + ck_assert_double_in_range(segment->end.location.x, 0.5, 0.501); + ck_assert_double_lt(segment->end.global_density, 0.1); + /* Clean up */ cloudsDeleteWalker(walker);