paysages3d/lib_paysages/clouds/tools.c
2013-02-03 21:18:32 +00:00

418 lines
13 KiB
C

#include "private.h"
/*
* Clouds tools.
*/
#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);
}
/**
* 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;
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;
double max_length, total_length, inside_length;
Vector3 direction;
Color col;
CloudSegment 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);
/* 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);
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 (!_optimizeSearchLimits(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;
}
/*
* 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;
}