diff --git a/src/rendering/clouds/clo_walking.c b/src/rendering/clouds/clo_walking.c index 5a84b4c..c2ba795 100644 --- a/src/rendering/clouds/clo_walking.c +++ b/src/rendering/clouds/clo_walking.c @@ -35,10 +35,15 @@ struct CloudsWalker double cursor; double max_length; double step_size; + int skip_void; int started; CloudWalkerStepInfo last_segment; + int subdivision_current; + int subdivision_count; + CloudWalkerStepInfo subdivision_parent; + CloudWalkingNextAction next_action; }; @@ -107,8 +112,10 @@ CloudsWalker* cloudsCreateWalker(Renderer* renderer, CloudsLayerDefinition* laye result->max_length = v3Norm(result->diff); result->cursor = 0.0; result->step_size = 1.0; + result->skip_void = 0; result->started = 0; + result->subdivision_count = 0; result->last_segment.renderer = renderer; result->last_segment.layer = layer; @@ -135,6 +142,11 @@ void cloudsWalkerSetStepSize(CloudsWalker* walker, double step) } } +void cloudsWalkerSetVoidSkipping(CloudsWalker* walker, int enabled) +{ + walker->skip_void = enabled; +} + static void _getPoint(CloudsWalker* walker, double cursor, CloudWalkerPoint* out_point) { out_point->distance_from_start = cursor; @@ -187,52 +199,114 @@ static void _refineSegment(CloudsWalker* walker, double start_cursor, double sta int cloudsWalkerPerformStep(CloudsWalker* walker) { + int result = -1; + 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) + while (result < 0) { - walker->next_action.order = CLOUD_WALKING_STOP; - return 0; + if (walker->next_action.order == CLOUD_WALKING_STOP || walker->cursor >= walker->max_length) + { + walker->next_action.order = CLOUD_WALKING_STOP; + result = 0; + } + else if (walker->subdivision_count > 0) + { + if (walker->subdivision_current >= walker->subdivision_count) + { + /* Exit subdivision */ + walker->subdivision_count = 0; + walker->last_segment = walker->subdivision_parent; + walker->next_action.order = CLOUD_WALKING_CONTINUE; + walker->cursor = walker->subdivision_parent.end.distance_from_start; + + /* Recursive call to progress */ + result = cloudsWalkerPerformStep(walker); + } + else + { + /* Continue subdivision */ + walker->last_segment.start = walker->last_segment.end; + + walker->cursor += walker->subdivision_parent.length / (double)walker->subdivision_count; + + _getPoint(walker, walker->cursor, &walker->last_segment.end); + walker->last_segment.length = walker->subdivision_parent.length / (double)walker->subdivision_count; + walker->last_segment.refined = 0; + walker->last_segment.subdivided = walker->subdivision_count; + + walker->subdivision_current++; + + result = 1; + } + } + else if (walker->next_action.order == CLOUD_WALKING_CONTINUE) + { + /* TODO Limit to lookup 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; + walker->last_segment.subdivided = 0; + + result = 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->last_segment.subdivided = 0; + + walker->next_action.order = CLOUD_WALKING_CONTINUE; + + result = 1; + } + else if (walker->next_action.order == CLOUD_WALKING_SUBDIVIDE) + { + /* Starting subdivision */ + walker->subdivision_count = walker->next_action.max_segments; + walker->subdivision_current = 0; + walker->subdivision_parent = walker->last_segment; + walker->cursor = walker->subdivision_parent.start.distance_from_start; + + /* Copy parent segment start, to be used as first subdivided segment start */ + walker->last_segment.end = walker->subdivision_parent.start; + + /* Recursive call to get first subdivided segment */ + cloudsWalkerPerformStep(walker); + + result = 1; + } + else + { + /* Unknown order... */ + result = 0; + } + + /* Check if we need to loop */ + if (result > 0 && walker->skip_void && walker->last_segment.start.global_density == 0.0 && walker->last_segment.end.global_density == 0.0) + { + /* Last segment is considered void, and skipping is enabled */ + result = -1; + } } - 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; - } + return result; } void cloudsWalkerOrderStop(CloudsWalker* walker) @@ -242,14 +316,20 @@ void cloudsWalkerOrderStop(CloudsWalker* walker) void cloudsWalkerOrderRefine(CloudsWalker* walker, double precision) { - walker->next_action.order = CLOUD_WALKING_REFINE; - walker->next_action.precision = precision; + if (walker->subdivision_count == 0) + { + 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; + if (walker->subdivision_count == 0) + { + walker->next_action.order = CLOUD_WALKING_SUBDIVIDE; + walker->next_action.max_segments = max_segments; + } } CloudWalkerStepInfo* cloudsWalkerGetLastSegment(CloudsWalker* walker) diff --git a/src/rendering/clouds/clo_walking.h b/src/rendering/clouds/clo_walking.h index 1bd31fc..a3c56e7 100644 --- a/src/rendering/clouds/clo_walking.h +++ b/src/rendering/clouds/clo_walking.h @@ -33,8 +33,7 @@ typedef struct double length; int refined; - /*int subdivision_level; - double precision_asked;*/ + int subdivided; void* data; } CloudWalkerStepInfo; @@ -79,6 +78,14 @@ void cloudsDeleteWalker(CloudsWalker* walker); */ void cloudsWalkerSetStepSize(CloudsWalker* walker, double step); +/** + * Set the void skipping mode. + * + * @param walker The walker to configure + * @param enabled 1 to enable the void skipping, 0 to disable + */ +void cloudsWalkerSetVoidSkipping(CloudsWalker* walker, int enabled); + /** * Perform a single step. * @@ -108,6 +115,8 @@ void cloudsWalkerOrderRefine(CloudsWalker* walker, double precision); /** * Order the walker to subdivide the previous segment in smaller segments. * + * Next steps will yield subdivided segments. Once subdivided segments have been processed, normal walking + * will resume automatically. * @param walker The walker to use * @param max_segments Maximal number of segments */ diff --git a/src/testing/test_clouds.c b/src/testing/test_clouds.c index 4d255c3..5e276a2 100644 --- a/src/testing/test_clouds.c +++ b/src/testing/test_clouds.c @@ -215,7 +215,7 @@ START_TEST(test_clouds_walking) renderer->render_quality = 8; renderer->clouds->getLayerDensity = _getLayerDensitySinX; - CloudsWalker* walker = cloudsCreateWalker(renderer, layer, v3(-0.4, 0.0, 0.0), v3(10.0, 0.0, 0.0)); + CloudsWalker* walker = cloudsCreateWalker(renderer, layer, v3(-0.4, 0.0, 0.0), v3(1.75, 0.0, 0.0)); CloudWalkerStepInfo* segment; int result; @@ -225,6 +225,7 @@ START_TEST(test_clouds_walking) segment = cloudsWalkerGetLastSegment(walker); ck_assert_int_eq(result, 1); ck_assert_false(segment->refined); + ck_assert_false(segment->subdivided); 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); @@ -238,6 +239,7 @@ START_TEST(test_clouds_walking) segment = cloudsWalkerGetLastSegment(walker); ck_assert_int_eq(result, 1); ck_assert_false(segment->refined); + ck_assert_false(segment->subdivided); 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); @@ -252,6 +254,7 @@ START_TEST(test_clouds_walking) segment = cloudsWalkerGetLastSegment(walker); ck_assert_int_eq(result, 1); ck_assert_true(segment->refined); + ck_assert_false(segment->subdivided); 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); @@ -266,6 +269,7 @@ START_TEST(test_clouds_walking) segment = cloudsWalkerGetLastSegment(walker); ck_assert_int_eq(result, 1); ck_assert_false(segment->refined); + ck_assert_false(segment->subdivided); 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); @@ -280,13 +284,127 @@ START_TEST(test_clouds_walking) segment = cloudsWalkerGetLastSegment(walker); ck_assert_int_eq(result, 1); ck_assert_true(segment->refined); + ck_assert_false(segment->subdivided); 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); + ck_assert_double_eq(segment->end.global_density, 0.0); + + /* Find next entry point by skipping blank */ + cloudsWalkerSetVoidSkipping(walker, 1); + cloudsWalkerSetStepSize(walker, 0.2); + result = cloudsWalkerPerformStep(walker); + segment = cloudsWalkerGetLastSegment(walker); + ck_assert_int_eq(result, 1); + ck_assert_false(segment->refined); + ck_assert_false(segment->subdivided); + ck_assert_double_eq(segment->length, 0.2); + ck_assert_double_in_range(segment->start.distance_from_start, 1.2, 1.4); + ck_assert_double_in_range(segment->start.location.x, 0.8, 1.0); + ck_assert_double_eq(segment->start.global_density, 0.0); + ck_assert_double_in_range(segment->end.distance_from_start, 1.4, 1.6); + ck_assert_double_in_range(segment->end.location.x, 1.0, 1.2); + ck_assert_double_gt(segment->end.global_density, 0.0); + + /* Refine 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_false(segment->subdivided); + ck_assert_double_in_range(segment->length, 0.0, 0.2); + ck_assert_double_in_range(segment->start.distance_from_start, 1.4, 1.41); + ck_assert_double_in_range(segment->start.location.x, 1.0, 1.01); + ck_assert_double_gt(segment->start.global_density, 0.0); + ck_assert_double_in_range(segment->end.distance_from_start, 1.41, 1.6); + ck_assert_double_in_range(segment->end.location.x, 1.01, 1.2); + ck_assert_double_gt(segment->end.global_density, 0.0); + + /* Subdivide entry for more detail */ + CloudWalkerStepInfo parent = *segment; + cloudsWalkerOrderSubdivide(walker, 3); + result = cloudsWalkerPerformStep(walker); + segment = cloudsWalkerGetLastSegment(walker); + ck_assert_int_eq(result, 1); + ck_assert_false(segment->refined); + ck_assert_true(segment->subdivided); + ck_assert_double_eq(segment->length, parent.length / 3.0); + ck_assert_double_eq(segment->start.distance_from_start, parent.start.distance_from_start); + ck_assert_double_eq(segment->start.location.x, parent.start.location.x); + ck_assert_double_eq(segment->end.distance_from_start, parent.start.distance_from_start + segment->length); + ck_assert_double_eq(segment->end.location.x, parent.start.location.x + segment->length); + result = cloudsWalkerPerformStep(walker); + segment = cloudsWalkerGetLastSegment(walker); + ck_assert_int_eq(result, 1); + ck_assert_false(segment->refined); + ck_assert_true(segment->subdivided); + ck_assert_double_eq(segment->length, parent.length / 3.0); + ck_assert_double_eq(segment->start.distance_from_start, parent.start.distance_from_start + segment->length); + ck_assert_double_eq(segment->start.location.x, parent.start.location.x + segment->length); + ck_assert_double_eq(segment->end.distance_from_start, parent.start.distance_from_start + 2.0 * segment->length); + ck_assert_double_eq(segment->end.location.x, parent.start.location.x + 2.0 * segment->length); + result = cloudsWalkerPerformStep(walker); + segment = cloudsWalkerGetLastSegment(walker); + ck_assert_int_eq(result, 1); + ck_assert_false(segment->refined); + ck_assert_true(segment->subdivided); + ck_assert_double_eq(segment->length, parent.length / 3.0); + ck_assert_double_eq(segment->start.distance_from_start, parent.start.distance_from_start + 2.0 * segment->length); + ck_assert_double_eq(segment->start.location.x, parent.start.location.x + 2.0 * segment->length); + ck_assert_double_eq(segment->end.distance_from_start, parent.end.distance_from_start); + ck_assert_double_eq(segment->end.location.x, parent.end.location.x); + + /* After subdividing, normal walking resumes */ + result = cloudsWalkerPerformStep(walker); + segment = cloudsWalkerGetLastSegment(walker); + ck_assert_int_eq(result, 1); + ck_assert_false(segment->refined); + ck_assert_false(segment->subdivided); + ck_assert_double_eq(segment->length, 0.2); + ck_assert_double_in_range(segment->start.distance_from_start, 1.41, 1.6); + ck_assert_double_in_range(segment->start.location.x, 1.01, 1.2); + ck_assert_double_gt(segment->start.global_density, 0.0); + ck_assert_double_in_range(segment->end.distance_from_start, 1.61, 1.8); + ck_assert_double_in_range(segment->end.location.x, 1.21, 1.4); + ck_assert_double_gt(segment->end.global_density, 0.0); + + /* Exiting cloud again */ + cloudsWalkerSetStepSize(walker, 0.3); + result = cloudsWalkerPerformStep(walker); + segment = cloudsWalkerGetLastSegment(walker); + ck_assert_int_eq(result, 1); + ck_assert_false(segment->refined); + ck_assert_false(segment->subdivided); + ck_assert_double_eq(segment->length, 0.3); + ck_assert_double_in_range(segment->start.distance_from_start, 1.61, 1.8); + ck_assert_double_in_range(segment->start.location.x, 1.21, 1.4); + ck_assert_double_gt(segment->start.global_density, 0.0); + ck_assert_double_in_range(segment->end.distance_from_start, 1.91, 2.1); + ck_assert_double_in_range(segment->end.location.x, 1.5, 1.7); + ck_assert_double_eq(segment->end.global_density, 0.0); + + /* A step in the void without skipping */ + cloudsWalkerSetVoidSkipping(walker, 0); + result = cloudsWalkerPerformStep(walker); + segment = cloudsWalkerGetLastSegment(walker); + ck_assert_int_eq(result, 1); + ck_assert_false(segment->refined); + ck_assert_false(segment->subdivided); + ck_assert_double_eq(segment->length, 0.3); + ck_assert_double_in_range(segment->start.distance_from_start, 1.91, 2.1); + ck_assert_double_in_range(segment->start.location.x, 1.5, 1.7); + ck_assert_double_eq(segment->start.global_density, 0.0); + ck_assert_double_in_range(segment->end.distance_from_start, 2.21, 2.4); + ck_assert_double_in_range(segment->end.location.x, 1.8, 2.0); + ck_assert_double_eq(segment->end.global_density, 0.0); + + /* Walker reached the lookup segment's end, it should stop */ + result = cloudsWalkerPerformStep(walker); + ck_assert_int_eq(result, 0); /* Clean up */ cloudsDeleteWalker(walker);