paysages3d/lib_paysages/terrain.c
2011-12-23 22:00:19 +00:00

403 lines
10 KiB
C

#include <stdlib.h>
#include <math.h>
#include "shared/types.h"
#include "shared/functions.h"
#include "shared/globals.h"
#include "shared/constants.h"
#include "water.h"
#define MAX_TEXTURES 10
#define MAX_MODIFIERS 50
typedef struct {
Texture* tex;
double subsurf_scale;
Zone* zone;
double border_scaling;
} TerrainTexture;
static NoiseGenerator* _noise_height;
static NoiseGenerator* _noise_texture_borders;
static double _max_height = 1.0;
static double _base_chunk_size = 1.0, _visible_chunk_size = 1.0;
static int _texture_count = 0;
static TerrainTexture _textures[MAX_TEXTURES];
static int _modifiers_count = 0;
static HeightModifier* _modifiers[MAX_MODIFIERS];
void terrainSave(FILE* f)
{
noiseSave(_noise_height, f);
noiseSave(_noise_texture_borders, f);
toolsSaveDouble(f, _max_height);
toolsSaveDouble(f, _base_chunk_size);
toolsSaveDouble(f, _visible_chunk_size);
/* TODO Textures */
/* TODO Modifiers */
}
void terrainLoad(FILE* f)
{
noiseLoad(_noise_height, f);
noiseLoad(_noise_texture_borders, f);
_max_height = toolsLoadDouble(f);
_base_chunk_size = toolsLoadDouble(f);
_visible_chunk_size = toolsLoadDouble(f);
}
void terrainInit()
{
_noise_height = noiseCreateGenerator();
_max_height = noiseGetMaxValue(_noise_height);
_noise_texture_borders = noiseCreateGenerator();
noiseGenerateBaseNoise(_noise_texture_borders, 100);
noiseAddLevelsSimple(_noise_texture_borders, 10, 1.0, 1.0);
noiseNormalizeHeight(_noise_texture_borders, 0.0, 1.0, 0);
}
NoiseGenerator* terrainGetNoiseGenerator()
{
return _noise_height;
}
void terrainSetNoiseGenerator(NoiseGenerator* generator)
{
noiseCopy(generator, _noise_height);
_max_height = noiseGetMaxValue(_noise_height);
/* FIXME Max height depends on modifiers*/
}
void terrainAddTexture(Texture* tex, double subsurf_scale, Zone* zone, double border_scaling)
{
TerrainTexture ttex;
if (_texture_count < MAX_TEXTURES)
{
ttex.tex = tex;
ttex.subsurf_scale = subsurf_scale;
ttex.zone = zone;
ttex.border_scaling = border_scaling;
_textures[_texture_count++] = ttex;
}
}
void terrainAddModifier(HeightModifier* modifier)
{
if (_modifiers_count < MAX_MODIFIERS)
{
_modifiers[_modifiers_count++] = modifier;
}
}
static inline double _getHeight(double x, double z, double precision)
{
Vector3 location;
int i;
location.x = x;
location.y = noiseGet2DDetail(_noise_height, x, z, precision);
location.z = z;
for (i = 0; i < _modifiers_count; i++)
{
location = modifierApply(_modifiers[i], location);
}
return location.y;
}
static inline Vector3 _getPoint(double x, double z, double precision)
{
Vector3 result;
result.x = x;
result.y = _getHeight(x, z, precision);
result.z = z;
return result;
}
double terrainGetShadow(Vector3 start, Vector3 direction)
{
Vector3 inc_vector;
double inc_value, inc_base, inc_factor, height, diff, light, smoothing, length, water;
direction = v3Normalize(direction);
inc_factor = (double)render_quality;
inc_base = 1.0;
inc_value = inc_base / inc_factor;
smoothing = 0.03 * inc_factor;;
water = waterGetLightFactor(start);
light = 1.0;
length = 0.0;
do
{
inc_vector = v3Scale(direction, inc_value);
length += v3Norm(inc_vector);
start = v3Add(start, inc_vector);
height = _getHeight(start.x, start.z, inc_value);
diff = start.y - height;
if (diff < 0.0)
{
light += diff / smoothing;
}
if (diff < inc_base / inc_factor)
{
inc_value = inc_base / inc_factor;
}
else if (diff > inc_base)
{
inc_value = inc_base;
}
else
{
inc_value = diff;
}
} while (light > 0.0 && length < 50.0 && start.y <= _max_height);
light *= water;
if (light < 0.0)
{
return 1.0;
}
else
{
return 1.0 - light;
}
}
static inline Vector3 _getNormal(Vector3 point, double scale)
{
Vector3 dpoint, ref, normal;
ref.x = 0.0;
ref.y = 0.0;
dpoint = _getPoint(point.x - scale, point.z, scale * 0.3);
ref.z = -1.0;
normal = v3Normalize(v3Cross(ref, v3Sub(dpoint, point)));
dpoint = _getPoint(point.x + scale, point.z, scale * 0.3);
ref.z = 1.0;
normal = v3Add(normal, v3Normalize(v3Cross(ref, v3Sub(dpoint, point))));
ref.z = 0.0;
dpoint = _getPoint(point.x, point.z - scale, scale * 0.3);
ref.x = 1.0;
normal = v3Add(normal, v3Normalize(v3Cross(ref, v3Sub(dpoint, point))));
dpoint = _getPoint(point.x, point.z + scale, scale * 0.3);
ref.x = -1.0;
normal = v3Add(normal, v3Normalize(v3Cross(ref, v3Sub(dpoint, point))));
return v3Normalize(normal);
}
static Color _getColor(Vector3 point, double precision)
{
Vector3 normal;
Color color, tex_color;
int i;
double shadowed, coverage, value;
shadowed = terrainGetShadow(point, sun_direction_inv);
/* Apply textures and subsurface lighting */
color = COLOR_GREEN;
for (i = 0; i < _texture_count; i++)
{
/* TODO Don't recalculate normals for same precision */
/* TODO Don't compute textures that will be totally covered */
normal = _getNormal(point, precision * _textures[i].subsurf_scale);
coverage = zoneGetValue(_textures[i].zone, point, normal);
if (coverage > 0.0)
{
if (coverage < 1.0)
{
value = noiseGet2DTotal(_noise_texture_borders, point.x / _textures[i].border_scaling, point.z / _textures[i].border_scaling);
if (value < coverage)
{
/* TODO Make smoothness precision-dependant */
value = (coverage - value) / 0.1;
coverage = (value > 1.0) ? 1.0 : value;
}
else
{
coverage = 0.0;
}
}
if (coverage > 0.0)
{
tex_color = textureApply(_textures[i].tex, point, normal);
tex_color = lightingApply(point, normal, shadowed, tex_color, 0.1, 0.1);
tex_color.a = coverage;
colorMask(&color, &tex_color);
}
}
}
color = fogApplyToLocation(point, color);
//color = cloudsApplySegmentResult(color, camera_location, point);
return color;
}
int terrainProjectRay(Vector3 start, Vector3 direction, Vector3* hit_point, Color* hit_color)
{
Vector3 inc_vector;
double inc_value, inc_base, inc_factor, height, diff, length;
direction = v3Normalize(direction);
inc_factor = (double)render_quality;
inc_base = 1.0;
inc_value = inc_base / inc_factor;
length = 0.0;
do
{
inc_vector = v3Scale(direction, inc_value);
length += v3Norm(inc_vector);
start = v3Add(start, inc_vector);
height = _getHeight(start.x, start.z, inc_value);
diff = start.y - height;
if (diff < 0.0)
{
start.y = height;
*hit_point = start;
*hit_color = _getColor(start, inc_value);
return 1;
}
if (diff < inc_base / inc_factor)
{
inc_value = inc_base / inc_factor;
}
else if (diff > inc_base)
{
inc_value = inc_base;
}
else
{
inc_value = diff;
}
} while (length < 50.0 && start.y <= _max_height);
return 0;
}
static int _postProcessFragment(RenderFragment* fragment)
{
Vector3 point;
double precision;
point = fragment->vertex.location;
precision = renderGetPrecision(point);
point = _getPoint(point.x, point.z, precision);
fragment->vertex.color = _getColor(point, precision);
return 1;
}
static Vertex _getFirstPassVertex(double x, double z, double precision)
{
Vertex result;
double value;
result.location = _getPoint(x, z, 0.0);
value = sin(x) * sin(x) * cos(z) * cos(z);
result.color.r = value;
result.color.g = value;
result.color.b = value;
result.color.a = 1.0;
result.normal.x = result.normal.y = result.normal.z = 0.0;
result.callback = _postProcessFragment;
return result;
}
static void _renderQuad(double x, double z, double size)
{
Vertex v1, v2, v3, v4;
v1 = _getFirstPassVertex(x, z, size);
v2 = _getFirstPassVertex(x, z + size, size);
v3 = _getFirstPassVertex(x + size, z + size, size);
v4 = _getFirstPassVertex(x + size, z, size);
renderPushQuad(&v1, &v2, &v3, &v4);
}
double terrainGetHeight(double x, double z)
{
return _getHeight(x, z, 0.01 / (double)render_quality);
}
double terrainGetHeightNormalized(double x, double z)
{
return 0.5 + _getHeight(x, z, 0.01 / (double)render_quality) / (_max_height * 2.0);
}
Color terrainGetColor(double x, double z, double precision)
{
return _getColor(_getPoint(x, z, precision), precision);
}
void terrainSetChunkSize(double min_size, double visible_size)
{
_base_chunk_size = min_size;
_visible_chunk_size = visible_size;
}
void terrainRender(RenderProgressCallback callback)
{
int chunk_factor, chunk_count, i;
double cx = camera_location.x;
double cz = camera_location.z;
double radius_int, radius_ext, chunk_size;
chunk_factor = 1;
chunk_count = 2;
radius_int = 0.0;
radius_ext = _base_chunk_size;
chunk_size = _base_chunk_size;
while (radius_ext < 1000.0)
{
if (!callback(radius_ext / 1000.0))
{
return;
}
for (i = 0; i < chunk_count - 1; i++)
{
_renderQuad(cx - radius_ext + chunk_size * i, cz - radius_ext, chunk_size);
_renderQuad(cx + radius_int, cz - radius_ext + chunk_size * i, chunk_size);
_renderQuad(cx + radius_int - chunk_size * i, cz + radius_int, chunk_size);
_renderQuad(cx - radius_ext, cz + radius_int - chunk_size * i, chunk_size);
}
if (chunk_count % 64 == 0 && chunk_size / radius_int < _visible_chunk_size)
{
chunk_count /= 2;
chunk_factor *= 2;
/* TODO Fill in gaps with triangles */
}
chunk_count += 2;
chunk_size = _base_chunk_size * chunk_factor;
radius_int = radius_ext;
radius_ext += chunk_size;
}
}