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/editing/formclouds.cpp b/src/editing/formclouds.cpp index 885e73b..4a47163 100644 --- a/src/editing/formclouds.cpp +++ b/src/editing/formclouds.cpp @@ -1,5 +1,6 @@ #include "formclouds.h" +#include "rendering/clouds/clo_preview.h" #include "rendering/tools/color.h" #include "rendering/tools/euclid.h" #include "rendering/scenery.h" @@ -12,24 +13,22 @@ class PreviewCloudsCoverage:public BasePreview public: PreviewCloudsCoverage(QWidget* parent, CloudsLayerDefinition* layer):BasePreview(parent) { - _renderer = cloudsCreatePreviewCoverageRenderer(); + _renderer = cloudsPreviewCoverageCreateRenderer(); _3d = true; _original_layer = layer; - _preview_definition = (CloudsDefinition*)CloudsDefinitionClass.create(); addToggle("3d", tr("Perspective"), true); configScaling(100.0, 1000.0, 20.0, 200.0); } ~PreviewCloudsCoverage() { - CloudsDefinitionClass.destroy(_preview_definition); rendererDelete(_renderer); } protected: Color getColor(double x, double y) { - return cloudsGetPreviewCoverage(_renderer, x, y, scaling, _3d); + return cloudsPreviewCoverageGetPixel(_renderer, x, y, scaling, _3d); } virtual void toggleChangeEvent(QString key, bool value) { @@ -41,15 +40,12 @@ protected: } void updateData() { - layersDeleteLayer(_preview_definition->layers, 0); - layersAddLayer(_preview_definition->layers, _original_layer); - CloudsRendererClass.bind(_renderer, _preview_definition); + cloudsPreviewCoverageBindLayer(_renderer, _original_layer); } private: Renderer* _renderer; CloudsLayerDefinition* _original_layer; - CloudsDefinition* _preview_definition; bool _3d; }; @@ -59,27 +55,28 @@ public: PreviewCloudsColor(QWidget* parent, CloudsLayerDefinition* layer):BasePreview(parent) { _original_layer = layer; - _preview_definition = (CloudsDefinition*)CloudsDefinitionClass.create(); - _renderer = cloudsCreatePreviewColorRenderer(); + _renderer = cloudsPreviewMaterialCreateRenderer(); configScaling(0.5, 2.0, 0.1, 2.0); } + + ~PreviewCloudsColor() + { + rendererDelete(_renderer); + } protected: Color getColor(double x, double y) { - return cloudsGetPreviewColor(_renderer, x, y); + return cloudsPreviewMaterialGetPixel(_renderer, x, y); } void updateData() { - layersDeleteLayer(_preview_definition->layers, 0); - layersAddLayer(_preview_definition->layers, _original_layer); - CloudsRendererClass.bind(_renderer, _preview_definition); + cloudsPreviewMaterialBindLayer(_renderer, _original_layer); } private: Renderer* _renderer; CloudsLayerDefinition* _original_layer; - CloudsDefinition* _preview_definition; }; /**************** Form ****************/ diff --git a/src/rendering/clouds/clo_preview.c b/src/rendering/clouds/clo_preview.c index df39f87..5a0afa6 100644 --- a/src/rendering/clouds/clo_preview.c +++ b/src/rendering/clouds/clo_preview.c @@ -18,7 +18,7 @@ Color _fakeApplyLightingToSurface(Renderer* renderer, Vector3 location, Vector3 return COLOR_WHITE; } -Renderer* cloudsCreatePreviewCoverageRenderer() +Renderer* cloudsPreviewCoverageCreateRenderer() { Renderer* result = rendererCreate(); result->render_quality = 5; @@ -26,7 +26,15 @@ Renderer* cloudsCreatePreviewCoverageRenderer() return result; } -Color cloudsGetPreviewCoverage(Renderer* renderer, double x, double y, double scaling, int perspective) +void cloudsPreviewCoverageBindLayer(Renderer* renderer, CloudsLayerDefinition* layer) +{ + CloudsDefinition* definition = (CloudsDefinition*)CloudsDefinitionClass.create(); + layersAddLayer(definition->layers, layer); + CloudsRendererClass.bind(renderer, definition); + CloudsDefinitionClass.destroy(definition); +} + +Color cloudsPreviewCoverageGetPixel(Renderer* renderer, double x, double y, double scaling, int perspective) { if (perspective) { @@ -55,25 +63,91 @@ Color cloudsGetPreviewCoverage(Renderer* renderer, double x, double y, double sc } } -Renderer* cloudsCreatePreviewColorRenderer() +static void _getLightingStatus(Renderer* renderer, LightStatus* status, Vector3 normal, int opaque) +{ + LightDefinition light; + + UNUSED(renderer); + UNUSED(normal); + UNUSED(opaque); + + light.color.r = 1.0; + light.color.g = 1.0; + light.color.b = 1.0; + light.direction.x = -1.0; + light.direction.y = -0.5; + light.direction.z = 1.0; + light.direction = v3Normalize(light.direction); + light.altered = 1; + light.reflection = 0.0; + lightingPushLight(status, &light); + + light.color.r = 0.2; + light.color.g = 0.2; + light.color.b = 0.2; + light.direction.x = 1.0; + light.direction.y = -0.5; + light.direction.z = -1.0; + light.direction = v3Normalize(light.direction); + light.altered = 0; + light.reflection = 0.0; + lightingPushLight(status, &light); +} + +Renderer* cloudsPreviewMaterialCreateRenderer() { Renderer* result = rendererCreate(); result->render_quality = 8; + result->atmosphere->getLightingStatus = _getLightingStatus; return result; } -Color cloudsGetPreviewColor(Renderer* renderer, double x, double y) +static double _getDensity(Renderer* renderer, CloudsLayerDefinition* layer, Vector3 location) +{ + UNUSED(renderer); + UNUSED(layer); + + double distance = v3Norm(location); + if (distance > 1.0) + { + return 0.0; + } + else if (distance < 0.8) + { + return 1.0; + } + else + { + return (1.0 - distance) / 0.2; + } +} + +void cloudsPreviewMaterialBindLayer(Renderer* renderer, CloudsLayerDefinition* layer) +{ + CloudsDefinition* definition = (CloudsDefinition*)CloudsDefinitionClass.create(); + layersAddLayer(definition->layers, layer); + CloudsRendererClass.bind(renderer, definition); + CloudsDefinitionClass.destroy(definition); + + layer = layersGetLayer(renderer->clouds->definition->layers, 0); + layer->lower_altitude = -1.0; + layer->thickness = 2.0; + + renderer->clouds->getLayerDensity = _getDensity; +} + +Color cloudsPreviewMaterialGetPixel(Renderer* renderer, double x, double y) { Vector3 start, end; - double thickness = 0.5; + double thickness = 2.0; start.x = x * thickness * 0.5; - start.y = -y * thickness * 0.5; - start.z = thickness * 0.5; + start.z = y * thickness * 0.5; + start.y = thickness * 0.5; end.x = start.x; - end.y = start.y; - end.z = -start.z; + end.z = start.z; + end.y = -start.y; return renderer->clouds->getColor(renderer, COLOR_BLUE, start, end); } diff --git a/src/rendering/clouds/clo_preview.h b/src/rendering/clouds/clo_preview.h new file mode 100644 index 0000000..c1bfe35 --- /dev/null +++ b/src/rendering/clouds/clo_preview.h @@ -0,0 +1,28 @@ +#ifndef _PAYSAGES_CLOUDS_PREVIEW_H_ +#define _PAYSAGES_CLOUDS_PREVIEW_H_ + +#include "public.h" +#include "../tools/euclid.h" + +/** + * Cloud preview helpers. + */ + +#ifdef __cplusplus +extern "C" +{ +#endif + +Renderer* cloudsPreviewCoverageCreateRenderer(); +void cloudsPreviewCoverageBindLayer(Renderer* renderer, CloudsLayerDefinition* layer); +Color cloudsPreviewCoverageGetPixel(Renderer* renderer, double x, double y, double scaling, int perspective); + +Renderer* cloudsPreviewMaterialCreateRenderer(); +void cloudsPreviewMaterialBindLayer(Renderer* renderer, CloudsLayerDefinition* layer); +Color cloudsPreviewMaterialGetPixel(Renderer* renderer, double x, double y); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/rendering/clouds/clo_rendering.c b/src/rendering/clouds/clo_rendering.c index e5e8ada..1d6abd4 100644 --- a/src/rendering/clouds/clo_rendering.c +++ b/src/rendering/clouds/clo_rendering.c @@ -1,9 +1,11 @@ #include "private.h" +#include #include #include "../tools.h" #include "../renderer.h" #include "clo_density.h" +#include "clo_walking.h" /******************** Fake ********************/ static int _fakeAlterLight(Renderer* renderer, LightDefinition* light, Vector3 location) @@ -25,24 +27,117 @@ static Color _fakeGetColor(Renderer* renderer, Color base, Vector3 start, Vector } /******************** Real ********************/ -/*static int _cmpLayer(const void* layer1, const void* layer2) +typedef struct { - return (((CloudsLayerDefinition*)layer1)->lower_altitude > ((CloudsLayerDefinition*)layer2)->lower_altitude) ? -1 : 1; -}*/ + 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) { 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; + + 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 +{ + 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) +{ + 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) @@ -59,7 +154,39 @@ static Color _getColor(Renderer* renderer, Color base, Vector3 start, Vector3 en /* TODO Iter layers in sorted order */ for (i = 0; i < n; i++) { - base = cloudsApplyLayer(layersGetLayer(definition->layers, i), base, renderer, start, end); + CloudsLayerDefinition* layer = (CloudsLayerDefinition*)layersGetLayer(renderer->clouds->definition->layers, i); + Vector3 ostart, oend; + + ostart = start; + oend = end; + if (!cloudsOptimizeWalkingBounds(layer, &ostart, &oend)) + { + continue; + } + else + { + CloudsWalker* walker; + AccumulatedMaterialData data; + data.out_scattering = 0.0; + data.in_scattering = COLOR_BLACK; + + walker = cloudsCreateWalker(renderer, layer, ostart, oend); + cloudsWalkerSetStepSize(walker, -1.0); + cloudsStartWalking(walker, _walkerMaterialCallback, &data); + cloudsDeleteWalker(walker); + + /* 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->atmosphere->applyAerialPerspective(renderer, ostart, base).final; + } } return base; diff --git a/src/rendering/clouds/clo_tools.c b/src/rendering/clouds/clo_tools.c deleted file mode 100644 index 5337e54..0000000 --- a/src/rendering/clouds/clo_tools.c +++ /dev/null @@ -1,149 +0,0 @@ -#include "private.h" - -/* - * Clouds tools. - */ - -#include "clo_walking.h" -#include "../renderer.h" -#include "../tools.h" - -static inline Vector3 _getNormal(CloudsLayerDefinition* layer, Vector3 position, double detail) -{ - Vector3 result = {0.0, 0.0, 0.0}; - /*Vector3 dposition; - double val, dval; - - val = _getDistanceToBorder(layer, position); - - dposition.x = position.x + detail; - dposition.y = position.y; - dposition.z = position.z; - dval = val - _getDistanceToBorder(layer, dposition); - result.x += dval; - - dposition.x = position.x - detail; - dval = val - _getDistanceToBorder(layer, dposition); - result.x -= dval; - - dposition.x = position.x; - dposition.y = position.y + detail; - dval = val - _getDistanceToBorder(layer, dposition); - result.y += dval; - - dposition.y = position.y - detail; - dval = val - _getDistanceToBorder(layer, dposition); - result.y -= dval; - - dposition.y = position.y; - dposition.z = position.z + detail; - dval = val - _getDistanceToBorder(layer, dposition); - result.z += dval; - - dposition.z = position.z - detail; - dval = val - _getDistanceToBorder(layer, dposition); - result.z -= dval;*/ - - return v3Normalize(result); -} - -static Color _applyLayerLighting(CloudsLayerDefinition* definition, Renderer* renderer, Vector3 location, double detail) -{ - Vector3 normal; - Color col1, col2; - LightStatus* lighting; - - normal = _getNormal(definition, location, 3.0); - if (renderer->render_quality > 3) - { - normal = v3Add(normal, _getNormal(definition, location, 2.0)); - normal = v3Add(normal, _getNormal(definition, location, 1.0)); - } - if (renderer->render_quality > 5) - { - normal = v3Add(normal, _getNormal(definition, location, 0.5)); - } - if (renderer->render_quality > 8) - { - normal = v3Add(normal, _getNormal(definition, location, 0.75)); - normal = v3Add(normal, _getNormal(definition, location, 1.25)); - normal = v3Add(normal, _getNormal(definition, location, 2.5)); - } - normal = v3Scale(v3Normalize(normal), definition->hardness); - - return renderer->applyLightingToSurface(renderer, location, normal, &definition->material); - - lighting = lightingCreateStatus(renderer->lighting, location, renderer->getCameraLocation(renderer, location)); - renderer->atmosphere->getLightingStatus(renderer, lighting, normal, 0); - col1 = lightingApplyStatus(lighting, normal, &definition->material); - col2 = lightingApplyStatus(lighting, v3Scale(normal, -1.0), &definition->material); - lightingDeleteStatus(lighting); - - col1.r = (col1.r + col2.r) / 2.0; - col1.g = (col1.g + col2.g) / 2.0; - col1.b = (col1.b + col2.b) / 2.0; - col1.a = (col1.a + col2.a) / 2.0; - - return col1; -} - -Color cloudsApplyLayer(CloudsLayerDefinition* definition, Color base, Renderer* renderer, Vector3 start, Vector3 end) -{ - int segment_count; - Color col; - CloudPrimarySegment segments[MAX_SEGMENT_COUNT]; - - 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 = 1.0; - } - else - { - col.a = inside_length / definition->transparencydepth; - }*/ - - col = renderer->atmosphere->applyAerialPerspective(renderer, start, col).final; - col.a = 0.0; - - colorMask(&base, &col); - - return base; -} - -Color cloudsLayerFilterLight(CloudsLayerDefinition* definition, Renderer* renderer, Color light, Vector3 location, Vector3 light_location, Vector3 direction_to_light) -{ - /*double inside_depth, total_depth, factor; - CloudSegment segments[MAX_SEGMENT_COUNT]; - - if (!cloudsOptimizeWalkingBounds(definition, &location, &light_location)) - { - return light; - } - - _getPrimarySegments(definition, renderer, location, direction_to_light, MAX_SEGMENT_COUNT, definition->lighttraversal, v3Norm(v3Sub(light_location, location)), &inside_depth, &total_depth, segments); - - if (definition->lighttraversal < 0.0001) - { - factor = 0.0; - } - else - { - factor = inside_depth / definition->lighttraversal; - if (factor > 1.0) - { - factor = 1.0; - } - } - - factor = 1.0 - (1.0 - definition->minimumlight) * factor; - - light.r = light.r * factor; - light.g = light.g * factor; - light.b = light.b * factor;*/ - - return light; -} diff --git a/src/rendering/clouds/clo_walking.c b/src/rendering/clouds/clo_walking.c index 5efb929..5a84b4c 100644 --- a/src/rendering/clouds/clo_walking.c +++ b/src/rendering/clouds/clo_walking.c @@ -2,6 +2,47 @@ #include "../renderer.h" +/** + * Control of the next walking order. + */ +typedef enum +{ + CLOUD_WALKING_CONTINUE, + CLOUD_WALKING_STOP, + CLOUD_WALKING_REFINE, + CLOUD_WALKING_SUBDIVIDE +} CloudWalkingOrder; + +/** + * Additional info for walking orders. + */ +typedef struct +{ + CloudWalkingOrder order; + double precision; + int max_segments; +} CloudWalkingNextAction; + +/* + * Private structure for the walker. + */ +struct CloudsWalker +{ + Vector3 start; + Vector3 end; + Vector3 diff; + + double cursor; + double max_length; + double step_size; + + int started; + CloudWalkerStepInfo last_segment; + + CloudWalkingNextAction next_action; +}; + + int cloudsOptimizeWalkingBounds(CloudsLayerDefinition* layer, Vector3* start, Vector3* end) { Vector3 diff; @@ -51,107 +92,176 @@ int cloudsOptimizeWalkingBounds(CloudsLayerDefinition* layer, Vector3* start, Ve } } - /* TODO Limit the search length */ return 1; } -int cloudsGetLayerPrimarySegments(Renderer* renderer, CloudsLayerDefinition* layer, Vector3 start, Vector3 end, int max_segments, CloudPrimarySegment* out_segments) +CloudsWalker* cloudsCreateWalker(Renderer* renderer, CloudsLayerDefinition* layer, Vector3 start, Vector3 end) { - int inside, segment_count; - double step_length, segment_length; - Vector3 diff, walker, segment_start; - double render_precision, density; - double diff_length, progress; + CloudsWalker* result; - if (max_segments <= 0) - { - return 0; - } + result = (CloudsWalker*)malloc(sizeof (CloudsWalker)); - if (!cloudsOptimizeWalkingBounds(layer, &start, &end)) - { - return 0; - } + result->start = start; + result->end = end; + result->diff = v3Sub(end, start); + result->max_length = v3Norm(result->diff); + result->cursor = 0.0; + result->step_size = 1.0; - diff = v3Sub(end, start); - diff_length = v3Norm(diff); + result->started = 0; + result->last_segment.renderer = renderer; + result->last_segment.layer = layer; - if (diff_length < 0.000001) - { - return 0; - } + result->next_action.order = CLOUD_WALKING_CONTINUE; - 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; + return result; +} + +void cloudsDeleteWalker(CloudsWalker* walker) +{ + free(walker); +} + +void cloudsWalkerSetStepSize(CloudsWalker* walker, double 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) +{ + out_point->distance_from_start = cursor; + out_point->location = v3Add(walker->start, v3Scale(walker->diff, out_point->distance_from_start / walker->max_length)); + + Renderer* renderer = walker->last_segment.renderer; + CloudsLayerDefinition* layer = walker->last_segment.layer; + out_point->global_density = renderer->clouds->getLayerDensity(renderer, layer, out_point->location); +} + +static void _refineSegment(CloudsWalker* walker, double start_cursor, double start_density, double end_cursor, double end_density, double precision, CloudWalkerPoint* result) +{ + CloudWalkerPoint middle; + + _getPoint(walker, (start_cursor + end_cursor) / 2.0, &middle); + + if (start_density == 0.0) + { + /* Looking for entry */ + 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 + { + _refineSegment(walker, start_cursor, start_density, middle.distance_from_start, middle.global_density, precision, result); + } + } + else + { + /* Looking for exit */ + 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 + { + _refineSegment(walker, middle.distance_from_start, middle.global_density, end_cursor, end_density, precision, result); + } + } +} + +int cloudsWalkerPerformStep(CloudsWalker* walker) +{ + if (!walker->started) + { + _getPoint(walker, 0.0, &walker->last_segment.end); + walker->started = 1; + } + + if (walker->next_action.order == CLOUD_WALKING_STOP || walker->cursor >= walker->max_length) + { + walker->next_action.order = CLOUD_WALKING_STOP; + return 0; + } + else if (walker->next_action.order == CLOUD_WALKING_CONTINUE) + { + /* TODO Limit to end */ + walker->last_segment.start = walker->last_segment.end; + + walker->cursor += walker->step_size; + + _getPoint(walker, walker->cursor, &walker->last_segment.end); + walker->last_segment.length = walker->step_size; + walker->last_segment.refined = 0; + + return 1; + } + else if (walker->next_action.order == CLOUD_WALKING_REFINE) + { + /* Refine segment with dichotomy */ + _refineSegment(walker, + walker->last_segment.start.distance_from_start, + walker->last_segment.start.global_density, + walker->last_segment.end.distance_from_start, + walker->last_segment.end.global_density, + walker->next_action.precision, + (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 + { + /* TODO */ + return 0; + } +} + +void cloudsWalkerOrderStop(CloudsWalker* walker) +{ + walker->next_action.order = CLOUD_WALKING_STOP; +} + +void cloudsWalkerOrderRefine(CloudsWalker* walker, double precision) +{ + walker->next_action.order = CLOUD_WALKING_REFINE; + walker->next_action.precision = precision; +} + +void cloudsWalkerOrderSubdivide(CloudsWalker* walker, double max_segments) +{ + walker->next_action.order = CLOUD_WALKING_SUBDIVIDE; + walker->next_action.max_segments = max_segments; +} + +CloudWalkerStepInfo* cloudsWalkerGetLastSegment(CloudsWalker* walker) +{ + return &walker->last_segment; +} + +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 2ad19b3..1bd31fc 100644 --- a/src/rendering/clouds/clo_walking.h +++ b/src/rendering/clouds/clo_walking.h @@ -15,10 +15,33 @@ extern "C" typedef struct { - Vector3 enter; - Vector3 exit; + double distance_from_start; + Vector3 location; + double global_density; +} CloudWalkerPoint; + +/** + * Information on a segment yielded by walking. + */ +typedef struct +{ + Renderer* renderer; + CloudsLayerDefinition* layer; + + CloudWalkerPoint start; + CloudWalkerPoint end; double length; -} CloudPrimarySegment; + + int refined; + /*int subdivision_level; + double precision_asked;*/ + + void* data; +} CloudWalkerStepInfo; + +typedef struct CloudsWalker CloudsWalker; + +typedef void (*FuncCloudsWalkingCallback)(CloudsWalker* walker); /** * Optimize the search limits in a layer. @@ -31,17 +54,81 @@ typedef struct 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). + * Create a cloud walker. * - * @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 + * For better performance, the segment should by optimized using cloudsOptimizeWalkingBounds. + * @param renderer Renderer context + * @param layer The cloud layer to traverse + * @param start Start of the walk + * @param end End of the walk */ -int cloudsGetLayerPrimarySegments(Renderer* renderer, CloudsLayerDefinition* layer, Vector3 start, Vector3 end, int max_segments, CloudPrimarySegment* out_segments); +CloudsWalker* cloudsCreateWalker(Renderer* renderer, CloudsLayerDefinition* layer, Vector3 start, Vector3 end); + +/** + * Delete a cloud walker. + * + * @param walker The walker to free + */ +void cloudsDeleteWalker(CloudsWalker* walker); + +/** + * Define the segment size for next steps. + * + * @param walker The walker to configure + * @param step The step length, negative for automatic + */ +void cloudsWalkerSetStepSize(CloudsWalker* walker, double step); + +/** + * Perform a single step. + * + * @param walker The walker to use + * @return 1 to continue the loop, 0 to stop + */ +int cloudsWalkerPerformStep(CloudsWalker* walker); + +/** + * Order the walker to stop. + * + * @param walker The walker to use + */ +void cloudsWalkerOrderStop(CloudsWalker* walker); + +/** + * Order the walker to refine the search for cloud entry or exit. + * + * The refinement will next yield a shorter version of the segment, containing only the cloud-inside portion, with a + * tolerance fixed by precision. For an entry point, this will discard the part before cloud entry. For en exit point, + * the portion after this point will be part of the next step, as normal walking resumes. + * @param walker The walker to use + * @param precision Precision wanted for the refinement + */ +void cloudsWalkerOrderRefine(CloudsWalker* walker, double precision); + +/** + * Order the walker to subdivide the previous segment in smaller segments. + * + * @param walker The walker to use + * @param max_segments Maximal number of segments + */ +void cloudsWalkerOrderSubdivide(CloudsWalker* walker, double max_segments); + +/** + * Get the last segment information. + * + * @param walker The walker to use + */ +CloudWalkerStepInfo* cloudsWalkerGetLastSegment(CloudsWalker* walker); + +/** + * Start walking automatically through a segment. + * + * The callback will be called with each segment found, giving info and asking for desired alteration on walking. + * @param walker The walker to use + * @param callback Callback to be called with each found segment + * @param data User data that will be passed back in the callback + */ +void cloudsStartWalking(CloudsWalker* walker, FuncCloudsWalkingCallback callback, void* data); #ifdef __cplusplus } diff --git a/src/rendering/clouds/public.h b/src/rendering/clouds/public.h index beded50..283ac27 100644 --- a/src/rendering/clouds/public.h +++ b/src/rendering/clouds/public.h @@ -80,12 +80,6 @@ LayerType cloudsGetLayerType(); void cloudsAutoPreset(CloudsDefinition* definition, CloudsPreset preset); void cloudsLayerAutoPreset(CloudsLayerDefinition* definition, CloudsLayerPreset preset); -Renderer* cloudsCreatePreviewCoverageRenderer(); -Color cloudsGetPreviewCoverage(Renderer* renderer, double x, double y, double scaling, int perspective); - -Renderer* cloudsCreatePreviewColorRenderer(); -Color cloudsGetPreviewColor(Renderer* renderer, double x, double y); - #ifdef __cplusplus } #endif 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 7ab715e..4d255c3 100644 --- a/src/testing/test_clouds.c +++ b/src/testing/test_clouds.c @@ -200,11 +200,9 @@ static double _getLayerDensitySinX(Renderer* renderer, CloudsLayerDefinition* la return (density > 0.0) ? density : 0.0; } -START_TEST(test_clouds_primary_segments) +START_TEST(test_clouds_walking) { - int segment_count, i; - CloudPrimarySegment segments[10]; - + /* Init */ CloudsLayerDefinition* layer; layer = cloudsGetLayerType().callback_create(); layer->lower_altitude = -1.0; @@ -217,36 +215,88 @@ START_TEST(test_clouds_primary_segments) 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); + CloudsWalker* walker = cloudsCreateWalker(renderer, layer, v3(-0.4, 0.0, 0.0), v3(10.0, 0.0, 0.0)); + CloudWalkerStepInfo* segment; + int result; + + /* First step */ + 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); + ck_assert_double_eq(segment->start.global_density, 0.0); + ck_assert_double_eq(segment->end.distance_from_start, 0.3); + ck_assert_vector_values(segment->end.location, -0.1, 0.0, 0.0); + ck_assert_double_eq(segment->end.global_density, 0.0); + + /* Second step */ + 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); + ck_assert_double_eq(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); + + /* Order to refine second step around the entry point */ + cloudsWalkerOrderRefine(walker, 0.01); + 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.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); 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) +TEST_CASE(clouds, + test_clouds_density, + test_clouds_walking_boundaries, + test_clouds_walking)