2012-01-22 18:39:42 +00:00
|
|
|
#include "clouds.h"
|
|
|
|
|
2012-01-18 18:47:46 +00:00
|
|
|
#include <string.h>
|
2012-02-08 21:20:22 +00:00
|
|
|
#include <stdlib.h>
|
2011-12-10 13:25:22 +00:00
|
|
|
#include <math.h>
|
2013-01-19 22:42:50 +00:00
|
|
|
#include "tools/color.h"
|
|
|
|
#include "tools/euclid.h"
|
2012-01-29 17:39:56 +00:00
|
|
|
#include "tools.h"
|
2011-12-10 13:25:22 +00:00
|
|
|
|
2012-08-26 13:36:46 +00:00
|
|
|
#define CLOUDS_MAX_LAYERS 6
|
2012-07-01 13:27:57 +00:00
|
|
|
#define MAX_SEGMENT_COUNT 30
|
|
|
|
|
2011-12-10 13:25:22 +00:00
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
Vector3 start;
|
|
|
|
Vector3 end;
|
2012-06-17 09:40:40 +00:00
|
|
|
double length;
|
2011-12-10 13:25:22 +00:00
|
|
|
} CloudSegment;
|
|
|
|
|
2012-01-23 23:45:33 +00:00
|
|
|
CloudsDefinition cloudsCreateDefinition()
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-01-23 23:45:33 +00:00
|
|
|
CloudsDefinition result;
|
|
|
|
|
2012-08-26 13:36:46 +00:00
|
|
|
result.layers = layersCreate(cloudsGetLayerType(), CLOUDS_MAX_LAYERS);
|
2012-01-23 23:45:33 +00:00
|
|
|
|
|
|
|
return result;
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
|
2012-01-23 23:45:33 +00:00
|
|
|
void cloudsDeleteDefinition(CloudsDefinition* definition)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-08-26 13:36:46 +00:00
|
|
|
layersDelete(definition->layers);
|
2012-01-23 23:45:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void cloudsCopyDefinition(CloudsDefinition* source, CloudsDefinition* destination)
|
|
|
|
{
|
2012-08-26 13:36:46 +00:00
|
|
|
layersCopy(source->layers, destination->layers);
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
|
2012-01-23 23:45:33 +00:00
|
|
|
void cloudsValidateDefinition(CloudsDefinition* definition)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-08-26 13:36:46 +00:00
|
|
|
layersValidate(definition->layers);
|
|
|
|
}
|
|
|
|
|
|
|
|
void cloudsSave(PackStream* stream, CloudsDefinition* definition)
|
|
|
|
{
|
|
|
|
layersSave(stream, definition->layers);
|
|
|
|
}
|
|
|
|
|
|
|
|
void cloudsLoad(PackStream* stream, CloudsDefinition* definition)
|
|
|
|
{
|
|
|
|
layersLoad(stream, definition->layers);
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
|
2012-06-17 09:40:40 +00:00
|
|
|
static double _standardCoverageFunc(CloudsLayerDefinition* layer, Vector3 position)
|
2012-02-08 14:24:53 +00:00
|
|
|
{
|
2012-07-01 13:27:57 +00:00
|
|
|
if (position.y < layer->lower_altitude || position.y >= layer->lower_altitude + layer->thickness)
|
2012-02-08 14:24:53 +00:00
|
|
|
{
|
|
|
|
return 0.0;
|
|
|
|
}
|
|
|
|
else
|
2012-05-29 13:32:23 +00:00
|
|
|
{
|
2012-12-05 15:35:25 +00:00
|
|
|
return layer->base_coverage * curveGetValue(layer->_coverage_by_altitude, (position.y - layer->lower_altitude) / layer->thickness);
|
2012-02-08 14:24:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-08-26 13:36:46 +00:00
|
|
|
CloudsLayerDefinition* cloudsLayerCreateDefinition()
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-08-26 13:36:46 +00:00
|
|
|
CloudsLayerDefinition* result;
|
2012-12-08 14:12:02 +00:00
|
|
|
|
2012-08-26 13:36:46 +00:00
|
|
|
result = malloc(sizeof(CloudsLayerDefinition));
|
2012-12-05 15:35:25 +00:00
|
|
|
result->_coverage_by_altitude = curveCreate();
|
|
|
|
result->_shape_noise = noiseCreateGenerator();
|
|
|
|
result->_edge_noise = noiseCreateGenerator();
|
2012-11-24 12:30:51 +00:00
|
|
|
|
2012-08-26 13:36:46 +00:00
|
|
|
result->_custom_coverage = _standardCoverageFunc;
|
2012-12-08 14:12:02 +00:00
|
|
|
|
2012-11-24 16:04:33 +00:00
|
|
|
cloudsLayerAutoPreset(result, CLOUDS_PRESET_CIRRUS);
|
2011-12-17 15:54:10 +00:00
|
|
|
|
2011-12-10 13:25:22 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2012-01-23 23:45:33 +00:00
|
|
|
void cloudsLayerDeleteDefinition(CloudsLayerDefinition* definition)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-12-05 15:35:25 +00:00
|
|
|
curveDelete(definition->_coverage_by_altitude);
|
|
|
|
noiseDeleteGenerator(definition->_shape_noise);
|
|
|
|
noiseDeleteGenerator(definition->_edge_noise);
|
2012-08-26 13:36:46 +00:00
|
|
|
free(definition);
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
|
2012-11-24 12:30:51 +00:00
|
|
|
void cloudsLayerAutoPreset(CloudsLayerDefinition* definition, CloudsPreset preset)
|
|
|
|
{
|
|
|
|
definition->material.base.r = 0.7;
|
|
|
|
definition->material.base.g = 0.7;
|
|
|
|
definition->material.base.b = 0.7;
|
|
|
|
definition->material.base.a = 1.0;
|
2012-12-08 14:12:02 +00:00
|
|
|
|
2012-12-05 15:35:25 +00:00
|
|
|
switch (preset)
|
2012-11-24 12:30:51 +00:00
|
|
|
{
|
2012-12-05 15:35:25 +00:00
|
|
|
case CLOUDS_PRESET_CIRRUS:
|
|
|
|
definition->type = CLOUDS_TYPE_CIRRUS;
|
|
|
|
definition->lower_altitude = 25.0;
|
|
|
|
definition->thickness = 2.0;
|
|
|
|
definition->material.reflection = 0.4;
|
|
|
|
definition->material.shininess = 0.5;
|
|
|
|
definition->hardness = 0.0;
|
|
|
|
definition->transparencydepth = 3.0;
|
|
|
|
definition->lighttraversal = 10.0;
|
|
|
|
definition->minimumlight = 0.6;
|
|
|
|
definition->shape_scaling = 8.0;
|
|
|
|
definition->edge_scaling = 2.0;
|
|
|
|
definition->edge_length = 0.8;
|
|
|
|
definition->base_coverage = 0.6;
|
|
|
|
break;
|
|
|
|
case CLOUDS_PRESET_CUMULUS:
|
|
|
|
definition->type = CLOUDS_TYPE_CUMULUS;
|
|
|
|
definition->lower_altitude = 15.0;
|
|
|
|
definition->thickness = 15.0;
|
|
|
|
definition->material.reflection = 0.5;
|
|
|
|
definition->material.shininess = 1.2;
|
|
|
|
definition->hardness = 0.25;
|
|
|
|
definition->transparencydepth = 1.5;
|
|
|
|
definition->lighttraversal = 8.0;
|
|
|
|
definition->minimumlight = 0.4;
|
|
|
|
definition->shape_scaling = 20.0;
|
|
|
|
definition->edge_scaling = 2.0;
|
|
|
|
definition->edge_length = 0.0;
|
|
|
|
definition->base_coverage = 0.7;
|
|
|
|
break;
|
|
|
|
case CLOUDS_PRESET_STRATOCUMULUS:
|
|
|
|
definition->type = CLOUDS_TYPE_STRATOCUMULUS;
|
|
|
|
definition->lower_altitude = 5.0;
|
|
|
|
definition->thickness = 6.0;
|
|
|
|
definition->material.reflection = 0.3;
|
|
|
|
definition->material.shininess = 0.8;
|
|
|
|
definition->hardness = 0.25;
|
|
|
|
definition->transparencydepth = 1.5;
|
|
|
|
definition->lighttraversal = 7.0;
|
|
|
|
definition->minimumlight = 0.4;
|
|
|
|
definition->shape_scaling = 10.0;
|
|
|
|
definition->edge_scaling = 0.8;
|
|
|
|
definition->edge_length = 0.3;
|
|
|
|
definition->base_coverage = 0.4;
|
|
|
|
break;
|
|
|
|
case CLOUDS_PRESET_STRATUS:
|
|
|
|
definition->type = CLOUDS_TYPE_STRATUS;
|
|
|
|
definition->lower_altitude = 3.0;
|
|
|
|
definition->thickness = 4.0;
|
|
|
|
definition->material.reflection = 0.1;
|
|
|
|
definition->material.shininess = 0.8;
|
|
|
|
definition->hardness = 0.1;
|
|
|
|
definition->transparencydepth = 3.0;
|
|
|
|
definition->lighttraversal = 10.0;
|
|
|
|
definition->minimumlight = 0.6;
|
|
|
|
definition->shape_scaling = 8.0;
|
|
|
|
definition->edge_scaling = 2.0;
|
|
|
|
definition->edge_length = 1.0;
|
|
|
|
definition->base_coverage = 0.4;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
2012-11-24 12:30:51 +00:00
|
|
|
}
|
2012-12-08 14:12:02 +00:00
|
|
|
|
2012-11-24 12:30:51 +00:00
|
|
|
cloudsLayerValidateDefinition(definition);
|
|
|
|
}
|
|
|
|
|
2012-01-23 23:45:33 +00:00
|
|
|
void cloudsLayerCopyDefinition(CloudsLayerDefinition* source, CloudsLayerDefinition* destination)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-05-29 13:32:23 +00:00
|
|
|
CloudsLayerDefinition temp;
|
2012-12-08 14:12:02 +00:00
|
|
|
|
2012-05-29 13:32:23 +00:00
|
|
|
temp = *destination;
|
2012-01-23 23:45:33 +00:00
|
|
|
*destination = *source;
|
2012-12-08 14:12:02 +00:00
|
|
|
|
2012-12-05 15:35:25 +00:00
|
|
|
destination->_shape_noise = temp._shape_noise;
|
|
|
|
noiseCopy(source->_shape_noise, destination->_shape_noise);
|
2012-06-10 09:30:30 +00:00
|
|
|
|
2012-12-05 15:35:25 +00:00
|
|
|
destination->_edge_noise = temp._edge_noise;
|
|
|
|
noiseCopy(source->_edge_noise, destination->_edge_noise);
|
2012-06-10 09:30:30 +00:00
|
|
|
|
2012-12-05 15:35:25 +00:00
|
|
|
destination->_coverage_by_altitude = temp._coverage_by_altitude;
|
|
|
|
curveCopy(source->_coverage_by_altitude, destination->_coverage_by_altitude);
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
|
2012-01-23 23:45:33 +00:00
|
|
|
void cloudsLayerValidateDefinition(CloudsLayerDefinition* definition)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-06-10 09:30:30 +00:00
|
|
|
if (definition->shape_scaling < 0.0001)
|
|
|
|
{
|
|
|
|
definition->shape_scaling = 0.00001;
|
|
|
|
}
|
|
|
|
if (definition->edge_scaling < 0.0001)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-06-10 09:30:30 +00:00
|
|
|
definition->edge_scaling = 0.00001;
|
2012-01-23 23:45:33 +00:00
|
|
|
}
|
2012-06-10 09:30:30 +00:00
|
|
|
if (definition->_custom_coverage == NULL)
|
2012-01-23 23:45:33 +00:00
|
|
|
{
|
2012-06-10 09:30:30 +00:00
|
|
|
definition->_custom_coverage = _standardCoverageFunc;
|
2012-02-08 21:20:22 +00:00
|
|
|
}
|
2012-12-05 15:35:25 +00:00
|
|
|
|
|
|
|
curveClear(definition->_coverage_by_altitude);
|
|
|
|
noiseClearLevels(definition->_shape_noise);
|
|
|
|
noiseClearLevels(definition->_edge_noise);
|
2012-12-08 14:12:02 +00:00
|
|
|
|
2012-12-05 15:35:25 +00:00
|
|
|
switch (definition->type)
|
|
|
|
{
|
|
|
|
case CLOUDS_TYPE_CIRRUS:
|
|
|
|
curveQuickAddPoint(definition->_coverage_by_altitude, 0.0, 0.0);
|
|
|
|
curveQuickAddPoint(definition->_coverage_by_altitude, 0.5, 1.0);
|
|
|
|
curveQuickAddPoint(definition->_coverage_by_altitude, 1.0, 0.0);
|
|
|
|
noiseAddLevelsSimple(definition->_shape_noise, 3, 1.0, 1.0);
|
|
|
|
noiseSetFunctionParams(definition->_shape_noise, NOISE_FUNCTION_SIMPLEX, 0.0);
|
|
|
|
noiseAddLevelsSimple(definition->_edge_noise, 4, 1.0, 1.0);
|
|
|
|
noiseSetFunctionParams(definition->_edge_noise, NOISE_FUNCTION_SIMPLEX, -0.2);
|
|
|
|
break;
|
|
|
|
case CLOUDS_TYPE_CUMULUS:
|
|
|
|
curveQuickAddPoint(definition->_coverage_by_altitude, 0.0, 0.0);
|
|
|
|
curveQuickAddPoint(definition->_coverage_by_altitude, 0.1, 1.0);
|
|
|
|
curveQuickAddPoint(definition->_coverage_by_altitude, 0.4, 0.8);
|
|
|
|
curveQuickAddPoint(definition->_coverage_by_altitude, 0.7, 1.0);
|
|
|
|
curveQuickAddPoint(definition->_coverage_by_altitude, 1.0, 0.0);
|
|
|
|
noiseAddLevelsSimple(definition->_shape_noise, 7, 1.0, 1.0);
|
|
|
|
noiseSetFunctionParams(definition->_shape_noise, NOISE_FUNCTION_SIMPLEX, 0.4);
|
|
|
|
break;
|
|
|
|
case CLOUDS_TYPE_STRATOCUMULUS:
|
|
|
|
curveQuickAddPoint(definition->_coverage_by_altitude, 0.0, 0.0);
|
|
|
|
curveQuickAddPoint(definition->_coverage_by_altitude, 0.2, 1.0);
|
|
|
|
curveQuickAddPoint(definition->_coverage_by_altitude, 0.5, 1.0);
|
|
|
|
curveQuickAddPoint(definition->_coverage_by_altitude, 1.0, 0.0);
|
|
|
|
noiseAddLevelsSimple(definition->_shape_noise, 2, 1.0, 1.0);
|
|
|
|
noiseSetFunctionParams(definition->_shape_noise, NOISE_FUNCTION_SIMPLEX, 0.3);
|
|
|
|
noiseAddLevelsSimple(definition->_edge_noise, 8, 1.0, 1.0);
|
|
|
|
noiseSetFunctionParams(definition->_edge_noise, NOISE_FUNCTION_SIMPLEX, 0.5);
|
|
|
|
break;
|
|
|
|
case CLOUDS_TYPE_STRATUS:
|
|
|
|
curveQuickAddPoint(definition->_coverage_by_altitude, 0.0, 0.0);
|
|
|
|
curveQuickAddPoint(definition->_coverage_by_altitude, 0.2, 1.0);
|
|
|
|
curveQuickAddPoint(definition->_coverage_by_altitude, 0.8, 1.0);
|
|
|
|
curveQuickAddPoint(definition->_coverage_by_altitude, 1.0, 0.0);
|
|
|
|
noiseAddLevelsSimple(definition->_shape_noise, 3, 1.0, 1.0);
|
|
|
|
noiseSetFunctionParams(definition->_shape_noise, NOISE_FUNCTION_SIMPLEX, -0.3);
|
|
|
|
noiseAddLevelsSimple(definition->_edge_noise, 4, 1.0, 1.0);
|
|
|
|
noiseSetFunctionParams(definition->_edge_noise, NOISE_FUNCTION_SIMPLEX, -0.5);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
|
2012-08-26 13:36:46 +00:00
|
|
|
void _cloudsLayerSave(PackStream* stream, CloudsLayerDefinition* layer)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-12-05 15:35:25 +00:00
|
|
|
int clouds_type = (int)layer->type;
|
2012-12-08 14:12:02 +00:00
|
|
|
|
2012-12-05 15:35:25 +00:00
|
|
|
packWriteInt(stream, &clouds_type);
|
2012-08-26 13:36:46 +00:00
|
|
|
packWriteDouble(stream, &layer->lower_altitude);
|
|
|
|
packWriteDouble(stream, &layer->thickness);
|
2012-12-05 15:35:25 +00:00
|
|
|
curveSave(stream, layer->_coverage_by_altitude);
|
|
|
|
noiseSaveGenerator(stream, layer->_shape_noise);
|
|
|
|
noiseSaveGenerator(stream, layer->_edge_noise);
|
2012-08-26 13:36:46 +00:00
|
|
|
materialSave(stream, &layer->material);
|
|
|
|
packWriteDouble(stream, &layer->hardness);
|
|
|
|
packWriteDouble(stream, &layer->transparencydepth);
|
|
|
|
packWriteDouble(stream, &layer->lighttraversal);
|
|
|
|
packWriteDouble(stream, &layer->minimumlight);
|
|
|
|
packWriteDouble(stream, &layer->shape_scaling);
|
|
|
|
packWriteDouble(stream, &layer->edge_scaling);
|
|
|
|
packWriteDouble(stream, &layer->edge_length);
|
|
|
|
packWriteDouble(stream, &layer->base_coverage);
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
|
2012-08-26 13:36:46 +00:00
|
|
|
void _cloudsLayerLoad(PackStream* stream, CloudsLayerDefinition* layer)
|
2012-01-23 23:45:33 +00:00
|
|
|
{
|
2012-12-05 15:35:25 +00:00
|
|
|
int clouds_type;
|
2012-12-08 14:12:02 +00:00
|
|
|
|
2012-12-05 15:35:25 +00:00
|
|
|
packReadInt(stream, &clouds_type);
|
|
|
|
layer->type = (CloudsType)clouds_type;
|
2012-08-26 13:36:46 +00:00
|
|
|
packReadDouble(stream, &layer->lower_altitude);
|
|
|
|
packReadDouble(stream, &layer->thickness);
|
|
|
|
materialLoad(stream, &layer->material);
|
|
|
|
packReadDouble(stream, &layer->hardness);
|
|
|
|
packReadDouble(stream, &layer->transparencydepth);
|
|
|
|
packReadDouble(stream, &layer->lighttraversal);
|
|
|
|
packReadDouble(stream, &layer->minimumlight);
|
|
|
|
packReadDouble(stream, &layer->shape_scaling);
|
|
|
|
packReadDouble(stream, &layer->edge_scaling);
|
|
|
|
packReadDouble(stream, &layer->edge_length);
|
|
|
|
packReadDouble(stream, &layer->base_coverage);
|
2012-12-08 14:12:02 +00:00
|
|
|
|
2012-12-05 15:35:25 +00:00
|
|
|
cloudsLayerValidateDefinition(layer);
|
2012-01-23 23:45:33 +00:00
|
|
|
}
|
|
|
|
|
2012-08-26 13:36:46 +00:00
|
|
|
LayerType cloudsGetLayerType()
|
2012-07-05 15:01:58 +00:00
|
|
|
{
|
2012-08-26 13:36:46 +00:00
|
|
|
LayerType result;
|
2012-12-08 14:12:02 +00:00
|
|
|
|
2012-08-26 13:36:46 +00:00
|
|
|
result.callback_create = (LayerCallbackCreate)cloudsLayerCreateDefinition;
|
|
|
|
result.callback_delete = (LayerCallbackDelete)cloudsLayerDeleteDefinition;
|
|
|
|
result.callback_copy = (LayerCallbackCopy)cloudsLayerCopyDefinition;
|
|
|
|
result.callback_validate = (LayerCallbackValidate)cloudsLayerValidateDefinition;
|
|
|
|
result.callback_save = (LayerCallbackSave)_cloudsLayerSave;
|
|
|
|
result.callback_load = (LayerCallbackLoad)_cloudsLayerLoad;
|
2012-12-08 14:12:02 +00:00
|
|
|
|
2012-08-26 13:36:46 +00:00
|
|
|
return result;
|
2012-07-05 15:01:58 +00:00
|
|
|
}
|
|
|
|
|
2012-06-17 09:40:40 +00:00
|
|
|
static inline double _getDistanceToBorder(CloudsLayerDefinition* layer, Vector3 position)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-06-17 09:40:40 +00:00
|
|
|
double density, coverage, val;
|
2011-12-10 13:25:22 +00:00
|
|
|
|
2012-12-05 15:35:25 +00:00
|
|
|
val = noiseGet3DTotal(layer->_shape_noise, position.x / layer->shape_scaling, position.y / layer->shape_scaling, position.z / layer->shape_scaling) / noiseGetMaxValue(layer->_shape_noise);
|
2012-06-10 09:30:30 +00:00
|
|
|
coverage = layer->_custom_coverage(layer, position);
|
|
|
|
density = 0.5 * val - 0.5 + coverage;
|
2012-12-08 14:12:02 +00:00
|
|
|
|
2012-06-10 09:30:30 +00:00
|
|
|
if (density <= 0.0)
|
|
|
|
{
|
|
|
|
/* outside the main shape */
|
|
|
|
return density * layer->shape_scaling;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* inside the main shape, using edge noise */
|
|
|
|
density /= coverage;
|
|
|
|
if (density < layer->edge_length)
|
|
|
|
{
|
2012-06-13 20:38:01 +00:00
|
|
|
density /= layer->edge_length;
|
2012-12-08 14:12:02 +00:00
|
|
|
|
2012-12-05 15:35:25 +00:00
|
|
|
val = 0.5 * noiseGet3DTotal(layer->_edge_noise, position.x / layer->edge_scaling, position.y / layer->edge_scaling, position.z / layer->edge_scaling) / noiseGetMaxValue(layer->_edge_noise);
|
2012-06-13 20:38:01 +00:00
|
|
|
val = val - 0.5 + density;
|
2011-12-10 13:25:22 +00:00
|
|
|
|
2012-06-13 20:38:01 +00:00
|
|
|
return val * (density * coverage * layer->shape_scaling + (1.0 - density) * layer->edge_scaling);
|
2012-06-10 09:30:30 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return density * coverage * layer->shape_scaling;
|
|
|
|
}
|
|
|
|
}
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
|
2012-06-17 09:40:40 +00:00
|
|
|
static inline Vector3 _getNormal(CloudsLayerDefinition* layer, Vector3 position, double detail)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
|
|
|
Vector3 result = {0.0, 0.0, 0.0};
|
2012-02-08 14:24:53 +00:00
|
|
|
Vector3 dposition;
|
2012-06-17 09:40:40 +00:00
|
|
|
double val, dval;
|
2012-12-08 14:12:02 +00:00
|
|
|
|
2012-02-08 14:24:53 +00:00
|
|
|
val = _getDistanceToBorder(layer, position);
|
2011-12-10 13:25:22 +00:00
|
|
|
|
2012-02-08 14:24:53 +00:00
|
|
|
dposition.x = position.x + detail;
|
|
|
|
dposition.y = position.y;
|
|
|
|
dposition.z = position.z;
|
|
|
|
dval = val - _getDistanceToBorder(layer, dposition);
|
2011-12-10 13:25:22 +00:00
|
|
|
result.x += dval;
|
|
|
|
|
2012-02-08 14:24:53 +00:00
|
|
|
dposition.x = position.x - detail;
|
|
|
|
dval = val - _getDistanceToBorder(layer, dposition);
|
2011-12-10 13:25:22 +00:00
|
|
|
result.x -= dval;
|
|
|
|
|
2012-02-08 14:24:53 +00:00
|
|
|
dposition.x = position.x;
|
|
|
|
dposition.y = position.y + detail;
|
|
|
|
dval = val - _getDistanceToBorder(layer, dposition);
|
2011-12-10 13:25:22 +00:00
|
|
|
result.y += dval;
|
|
|
|
|
2012-02-08 14:24:53 +00:00
|
|
|
dposition.y = position.y - detail;
|
|
|
|
dval = val - _getDistanceToBorder(layer, dposition);
|
2011-12-10 13:25:22 +00:00
|
|
|
result.y -= dval;
|
|
|
|
|
2012-02-08 14:24:53 +00:00
|
|
|
dposition.y = position.y;
|
|
|
|
dposition.z = position.z + detail;
|
|
|
|
dval = val - _getDistanceToBorder(layer, dposition);
|
2011-12-10 13:25:22 +00:00
|
|
|
result.z += dval;
|
|
|
|
|
2012-02-08 14:24:53 +00:00
|
|
|
dposition.z = position.z - detail;
|
|
|
|
dval = val - _getDistanceToBorder(layer, dposition);
|
2011-12-10 13:25:22 +00:00
|
|
|
result.z -= dval;
|
|
|
|
|
2012-01-23 23:45:33 +00:00
|
|
|
return v3Normalize(result);
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Optimize the search limits in a layer.
|
2011-12-17 15:54:10 +00:00
|
|
|
*
|
2011-12-10 13:25:22 +00:00
|
|
|
* @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
|
|
|
|
*/
|
2012-01-23 23:45:33 +00:00
|
|
|
static int _optimizeSearchLimits(CloudsLayerDefinition* layer, Vector3* start, Vector3* end)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
|
|
|
Vector3 diff;
|
2012-12-08 14:12:02 +00:00
|
|
|
|
2012-07-01 13:27:57 +00:00
|
|
|
if (start->y > layer->lower_altitude + layer->thickness)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-07-01 13:27:57 +00:00
|
|
|
if (end->y >= layer->lower_altitude + layer->thickness)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
diff = v3Sub(*end, *start);
|
2012-07-01 13:27:57 +00:00
|
|
|
*start = v3Add(*start, v3Scale(diff, (layer->lower_altitude + layer->thickness - start->y) / diff.y));
|
|
|
|
if (end->y < layer->lower_altitude)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-07-01 13:27:57 +00:00
|
|
|
*end = v3Add(*end, v3Scale(diff, (layer->lower_altitude - end->y) / diff.y));
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-07-01 13:27:57 +00:00
|
|
|
else if (start->y < layer->lower_altitude)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-07-01 13:27:57 +00:00
|
|
|
if (end->y <= layer->lower_altitude)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
diff = v3Sub(*end, *start);
|
2012-07-01 13:27:57 +00:00
|
|
|
*start = v3Add(*start, v3Scale(diff, (layer->lower_altitude - start->y) / diff.y));
|
|
|
|
if (end->y >= layer->lower_altitude + layer->thickness)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-07-01 13:27:57 +00:00
|
|
|
*end = v3Add(*end, v3Scale(diff, (layer->lower_altitude + layer->thickness - end->y) / diff.y));
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else /* start is inside layer */
|
|
|
|
{
|
|
|
|
diff = v3Sub(*end, *start);
|
2012-09-16 20:14:08 +00:00
|
|
|
if (end->y > layer->lower_altitude + layer->thickness)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-07-01 13:27:57 +00:00
|
|
|
*end = v3Add(*start, v3Scale(diff, (layer->lower_altitude + layer->thickness - start->y) / diff.y));
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
2012-07-01 13:27:57 +00:00
|
|
|
else if (end->y < layer->lower_altitude)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-07-01 13:27:57 +00:00
|
|
|
*end = v3Add(*start, v3Scale(diff, (layer->lower_altitude - start->y) / diff.y));
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
}
|
2011-12-17 15:54:10 +00:00
|
|
|
|
2011-12-10 13:25:22 +00:00
|
|
|
/* TODO Limit the search length */
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Go through the cloud layer to find segments (parts of the lookup that are inside the cloud).
|
2011-12-17 15:54:10 +00:00
|
|
|
*
|
2011-12-10 13:25:22 +00:00
|
|
|
* @param definition The cloud layer
|
2012-01-23 23:45:33 +00:00
|
|
|
* @param renderer The renderer environment
|
2011-12-10 13:25:22 +00:00
|
|
|
* @param start Start position of the lookup (already optimized)
|
|
|
|
* @param direction Normalized direction of the lookup
|
|
|
|
* @param detail Level of noise detail required
|
|
|
|
* @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
|
|
|
|
*/
|
2012-06-17 09:40:40 +00:00
|
|
|
static int _findSegments(CloudsLayerDefinition* definition, Renderer* renderer, Vector3 start, Vector3 direction, double detail, int max_segments, double max_inside_length, double max_total_length, double* inside_length, double* total_length, CloudSegment* out_segments)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
|
|
|
int inside, segment_count;
|
2012-06-17 09:40:40 +00:00
|
|
|
double current_total_length, current_inside_length;
|
|
|
|
double step_length, segment_length, remaining_length;
|
|
|
|
double noise_distance, last_noise_distance;
|
2011-12-10 13:25:22 +00:00
|
|
|
Vector3 walker, step, segment_start;
|
2012-06-17 09:40:40 +00:00
|
|
|
double render_precision;
|
2011-12-17 15:54:10 +00:00
|
|
|
|
2011-12-10 13:25:22 +00:00
|
|
|
if (max_segments <= 0)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-12-08 14:12:02 +00:00
|
|
|
render_precision = 1.005 - 0.01 * (double)(renderer->render_quality * renderer->render_quality);
|
2012-05-07 08:56:22 +00:00
|
|
|
if (render_precision > max_total_length / 10.0)
|
|
|
|
{
|
|
|
|
render_precision = max_total_length / 10.0;
|
|
|
|
}
|
2012-12-08 14:12:02 +00:00
|
|
|
else if (render_precision < max_total_length / 10000.0)
|
2012-05-07 08:56:22 +00:00
|
|
|
{
|
2012-12-08 14:12:02 +00:00
|
|
|
render_precision = max_total_length / 10000.0;
|
2012-05-07 08:56:22 +00:00
|
|
|
}
|
2012-01-23 23:45:33 +00:00
|
|
|
|
2011-12-10 13:25:22 +00:00
|
|
|
segment_count = 0;
|
|
|
|
current_total_length = 0.0;
|
|
|
|
current_inside_length = 0.0;
|
|
|
|
segment_length = 0.0;
|
|
|
|
walker = start;
|
2012-02-06 20:54:23 +00:00
|
|
|
noise_distance = _getDistanceToBorder(definition, start) * render_precision;
|
2011-12-10 13:25:22 +00:00
|
|
|
inside = (noise_distance > 0.0) ? 1 : 0;
|
2012-01-23 23:45:33 +00:00
|
|
|
step = v3Scale(direction, render_precision);
|
2011-12-10 13:25:22 +00:00
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
walker = v3Add(walker, step);
|
|
|
|
step_length = v3Norm(step);
|
|
|
|
last_noise_distance = noise_distance;
|
2012-02-06 20:54:23 +00:00
|
|
|
noise_distance = _getDistanceToBorder(definition, walker) * render_precision;
|
2011-12-10 13:25:22 +00:00
|
|
|
current_total_length += step_length;
|
2011-12-17 15:54:10 +00:00
|
|
|
|
2012-07-01 13:27:57 +00:00
|
|
|
if (current_total_length >= max_total_length || current_inside_length > max_inside_length)
|
|
|
|
{
|
|
|
|
noise_distance = 0.0;
|
|
|
|
}
|
|
|
|
|
2011-12-10 13:25:22 +00:00
|
|
|
if (noise_distance > 0.0)
|
|
|
|
{
|
|
|
|
if (inside)
|
|
|
|
{
|
|
|
|
// inside the cloud
|
|
|
|
segment_length += step_length;
|
|
|
|
current_inside_length += step_length;
|
2012-01-23 23:45:33 +00:00
|
|
|
step = v3Scale(direction, (noise_distance < render_precision) ? render_precision : noise_distance);
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// entering the cloud
|
|
|
|
inside = 1;
|
|
|
|
segment_length = step_length * noise_distance / (noise_distance - last_noise_distance);
|
|
|
|
segment_start = v3Add(walker, v3Scale(direction, -segment_length));
|
|
|
|
current_inside_length += segment_length;
|
2012-01-23 23:45:33 +00:00
|
|
|
step = v3Scale(direction, render_precision);
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (inside)
|
|
|
|
{
|
|
|
|
// exiting the cloud
|
|
|
|
remaining_length = step_length * last_noise_distance / (last_noise_distance - noise_distance);
|
|
|
|
segment_length += remaining_length;
|
|
|
|
current_inside_length += remaining_length;
|
2011-12-17 15:54:10 +00:00
|
|
|
|
2011-12-10 13:25:22 +00:00
|
|
|
out_segments->start = segment_start;
|
|
|
|
out_segments->end = v3Add(walker, v3Scale(direction, remaining_length - step_length));
|
|
|
|
out_segments->length = segment_length;
|
|
|
|
out_segments++;
|
|
|
|
if (++segment_count >= max_segments)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
2011-12-17 15:54:10 +00:00
|
|
|
|
2011-12-10 13:25:22 +00:00
|
|
|
inside = 0;
|
2012-01-23 23:45:33 +00:00
|
|
|
step = v3Scale(direction, render_precision);
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// searching for a cloud
|
2012-01-23 23:45:33 +00:00
|
|
|
step = v3Scale(direction, (noise_distance > -render_precision) ? render_precision : -noise_distance);
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
}
|
2012-07-01 13:27:57 +00:00
|
|
|
} 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));
|
2011-12-17 15:54:10 +00:00
|
|
|
|
2011-12-10 13:25:22 +00:00
|
|
|
*total_length = current_total_length;
|
|
|
|
*inside_length = current_inside_length;
|
|
|
|
return segment_count;
|
|
|
|
}
|
|
|
|
|
2012-06-17 09:40:40 +00:00
|
|
|
static Color _applyLayerLighting(CloudsLayerDefinition* definition, Renderer* renderer, Vector3 position, double detail)
|
2012-01-22 18:39:42 +00:00
|
|
|
{
|
2012-01-24 13:59:54 +00:00
|
|
|
Vector3 normal;
|
2012-06-05 20:22:12 +00:00
|
|
|
Color col1, col2;
|
2013-01-19 22:42:50 +00:00
|
|
|
LightStatus* lighting;
|
2012-01-22 18:39:42 +00:00
|
|
|
|
2012-05-17 16:18:29 +00:00
|
|
|
normal = _getNormal(definition, position, 3.0);
|
2012-04-30 19:04:39 +00:00
|
|
|
if (renderer->render_quality > 5)
|
|
|
|
{
|
2012-05-17 16:18:29 +00:00
|
|
|
normal = v3Add(normal, _getNormal(definition, position, 2.0));
|
|
|
|
normal = v3Add(normal, _getNormal(definition, position, 1.0));
|
|
|
|
}
|
|
|
|
if (renderer->render_quality > 5)
|
|
|
|
{
|
|
|
|
normal = v3Add(normal, _getNormal(definition, position, 0.5));
|
2012-04-30 19:04:39 +00:00
|
|
|
}
|
|
|
|
normal = v3Scale(v3Normalize(normal), definition->hardness);
|
2012-12-08 14:12:02 +00:00
|
|
|
|
2013-01-19 22:42:50 +00:00
|
|
|
lighting = lightingCreateStatus(renderer->lighting, position, renderer->camera_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);
|
2012-12-08 14:12:02 +00:00
|
|
|
|
2012-06-05 20:22:12 +00:00
|
|
|
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;
|
2012-01-22 18:39:42 +00:00
|
|
|
|
2012-06-05 20:22:12 +00:00
|
|
|
return col1;
|
2012-01-22 18:39:42 +00:00
|
|
|
}
|
|
|
|
|
2012-07-01 13:27:57 +00:00
|
|
|
Color cloudsApplyLayer(CloudsLayerDefinition* definition, Color base, Renderer* renderer, Vector3 start, Vector3 end)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
|
|
|
int i, segment_count;
|
2012-06-17 09:40:40 +00:00
|
|
|
double max_length, detail, total_length, inside_length;
|
2011-12-10 13:25:22 +00:00
|
|
|
Vector3 direction;
|
2012-07-01 13:27:57 +00:00
|
|
|
Color col;
|
|
|
|
CloudSegment segments[MAX_SEGMENT_COUNT];
|
2011-12-10 13:25:22 +00:00
|
|
|
|
|
|
|
if (!_optimizeSearchLimits(definition, &start, &end))
|
|
|
|
{
|
2012-07-01 13:27:57 +00:00
|
|
|
return base;
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
2011-12-17 15:54:10 +00:00
|
|
|
|
2011-12-10 13:25:22 +00:00
|
|
|
direction = v3Sub(end, start);
|
|
|
|
max_length = v3Norm(direction);
|
|
|
|
direction = v3Normalize(direction);
|
|
|
|
|
2012-06-10 09:30:30 +00:00
|
|
|
detail = renderer->getPrecision(renderer, start) / definition->shape_scaling;
|
2011-12-17 15:54:10 +00:00
|
|
|
|
2012-07-01 13:27:57 +00:00
|
|
|
segment_count = _findSegments(definition, renderer, start, direction, detail, MAX_SEGMENT_COUNT, definition->transparencydepth * (double)renderer->render_quality, max_length, &inside_length, &total_length, segments);
|
2012-02-18 10:48:10 +00:00
|
|
|
for (i = segment_count - 1; i >= 0; i--)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-02-07 21:03:58 +00:00
|
|
|
col = _applyLayerLighting(definition, renderer, segments[i].start, detail);
|
2012-07-01 13:27:57 +00:00
|
|
|
col.a = 1.0;
|
2012-11-25 21:53:01 +00:00
|
|
|
col = renderer->atmosphere->applyAerialPerspective(renderer, start, col);
|
2012-02-07 21:03:58 +00:00
|
|
|
col.a = (segments[i].length >= definition->transparencydepth) ? 1.0 : (segments[i].length / definition->transparencydepth);
|
2012-07-01 13:27:57 +00:00
|
|
|
colorMask(&base, &col);
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
2012-02-18 10:48:10 +00:00
|
|
|
if (inside_length >= definition->transparencydepth)
|
|
|
|
{
|
|
|
|
col.a = 1.0;
|
|
|
|
}
|
2012-01-24 15:47:49 +00:00
|
|
|
|
2012-07-01 13:27:57 +00:00
|
|
|
return base;
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
|
2012-08-26 13:36:46 +00:00
|
|
|
/*static int _cmpLayer(const void* layer1, const void* layer2)
|
2012-02-08 21:20:22 +00:00
|
|
|
{
|
2012-07-01 13:27:57 +00:00
|
|
|
return (((CloudsLayerDefinition*)layer1)->lower_altitude > ((CloudsLayerDefinition*)layer2)->lower_altitude) ? -1 : 1;
|
2012-08-26 13:36:46 +00:00
|
|
|
}*/
|
2012-02-08 21:20:22 +00:00
|
|
|
|
2012-07-01 13:27:57 +00:00
|
|
|
Color cloudsApply(CloudsDefinition* definition, Color base, Renderer* renderer, Vector3 start, Vector3 end)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-08-26 13:36:46 +00:00
|
|
|
int i, n;
|
2011-12-10 13:25:22 +00:00
|
|
|
|
2012-08-26 13:36:46 +00:00
|
|
|
n = layersCount(definition->layers);
|
|
|
|
if (n < 1)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-07-01 13:27:57 +00:00
|
|
|
return base;
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
|
2012-08-26 13:36:46 +00:00
|
|
|
/* TODO Iter layers in sorted order */
|
|
|
|
for (i = 0; i < n; i++)
|
2011-12-10 13:25:22 +00:00
|
|
|
{
|
2012-08-26 13:36:46 +00:00
|
|
|
base = cloudsApplyLayer(layersGetLayer(definition->layers, i), base, renderer, start, end);
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
|
|
|
|
2012-07-01 13:27:57 +00:00
|
|
|
return base;
|
2011-12-10 13:25:22 +00:00
|
|
|
}
|
2012-01-24 13:59:54 +00:00
|
|
|
|
|
|
|
Color cloudsLayerFilterLight(CloudsLayerDefinition* definition, Renderer* renderer, Color light, Vector3 location, Vector3 light_location, Vector3 direction_to_light)
|
|
|
|
{
|
2012-06-17 09:40:40 +00:00
|
|
|
double inside_depth, total_depth, factor;
|
2012-07-01 13:27:57 +00:00
|
|
|
CloudSegment segments[MAX_SEGMENT_COUNT];
|
2012-01-24 13:59:54 +00:00
|
|
|
|
|
|
|
_optimizeSearchLimits(definition, &location, &light_location);
|
|
|
|
|
2012-07-01 13:27:57 +00:00
|
|
|
_findSegments(definition, renderer, location, direction_to_light, 0.1, MAX_SEGMENT_COUNT, definition->lighttraversal, v3Norm(v3Sub(light_location, location)), &inside_depth, &total_depth, segments);
|
2012-01-24 13:59:54 +00:00
|
|
|
|
2012-02-07 21:03:58 +00:00
|
|
|
if (definition->lighttraversal < 0.0001)
|
2012-01-24 13:59:54 +00:00
|
|
|
{
|
2012-02-07 21:03:58 +00:00
|
|
|
factor = 0.0;
|
2012-01-24 13:59:54 +00:00
|
|
|
}
|
2012-02-07 21:03:58 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
factor = inside_depth / definition->lighttraversal;
|
|
|
|
if (factor > 1.0)
|
|
|
|
{
|
|
|
|
factor = 1.0;
|
|
|
|
}
|
|
|
|
}
|
2012-12-08 14:12:02 +00:00
|
|
|
|
2012-02-07 21:03:58 +00:00
|
|
|
factor = 1.0 - (1.0 - definition->minimumlight) * factor;
|
2012-01-24 13:59:54 +00:00
|
|
|
|
2012-02-07 21:03:58 +00:00
|
|
|
light.r = light.r * factor;
|
|
|
|
light.g = light.g * factor;
|
|
|
|
light.b = light.b * factor;
|
2012-01-24 13:59:54 +00:00
|
|
|
|
|
|
|
return light;
|
|
|
|
}
|
|
|
|
|
|
|
|
Color cloudsFilterLight(CloudsDefinition* definition, Renderer* renderer, Color light, Vector3 location, Vector3 light_location, Vector3 direction_to_light)
|
|
|
|
{
|
2012-08-26 13:36:46 +00:00
|
|
|
int i, n;
|
2012-12-08 14:12:02 +00:00
|
|
|
|
2012-08-26 13:36:46 +00:00
|
|
|
/* TODO Iter layers in sorted order */
|
|
|
|
n = layersCount(definition->layers);
|
|
|
|
for (i = 0; i < n; i++)
|
2012-01-24 13:59:54 +00:00
|
|
|
{
|
2012-08-26 13:36:46 +00:00
|
|
|
light = cloudsLayerFilterLight(layersGetLayer(definition->layers, i), renderer, light, location, light_location, direction_to_light);
|
2012-01-24 13:59:54 +00:00
|
|
|
}
|
|
|
|
return light;
|
|
|
|
}
|