From 3fdb42ba0edc888a09601fb03832412fc05ed5ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Lemaire?= Date: Sat, 18 May 2013 21:43:19 +0000 Subject: [PATCH] paysages : Started clouds refactoring with unit tests (WIP). git-svn-id: https://subversion.assembla.com/svn/thunderk/paysages@576 b1fd45b6-86a6-48da-8261-f70d1f35bdcc --- Makefile | 3 +- src/rendering/clouds/clo_density.c | 66 +++++++ src/rendering/clouds/clo_density.h | 45 +++++ src/rendering/clouds/clo_rendering.c | 22 +-- src/rendering/clouds/clo_tools.c | 286 +-------------------------- src/rendering/clouds/clo_walking.c | 157 +++++++++++++++ src/rendering/clouds/clo_walking.h | 50 +++++ src/rendering/clouds/private.h | 9 - src/rendering/clouds/public.h | 11 +- src/rendering/tools/euclid.h | 5 + src/testing/common.h | 25 +++ src/testing/main.c | 9 + src/testing/test_camera.c | 6 +- src/testing/test_clouds.c | 252 +++++++++++++++++++++++ 14 files changed, 629 insertions(+), 317 deletions(-) create mode 100644 src/rendering/clouds/clo_density.c create mode 100644 src/rendering/clouds/clo_density.h create mode 100644 src/rendering/clouds/clo_walking.c create mode 100644 src/rendering/clouds/clo_walking.h create mode 100644 src/testing/test_clouds.c diff --git a/Makefile b/Makefile index e3308d6..99ed38e 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,8 @@ debug: release: make BUILDMODE=release all -tests: all +tests: + make BUILDMODE=${BUILDMODE} all LD_LIBRARY_PATH=${BUILDPATH} CK_DEFAULT_TIMEOUT=30 ${BUILDPATH}/paysages-tests run_cli: all diff --git a/src/rendering/clouds/clo_density.c b/src/rendering/clouds/clo_density.c new file mode 100644 index 0000000..f967ae0 --- /dev/null +++ b/src/rendering/clouds/clo_density.c @@ -0,0 +1,66 @@ +#include "clo_density.h" + +#include "../tools.h" + +double cloudsGetLayerCoverage(CloudsLayerDefinition* layer, Vector3 location) +{ + if (layer->base_coverage == 0.0) + { + return 0.0; + } + else + { + double coverage = noiseGet2DTotal(layer->_coverage_noise, location.x / layer->shape_scaling, location.z / layer->shape_scaling); + coverage -= (1.0 - layer->base_coverage); + + coverage *= curveGetValue(layer->_coverage_by_altitude, (location.y - layer->lower_altitude) / layer->thickness); + + return (coverage <= 0.0) ? 0.0 : coverage; + } +} + +double cloudsGetLayerDensity(CloudsLayerDefinition* layer, Vector3 location, double coverage) +{ + if (coverage == 0.0) + { + return 0.0; + } + else if (coverage == 1.0) + { + return 1.0; + } + else + { + double density = noiseGet3DTotal(layer->_shape_noise, location.x / layer->shape_scaling, location.y / layer->shape_scaling, location.z / layer->shape_scaling); + density -= (0.5 - coverage); + return (density <= 0.0) ? 0.0 : density; + } +} + +static double _fakeGetDensity(Renderer* renderer, CloudsLayerDefinition* layer, Vector3 location) +{ + UNUSED(layer); + UNUSED(renderer); + UNUSED(location); + + return 0.0; +} + +void cloudsBindFakeDensityToRenderer(CloudsRenderer* renderer) +{ + renderer->getLayerDensity = _fakeGetDensity; +} + +static double _realGetDensity(Renderer* renderer, CloudsLayerDefinition* layer, Vector3 location) +{ + UNUSED(renderer); + + double coverage = cloudsGetLayerCoverage(layer, location); + + return cloudsGetLayerDensity(layer, location, coverage); +} + +void cloudsBindRealDensityToRenderer(CloudsRenderer* renderer) +{ + renderer->getLayerDensity = _realGetDensity; +} diff --git a/src/rendering/clouds/clo_density.h b/src/rendering/clouds/clo_density.h new file mode 100644 index 0000000..afdf125 --- /dev/null +++ b/src/rendering/clouds/clo_density.h @@ -0,0 +1,45 @@ +#ifndef _PAYSAGES_CLOUDS_DENSITY_H_ +#define _PAYSAGES_CLOUDS_DENSITY_H_ + +#include "public.h" + +/** + * Coverage/density management in a cloud layer. + */ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * Get the local coverage of a cloud layer [0.0;1.0] + * + * 0.0 means no cloud is present. + * 1.0 means full layer. + */ +double cloudsGetLayerCoverage(CloudsLayerDefinition* layer, Vector3 location); + +/** + * Get the local density of a cloud layer at a given point [0.0;1.0]. + * + * 0.0 means no cloud is present. + * 1.0 means full density (deep inside cloud). + */ +double cloudsGetLayerDensity(CloudsLayerDefinition* layer, Vector3 location, double coverage); + +/* + * Bind fake density functions to a renderer. + */ +void cloudsBindFakeDensityToRenderer(CloudsRenderer* renderer); + +/* + * Bind real density functions to a renderer. + */ +void cloudsBindRealDensityToRenderer(CloudsRenderer* renderer); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/rendering/clouds/clo_rendering.c b/src/rendering/clouds/clo_rendering.c index fc97f72..e5e8ada 100644 --- a/src/rendering/clouds/clo_rendering.c +++ b/src/rendering/clouds/clo_rendering.c @@ -3,6 +3,7 @@ #include #include "../tools.h" #include "../renderer.h" +#include "clo_density.h" /******************** Fake ********************/ static int _fakeAlterLight(Renderer* renderer, LightDefinition* light, Vector3 location) @@ -23,21 +24,6 @@ static Color _fakeGetColor(Renderer* renderer, Color base, Vector3 start, Vector return base; } -static CloudsInfo _fakeGetLayerInfo(Renderer* renderer, CloudsLayerDefinition* layer, Vector3 location) -{ - UNUSED(renderer); - UNUSED(layer); - UNUSED(location); - - CloudsInfo result; - - result.inside = 0; - result.density = 0.0; - result.distance_to_edge = 1.0; - - return result; -} - /******************** Real ********************/ /*static int _cmpLayer(const void* layer1, const void* layer2) { @@ -89,7 +75,8 @@ static CloudsRenderer* _createRenderer() result->getColor = _fakeGetColor; result->alterLight = (FuncLightingAlterLight)_fakeAlterLight; - result->getLayerInfo = _fakeGetLayerInfo; + + cloudsBindFakeDensityToRenderer(result); return result; } @@ -106,7 +93,8 @@ static void _bindRenderer(Renderer* renderer, CloudsDefinition* definition) renderer->clouds->getColor = _getColor; renderer->clouds->alterLight = (FuncLightingAlterLight)_alterLight; - renderer->clouds->getLayerInfo = cloudsGetLayerInfo; + + cloudsBindRealDensityToRenderer(renderer->clouds); lightingManagerRegisterFilter(renderer->lighting, (FuncLightingAlterLight)_alterLight, renderer); } diff --git a/src/rendering/clouds/clo_tools.c b/src/rendering/clouds/clo_tools.c index dd34015..cadf4c0 100644 --- a/src/rendering/clouds/clo_tools.c +++ b/src/rendering/clouds/clo_tools.c @@ -4,6 +4,7 @@ * Clouds tools. */ +#include "clo_walking.h" #include "../renderer.h" #include "../tools.h" @@ -46,179 +47,6 @@ static inline Vector3 _getNormal(CloudsLayerDefinition* layer, Vector3 position, return v3Normalize(result); } -/** - * Optimize the search limits in a layer. - * - * @param layer The cloud layer - * @param start Start of the search to optimize - * @param end End of the search to optimize - * @return 0 if the search is useless - */ -static int _optimizeSearchLimits(CloudsLayerDefinition* layer, Vector3* start, Vector3* end) -{ - Vector3 diff; - - if (start->y > layer->lower_altitude + layer->thickness) - { - if (end->y >= layer->lower_altitude + layer->thickness) - { - return 0; - } - else - { - diff = v3Sub(*end, *start); - *start = v3Add(*start, v3Scale(diff, (layer->lower_altitude + layer->thickness - start->y) / diff.y)); - if (end->y < layer->lower_altitude) - { - *end = v3Add(*end, v3Scale(diff, (layer->lower_altitude - end->y) / diff.y)); - } - } - } - else if (start->y < layer->lower_altitude) - { - if (end->y <= layer->lower_altitude) - { - return 0; - } - else - { - diff = v3Sub(*end, *start); - *start = v3Add(*start, v3Scale(diff, (layer->lower_altitude - start->y) / diff.y)); - if (end->y >= layer->lower_altitude + layer->thickness) - { - *end = v3Add(*end, v3Scale(diff, (layer->lower_altitude + layer->thickness - end->y) / diff.y)); - } - } - } - else /* start is inside layer */ - { - diff = v3Sub(*end, *start); - if (end->y > layer->lower_altitude + layer->thickness) - { - *end = v3Add(*start, v3Scale(diff, (layer->lower_altitude + layer->thickness - start->y) / diff.y)); - } - else if (end->y < layer->lower_altitude) - { - *end = v3Add(*start, v3Scale(diff, (layer->lower_altitude - start->y) / diff.y)); - } - } - - /* TODO Limit the search length */ - return 1; -} - -/** - * Go through the cloud layer to find segments (parts of the lookup that are likely to contain cloud). - * - * @param definition The cloud layer - * @param renderer The renderer environment - * @param start Start position of the lookup (already optimized) - * @param direction Normalized direction of the lookup - * @param max_segments Maximum number of segments to collect - * @param max_inside_length Maximum length to spend inside the cloud - * @param max_total_length Maximum lookup length - * @param inside_length Resulting length inside cloud (sum of all segments length) - * @param total_length Resulting lookup length - * @param out_segments Allocated space to fill found segments - * @return Number of segments found - */ -static int _getPrimarySegments(CloudsLayerDefinition* definition, Renderer* renderer, Vector3 start, Vector3 direction, int max_segments, double max_inside_length, double max_total_length, double* inside_length, double* total_length, CloudSegment* out_segments) -{ - int inside, segment_count; - double current_total_length, current_inside_length; - double step_length, segment_length; - Vector3 walker, step, segment_start; - CloudsInfo info; - double render_precision; - - if (max_segments <= 0) - { - return 0; - } - - render_precision = 1.005 - 0.01 * (double)(renderer->render_quality * renderer->render_quality); - if (render_precision > max_total_length / 10.0) - { - render_precision = max_total_length / 10.0; - } - else if (render_precision < max_total_length / 10000.0) - { - render_precision = max_total_length / 10000.0; - } - - segment_count = 0; - current_total_length = 0.0; - current_inside_length = 0.0; - segment_length = 0.0; - walker = start; - info = renderer->clouds->getLayerInfo(renderer, definition, start); - inside = info.inside; - step = v3Scale(direction, render_precision); - - do - { - walker = v3Add(walker, step); - step_length = v3Norm(step); - current_total_length += step_length; - - if (current_total_length >= max_total_length || current_inside_length > max_inside_length) - { - info.distance_to_edge = 0.0; - info.inside = 0; - } - else - { - info = renderer->clouds->getLayerInfo(renderer, definition, walker); - } - - if (info.inside) - { - if (inside) - { - /* inside the cloud */ - segment_length += step_length; - current_inside_length += step_length; - } - else - { - /* entering the cloud */ - segment_length = step_length; - segment_start = walker; - current_inside_length += segment_length; - /* TODO Refine entry position */ - - inside = 1; - } - } - else - { - if (inside) - { - /* exiting the cloud */ - segment_length += step_length; - current_inside_length += step_length; - - out_segments->start = segment_start; - out_segments->end = walker; - out_segments->length = segment_length; - out_segments++; - if (++segment_count >= max_segments) - { - break; - } - /* TODO Refine exit position */ - - inside = 0; - } - } - step = v3Scale(direction, (info.distance_to_edge < render_precision) ? render_precision : info.distance_to_edge); - } while (inside || (walker.y <= definition->lower_altitude + definition->thickness + 0.001 && walker.y >= definition->lower_altitude - 0.001 && current_total_length < max_total_length && current_inside_length < max_inside_length)); - - *total_length = current_total_length; - *inside_length = current_inside_length; - return segment_count; -} - static Color _applyLayerLighting(CloudsLayerDefinition* definition, Renderer* renderer, Vector3 location, double detail) { Vector3 normal; @@ -262,32 +90,22 @@ static Color _applyLayerLighting(CloudsLayerDefinition* definition, Renderer* re Color cloudsApplyLayer(CloudsLayerDefinition* definition, Color base, Renderer* renderer, Vector3 start, Vector3 end) { int segment_count; - double max_length, total_length, inside_length; - Vector3 direction; Color col; - CloudSegment segments[MAX_SEGMENT_COUNT]; + CloudPrimarySegment segments[MAX_SEGMENT_COUNT]; - if (!_optimizeSearchLimits(definition, &start, &end)) - { - return base; - } - - direction = v3Sub(end, start); - max_length = v3Norm(direction); - direction = v3Normalize(direction); - - segment_count = _getPrimarySegments(definition, renderer, start, direction, MAX_SEGMENT_COUNT, definition->transparencydepth * (double)renderer->render_quality, max_length, &inside_length, &total_length, segments); + segment_count = cloudsGetLayerPrimarySegments(renderer, definition, start, end, MAX_SEGMENT_COUNT, segments); /* TODO Crawl in segments for render */ col = definition->material.base; - if (definition->transparencydepth == 0 || inside_length >= definition->transparencydepth) + col.a = 0.0; + /*if (definition->transparencydepth == 0 || inside_length >= definition->transparencydepth) { col.a = 1.0; } else { col.a = inside_length / definition->transparencydepth; - } + }*/ col = renderer->atmosphere->applyAerialPerspective(renderer, start, col).final; @@ -298,10 +116,10 @@ Color cloudsApplyLayer(CloudsLayerDefinition* definition, Color base, Renderer* Color cloudsLayerFilterLight(CloudsLayerDefinition* definition, Renderer* renderer, Color light, Vector3 location, Vector3 light_location, Vector3 direction_to_light) { - double inside_depth, total_depth, factor; + /*double inside_depth, total_depth, factor; CloudSegment segments[MAX_SEGMENT_COUNT]; - if (!_optimizeSearchLimits(definition, &location, &light_location)) + if (!cloudsOptimizeWalkingBounds(definition, &location, &light_location)) { return light; } @@ -325,93 +143,7 @@ Color cloudsLayerFilterLight(CloudsLayerDefinition* definition, Renderer* render light.r = light.r * factor; light.g = light.g * factor; - light.b = light.b * factor; + light.b = light.b * factor;*/ return light; } - -/* - * Get the coverage factor at the given location [0.0;1.0]. - * 0.0 means no cloud is present. - * 1.0 means full layer. - */ -static inline double _getLayerCoverage(CloudsLayerDefinition* layer, double x, double z) -{ - if (layer->base_coverage == 0.0) - { - return 0.0; - } - else - { - double coverage = noiseGet2DTotal(layer->_coverage_noise, x / layer->shape_scaling, z / layer->shape_scaling); - coverage -= (1.0 - layer->base_coverage); - return (coverage <= 0.0) ? 0.0 : coverage; - } -} - -/* - * Get the local density factor at the given location [0.0;1.0]. - * 0.0 means no cloud is present. - * 1.0 means full density (deep inside cloud). - */ -static inline double _getLayerDensity(CloudsLayerDefinition* layer, Vector3 location, double coverage) -{ - if (coverage == 0.0) - { - return 0.0; - } - else if (coverage == 1.0) - { - return 1.0; - } - else - { - double density = noiseGet3DTotal(layer->_shape_noise, location.x / layer->shape_scaling, location.y / layer->shape_scaling, location.z / layer->shape_scaling); - density -= (0.5 - coverage); - return (density <= 0.0) ? 0.0 : density; - } -} - -CloudsInfo cloudsGetLayerInfo(Renderer* renderer, CloudsLayerDefinition* layer, Vector3 location) -{ - CloudsInfo result; - - UNUSED(renderer); - - result.density = 0.0; - result.distance_to_edge = 0.1; - - /* Get coverage info */ - double coverage = _getLayerCoverage(layer, location.x, location.z); - if (coverage <= 0.0) - { - /* TODO Distance to edge */ - } - else - { - /* Apply altitude to coverage */ - coverage *= curveGetValue(layer->_coverage_by_altitude, (location.y - layer->lower_altitude) / layer->thickness); - if (coverage <= 0.0) - { - /* TODO Distance to edge */ - } - else - { - /* Get local density */ - result.density = _getLayerDensity(layer, location, coverage); - if (result.density <= 0) - { - /* TODO Distance to edge */ - } - else - { - /* TODO Distance to edge */ - } - } - } - - result.inside = (result.density > 0.0); - - return result; -} - diff --git a/src/rendering/clouds/clo_walking.c b/src/rendering/clouds/clo_walking.c new file mode 100644 index 0000000..5efb929 --- /dev/null +++ b/src/rendering/clouds/clo_walking.c @@ -0,0 +1,157 @@ +#include "clo_walking.h" + +#include "../renderer.h" + +int cloudsOptimizeWalkingBounds(CloudsLayerDefinition* layer, Vector3* start, Vector3* end) +{ + Vector3 diff; + + if (start->y > layer->lower_altitude + layer->thickness) + { + if (end->y >= layer->lower_altitude + layer->thickness) + { + return 0; + } + else + { + diff = v3Sub(*end, *start); + *start = v3Add(*start, v3Scale(diff, (layer->lower_altitude + layer->thickness - start->y) / diff.y)); + if (end->y < layer->lower_altitude) + { + *end = v3Add(*end, v3Scale(diff, (layer->lower_altitude - end->y) / diff.y)); + } + } + } + else if (start->y < layer->lower_altitude) + { + if (end->y <= layer->lower_altitude) + { + return 0; + } + else + { + diff = v3Sub(*end, *start); + *start = v3Add(*start, v3Scale(diff, (layer->lower_altitude - start->y) / diff.y)); + if (end->y >= layer->lower_altitude + layer->thickness) + { + *end = v3Add(*end, v3Scale(diff, (layer->lower_altitude + layer->thickness - end->y) / diff.y)); + } + } + } + else /* start is inside layer */ + { + diff = v3Sub(*end, *start); + if (end->y > layer->lower_altitude + layer->thickness) + { + *end = v3Add(*start, v3Scale(diff, (layer->lower_altitude + layer->thickness - start->y) / diff.y)); + } + else if (end->y < layer->lower_altitude) + { + *end = v3Add(*start, v3Scale(diff, (layer->lower_altitude - start->y) / diff.y)); + } + } + + /* TODO Limit the search length */ + return 1; +} + +int cloudsGetLayerPrimarySegments(Renderer* renderer, CloudsLayerDefinition* layer, Vector3 start, Vector3 end, int max_segments, CloudPrimarySegment* out_segments) +{ + int inside, segment_count; + double step_length, segment_length; + Vector3 diff, walker, segment_start; + double render_precision, density; + double diff_length, progress; + + if (max_segments <= 0) + { + return 0; + } + + if (!cloudsOptimizeWalkingBounds(layer, &start, &end)) + { + return 0; + } + + diff = v3Sub(end, start); + diff_length = v3Norm(diff); + + if (diff_length < 0.000001) + { + return 0; + } + + render_precision = 1.005 - 0.01 * (double)(renderer->render_quality * renderer->render_quality); + /*if (render_precision > max_total_length / 10.0) + { + render_precision = max_total_length / 10.0; + } + else if (render_precision < max_total_length / 10000.0) + { + render_precision = max_total_length / 10000.0; + }*/ + + segment_count = 0; + segment_length = 0.0; + density = renderer->clouds->getLayerDensity(renderer, layer, start); + progress = 0.0; + step_length = render_precision; + inside = (density > 0.0); + + do + { + progress += step_length; + walker = v3Add(start, v3Scale(diff, progress / diff_length)); + + if (progress >= diff_length) + { + density = 0.0; + } + else + { + density = renderer->clouds->getLayerDensity(renderer, layer, walker); + } + + if (density > 0.0) + { + if (inside) + { + /* inside the cloud */ + segment_length += step_length; + } + else + { + /* entering the cloud */ + segment_length = step_length; + segment_start = v3Add(start, v3Scale(diff, (progress - step_length) / diff_length)); + /* TODO Refine entry position */ + + inside = 1; + } + } + else + { + if (inside) + { + /* exiting the cloud */ + segment_length += step_length; + + out_segments->enter = segment_start; + out_segments->exit = walker; + out_segments->length = segment_length; + out_segments++; + if (++segment_count >= max_segments) + { + break; + } + /* TODO Refine exit position */ + + inside = 0; + } + } + /* step = v3Scale(direction, (info.distance_to_edge < render_precision) ? render_precision : info.distance_to_edge); */ + } + while (inside || (walker.y <= layer->lower_altitude + layer->thickness + 0.001 && walker.y >= layer->lower_altitude - 0.001 && progress < diff_length)); + + return segment_count; +} diff --git a/src/rendering/clouds/clo_walking.h b/src/rendering/clouds/clo_walking.h new file mode 100644 index 0000000..2ad19b3 --- /dev/null +++ b/src/rendering/clouds/clo_walking.h @@ -0,0 +1,50 @@ +#ifndef _PAYSAGES_CLOUDS_WALKING_H_ +#define _PAYSAGES_CLOUDS_WALKING_H_ + +#include "public.h" +#include "../tools/euclid.h" + +/** + * Functions to walk through a cloud layer. + */ + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef struct +{ + Vector3 enter; + Vector3 exit; + double length; +} CloudPrimarySegment; + +/** + * Optimize the search limits in a layer. + * + * @param layer The cloud layer + * @param start Start of the search to optimize + * @param end End of the search to optimize + * @return 0 if the search is useless + */ +int cloudsOptimizeWalkingBounds(CloudsLayerDefinition* layer, Vector3* start, Vector3* end); + +/** + * Go through the cloud layer to find segments (parts of the lookup that are likely to contain cloud). + * + * @param renderer The renderer environment + * @param layer The cloud layer + * @param start Start position of the lookup + * @param end End position of the lookup + * @param max_segments Maximum number of segments to collect + * @param out_segments Allocated space to fill found segments + * @return Number of segments found + */ +int cloudsGetLayerPrimarySegments(Renderer* renderer, CloudsLayerDefinition* layer, Vector3 start, Vector3 end, int max_segments, CloudPrimarySegment* out_segments); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/rendering/clouds/private.h b/src/rendering/clouds/private.h index bc597da..47c44bd 100644 --- a/src/rendering/clouds/private.h +++ b/src/rendering/clouds/private.h @@ -6,18 +6,9 @@ #define CLOUDS_MAX_LAYERS 6 #define MAX_SEGMENT_COUNT 100 -typedef struct -{ - Vector3 start; - Vector3 end; - double length; -} CloudSegment; - void cloudsLayerValidateDefinition(CloudsLayerDefinition* definition); Color cloudsLayerFilterLight(CloudsLayerDefinition* definition, Renderer* renderer, Color light, Vector3 location, Vector3 light_location, Vector3 direction_to_light); Color cloudsApplyLayer(CloudsLayerDefinition* definition, Color base, Renderer* renderer, Vector3 start, Vector3 end); -CloudsInfo cloudsGetLayerInfo(Renderer* renderer, CloudsLayerDefinition* layer, Vector3 location); - #endif diff --git a/src/rendering/clouds/public.h b/src/rendering/clouds/public.h index 9bb6016..beded50 100644 --- a/src/rendering/clouds/public.h +++ b/src/rendering/clouds/public.h @@ -59,15 +59,8 @@ typedef struct Layers* layers; } CloudsDefinition; -typedef struct -{ - int inside; - double density; - double distance_to_edge; -} CloudsInfo; - typedef Color (*FuncCloudsGetColor)(Renderer* renderer, Color base, Vector3 start, Vector3 end); -typedef CloudsInfo (*FuncCloudsGetLayerInfo)(Renderer* renderer, CloudsLayerDefinition* layer, Vector3 location); +typedef double (*FuncCloudsGetLayerDensity)(Renderer* renderer, CloudsLayerDefinition* layer, Vector3 location); typedef struct { @@ -75,7 +68,7 @@ typedef struct FuncCloudsGetColor getColor; FuncLightingAlterLight alterLight; - FuncCloudsGetLayerInfo getLayerInfo; + FuncCloudsGetLayerDensity getLayerDensity; } CloudsRenderer; diff --git a/src/rendering/tools/euclid.h b/src/rendering/tools/euclid.h index b5bbd6f..90f68a6 100644 --- a/src/rendering/tools/euclid.h +++ b/src/rendering/tools/euclid.h @@ -77,6 +77,11 @@ extern Vector3 VECTOR_SOUTH; extern Vector3 VECTOR_EAST; extern Vector3 VECTOR_WEST; +static inline Vector3 v3(double x, double y, double z) +{ + Vector3 result = {x, y, z}; + return result; +}; void v3Save(PackStream* stream, Vector3* v); void v3Load(PackStream* stream, Vector3* v); Vector3 v3Translate(Vector3 v1, double x, double y, double z); diff --git a/src/testing/common.h b/src/testing/common.h index 3aabbf5..daf3b82 100644 --- a/src/testing/common.h +++ b/src/testing/common.h @@ -35,10 +35,30 @@ static inline int _double_not_equals(double x, double y) { return fabs(x - y) >= 0.00000000001; } +static inline int _double_greater(double x, double y) +{ + return _double_not_equals(x, y) || (x > y); +} +static inline int _double_greater_or_equal(double x, double y) +{ + return _double_equals(x, y) || (x >= y); +} +static inline int _double_less(double x, double y) +{ + return _double_not_equals(x, y) || (x < y); +} +static inline int _double_less_or_equal(double x, double y) +{ + return _double_equals(x, y) || (x <= y); +} #define _ck_assert_double(F, X, O, Y) ck_assert_msg(F(X, Y), "Assertion '"#X#O#Y"' failed: "#X"=%f, "#Y"=%f", X, Y) #define ck_assert_double_eq(X, Y) _ck_assert_double(_double_equals, X, ==, Y) #define ck_assert_double_ne(X, Y) _ck_assert_double(_double_not_equals, X, !=, Y) +#define ck_assert_double_gt(X, Y) _ck_assert_double(_double_greater, X, >, Y) +#define ck_assert_double_lt(X, Y) _ck_assert_double(_double_greater_or_equal, X, >=, Y) +#define ck_assert_double_gte(X, Y) _ck_assert_double(_double_less, X, <, Y) +#define ck_assert_double_lte(X, Y) _ck_assert_double(_double_less_or_equal, X, <=, Y) /***** Generic comparison assertions *****/ @@ -56,4 +76,9 @@ static inline char* _ck_gen_##_type_##_str(char* buffer, _type_ X) { \ static char _ck_gen_strbuf1[101]; static char _ck_gen_strbuf2[101]; + +/***** Some builtin comparisons *****/ +#define ck_assert_double_in_range(_double_, _x_, _y_) ck_assert_double_gte(_double_, _x_);ck_assert_double_lte(_double_, _y_) +#define ck_assert_vector_values(_vector_, _x_, _y_, _z_) ck_assert_double_eq(_vector_.x, _x_);ck_assert_double_eq(_vector_.y, _y_);ck_assert_double_eq(_vector_.z, _z_) + #endif diff --git a/src/testing/main.c b/src/testing/main.c index 5e32f3e..d66edc4 100644 --- a/src/testing/main.c +++ b/src/testing/main.c @@ -1,21 +1,30 @@ #include #include +#include "rendering/main.h" + extern void test_euclid_case(Suite* s); extern void test_camera_case(Suite* s); +extern void test_clouds_case(Suite* s); int main(int argc, char** argv) { int number_failed; Suite *s = suite_create("rendering"); + paysagesInit(); + /* TODO Find a way to automate this */ test_euclid_case(s); test_camera_case(s); + test_clouds_case(s); SRunner *sr = srunner_create(s); srunner_run_all(sr, CK_NORMAL); number_failed = srunner_ntests_failed(sr); srunner_free(sr); + + paysagesQuit(); + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } diff --git a/src/testing/test_camera.c b/src/testing/test_camera.c index 8c8ada9..6a8920a 100644 --- a/src/testing/test_camera.c +++ b/src/testing/test_camera.c @@ -2,8 +2,6 @@ #include "rendering/camera.h" -#define _checkVector(_vector_, _x_, _y_, _z_) ck_assert_double_eq(_vector_.x, _x_);ck_assert_double_eq(_vector_.y, _y_);ck_assert_double_eq(_vector_.z, _z_) - START_TEST(test_camera_definition) { CameraDefinition* cam; @@ -12,8 +10,8 @@ START_TEST(test_camera_definition) cameraSetLocationCoords(cam, 0.0, 1.0, 1.0); cameraSetTargetCoords(cam, 0.0, 0.0, 0.0); - _checkVector(cameraGetLocation(cam), 0.0, 1.0, 1.0); - _checkVector(cameraGetTarget(cam), 0.0, 0.0, 0.0); + ck_assert_vector_values(cameraGetLocation(cam), 0.0, 1.0, 1.0); + ck_assert_vector_values(cameraGetTarget(cam), 0.0, 0.0, 0.0); cameraDeleteDefinition(cam); } diff --git a/src/testing/test_clouds.c b/src/testing/test_clouds.c new file mode 100644 index 0000000..7ab715e --- /dev/null +++ b/src/testing/test_clouds.c @@ -0,0 +1,252 @@ +#include "testing/common.h" + +#include "rendering/renderer.h" +#include "rendering/tools.h" +#include "rendering/clouds/public.h" +#include "rendering/clouds/clo_density.h" +#include "rendering/clouds/clo_walking.h" + +START_TEST(test_clouds_density) +{ + /* Setup */ + double x, y, z; + CloudsLayerDefinition* layer; + layer = cloudsGetLayerType().callback_create(); + + /* Test default coverage (empty) */ + for (x = -10.0; x < 10.0; x += 10.0) + { + for (y = -10.0; y < 10.0; y += 10.0) + { + for (z = -10.0; z < 10.0; z += 10.0) + { + ck_assert_double_eq(cloudsGetLayerCoverage(layer, v3(x, y, z)), 0.0); + } + } + } + + /* Test coverage by altitude */ + layer->base_coverage = 1.0; + layer->lower_altitude = -1.0; + layer->thickness = 2.0; + cloudsGetLayerType().callback_validate(layer); + layer->base_coverage = 1.0; + noiseForceValue(layer->_coverage_noise, 1.0); + for (x = -10.0; x < 10.0; x += 10.0) + { + for (z = -10.0; z < 10.0; z += 10.0) + { + ck_assert_double_eq(cloudsGetLayerCoverage(layer, v3(x, 0.0, z)), 1.0); + ck_assert_double_eq(cloudsGetLayerCoverage(layer, v3(x, 0.5, z)), 0.5); + ck_assert_double_eq(cloudsGetLayerCoverage(layer, v3(x, 1.0, z)), 0.0); + ck_assert_double_eq(cloudsGetLayerCoverage(layer, v3(x, 1.5, z)), 0.0); + ck_assert_double_eq(cloudsGetLayerCoverage(layer, v3(x, -0.5, z)), 0.5); + ck_assert_double_eq(cloudsGetLayerCoverage(layer, v3(x, -1.0, z)), 0.0); + ck_assert_double_eq(cloudsGetLayerCoverage(layer, v3(x, -1.5, z)), 0.0); + } + } + layer->base_coverage = 0.5; + noiseForceValue(layer->_coverage_noise, 1.0); + for (x = -10.0; x < 10.0; x += 10.0) + { + for (z = -10.0; z < 10.0; z += 10.0) + { + ck_assert_double_eq(cloudsGetLayerCoverage(layer, v3(x, 0.0, z)), 0.5); + ck_assert_double_eq(cloudsGetLayerCoverage(layer, v3(x, 0.5, z)), 0.25); + ck_assert_double_eq(cloudsGetLayerCoverage(layer, v3(x, 1.0, z)), 0.0); + ck_assert_double_eq(cloudsGetLayerCoverage(layer, v3(x, 1.5, z)), 0.0); + ck_assert_double_eq(cloudsGetLayerCoverage(layer, v3(x, -0.5, z)), 0.25); + ck_assert_double_eq(cloudsGetLayerCoverage(layer, v3(x, -1.0, z)), 0.0); + ck_assert_double_eq(cloudsGetLayerCoverage(layer, v3(x, -1.5, z)), 0.0); + } + } + layer->base_coverage = 1.0; + noiseForceValue(layer->_coverage_noise, 0.5); + for (x = -10.0; x < 10.0; x += 10.0) + { + for (z = -10.0; z < 10.0; z += 10.0) + { + ck_assert_double_eq(cloudsGetLayerCoverage(layer, v3(x, 0.0, z)), 0.5); + ck_assert_double_eq(cloudsGetLayerCoverage(layer, v3(x, 0.5, z)), 0.25); + ck_assert_double_eq(cloudsGetLayerCoverage(layer, v3(x, 1.0, z)), 0.0); + ck_assert_double_eq(cloudsGetLayerCoverage(layer, v3(x, 1.5, z)), 0.0); + ck_assert_double_eq(cloudsGetLayerCoverage(layer, v3(x, -0.5, z)), 0.25); + ck_assert_double_eq(cloudsGetLayerCoverage(layer, v3(x, -1.0, z)), 0.0); + ck_assert_double_eq(cloudsGetLayerCoverage(layer, v3(x, -1.5, z)), 0.0); + } + } + + /* TODO Test fake renderer */ + + /* TODO Test real renderer */ + + /* Teardown */ + cloudsGetLayerType().callback_delete(layer); +} +END_TEST + +START_TEST(test_clouds_walking_boundaries) +{ + Vector3 start, end; + int result; + CloudsLayerDefinition* layer; + layer = cloudsGetLayerType().callback_create(); + layer->base_coverage = 1.0; + layer->lower_altitude = -1.0; + layer->thickness = 2.0; + cloudsGetLayerType().callback_validate(layer); + + /* Basic cases */ + start = v3(0.0, -3.0, 0.0); + end = v3(0.0, -2.0, 0.0); + result = cloudsOptimizeWalkingBounds(layer, &start, &end); + ck_assert_int_eq(result, 0); + + start = v3(0.0, 2.0, 0.0); + end = v3(0.0, 3.0, 0.0); + result = cloudsOptimizeWalkingBounds(layer, &start, &end); + ck_assert_int_eq(result, 0); + + start = v3(0.0, -2.0, 0.0); + end = v3(0.0, 2.0, 0.0); + result = cloudsOptimizeWalkingBounds(layer, &start, &end); + ck_assert_int_eq(result, 1); + ck_assert_vector_values(start, 0.0, -1.0, 0.0); + ck_assert_vector_values(end, 0.0, 1.0, 0.0); + + start = v3(0.0, 0.0, 0.0); + end = v3(0.0, 2.0, 0.0); + result = cloudsOptimizeWalkingBounds(layer, &start, &end); + ck_assert_int_eq(result, 1); + ck_assert_vector_values(start, 0.0, 0.0, 0.0); + ck_assert_vector_values(end, 0.0, 1.0, 0.0); + + start = v3(0.0, -2.0, 0.0); + end = v3(0.0, 0.0, 0.0); + result = cloudsOptimizeWalkingBounds(layer, &start, &end); + ck_assert_int_eq(result, 1); + ck_assert_vector_values(start, 0.0, -1.0, 0.0); + ck_assert_vector_values(end, 0.0, 0.0, 0.0); + + /* Basic cases (inverted) */ + start = v3(0.0, -2.0, 0.0); + end = v3(0.0, -3.0, 0.0); + result = cloudsOptimizeWalkingBounds(layer, &start, &end); + ck_assert_int_eq(result, 0); + + start = v3(0.0, 3.0, 0.0); + end = v3(0.0, 2.0, 0.0); + result = cloudsOptimizeWalkingBounds(layer, &start, &end); + ck_assert_int_eq(result, 0); + + start = v3(0.0, 2.0, 0.0); + end = v3(0.0, -2.0, 0.0); + result = cloudsOptimizeWalkingBounds(layer, &start, &end); + ck_assert_int_eq(result, 1); + ck_assert_vector_values(start, 0.0, 1.0, 0.0); + ck_assert_vector_values(end, 0.0, -1.0, 0.0); + + start = v3(0.0, 2.0, 0.0); + end = v3(0.0, 0.0, 0.0); + result = cloudsOptimizeWalkingBounds(layer, &start, &end); + ck_assert_int_eq(result, 1); + ck_assert_vector_values(start, 0.0, 1.0, 0.0); + ck_assert_vector_values(end, 0.0, 0.0, 0.0); + + start = v3(0.0, 0.0, 0.0); + end = v3(0.0, -2.0, 0.0); + result = cloudsOptimizeWalkingBounds(layer, &start, &end); + ck_assert_int_eq(result, 1); + ck_assert_vector_values(start, 0.0, 0.0, 0.0); + ck_assert_vector_values(end, 0.0, -1.0, 0.0); + + /* Horizontal cases */ + start = v3(0.0, 2.0, 0.0); + end = v3(10.0, 2.0, 0.0); + result = cloudsOptimizeWalkingBounds(layer, &start, &end); + ck_assert_int_eq(result, 0); + + start = v3(0.0, 1.00001, 0.0); + end = v3(10.0, 1.00001, 0.0); + result = cloudsOptimizeWalkingBounds(layer, &start, &end); + ck_assert_int_eq(result, 0); + + start = v3(0.0, -1.00001, 0.0); + end = v3(10.0, -1.00001, 0.0); + result = cloudsOptimizeWalkingBounds(layer, &start, &end); + ck_assert_int_eq(result, 0); + + start = v3(0.0, -2.0, 0.0); + end = v3(10.0, -2.0, 0.0); + result = cloudsOptimizeWalkingBounds(layer, &start, &end); + ck_assert_int_eq(result, 0); + + start = v3(0.0, 0.0, 0.0); + end = v3(10.0, 0.0, 0.0); + result = cloudsOptimizeWalkingBounds(layer, &start, &end); + ck_assert_int_eq(result, 1); + ck_assert_vector_values(start, 0.0, 0.0, 0.0); + ck_assert_vector_values(end, 10.0, 0.0, 0.0); + + cloudsGetLayerType().callback_delete(layer); +} +END_TEST + +static double _getLayerDensitySinX(Renderer* renderer, CloudsLayerDefinition* layer, Vector3 location) +{ + UNUSED(renderer); + UNUSED(layer); + double density = sin(location.x * (2.0 * M_PI)); + return (density > 0.0) ? density : 0.0; +} + +START_TEST(test_clouds_primary_segments) +{ + int segment_count, i; + CloudPrimarySegment segments[10]; + + CloudsLayerDefinition* layer; + layer = cloudsGetLayerType().callback_create(); + layer->lower_altitude = -1.0; + layer->thickness = 2.0; + cloudsGetLayerType().callback_validate(layer); + + Renderer* renderer; + renderer = rendererCreate(); + + renderer->render_quality = 8; + renderer->clouds->getLayerDensity = _getLayerDensitySinX; + + segment_count = cloudsGetLayerPrimarySegments(renderer, layer, v3(-0.4, 0.0, 0.0), v3(1.9, 0.0, 0.0), 10, segments); + ck_assert_int_eq(segment_count, 2); + for (i = 0; i < segment_count; i++) + { + ck_assert_double_eq(segments[i].enter.y, 0.0); + ck_assert_double_eq(segments[i].enter.z, 0.0); + ck_assert_double_eq(segments[i].exit.y, 0.0); + ck_assert_double_eq(segments[i].exit.z, 0.0); + } + ck_assert_double_in_range(segments[0].enter.x, -0.5, 0.0); + ck_assert_double_in_range(segments[0].exit.x, 0.5, 1.0); + ck_assert_double_in_range(segments[0].length, 0.5, 1.5); + ck_assert_double_gte(segments[1].enter.x, segments[0].exit.x); + ck_assert_double_in_range(segments[1].enter.x, 0.5, 1.0); + ck_assert_double_in_range(segments[1].exit.x, 1.5, 2.0); + ck_assert_double_in_range(segments[1].length, 0.5, 1.5); + + cloudsGetLayerType().callback_delete(layer); + rendererDelete(renderer); +} +END_TEST + +START_TEST(test_clouds_preview_color) +{ + Renderer* renderer = cloudsCreatePreviewColorRenderer(); + + /* TODO Test the density overriding */ + + rendererDelete(renderer); +} +END_TEST + +TEST_CASE(clouds, test_clouds_density, test_clouds_walking_boundaries, test_clouds_primary_segments, test_clouds_preview_color)