paysages : Terrain painting (WIP).

git-svn-id: https://subversion.assembla.com/svn/thunderk/paysages@493 b1fd45b6-86a6-48da-8261-f70d1f35bdcc
This commit is contained in:
Michaël Lemaire 2013-01-14 12:08:38 +00:00 committed by ThunderK
parent b816ab1105
commit 5cad0fb39a
15 changed files with 134 additions and 58 deletions

View file

@ -49,7 +49,7 @@ bool ExplorerChunkTerrain::onMaintainEvent()
{ {
if (_tessellation_current_size == 0 || i % old_tessellation_inc != 0 || j % old_tessellation_inc != 0) if (_tessellation_current_size == 0 || i % old_tessellation_inc != 0 || j % old_tessellation_inc != 0)
{ {
double height = renderer->terrain->getHeight(renderer, _startx + _tessellation_step * (double)i, _startz + _tessellation_step * (double)j); double height = renderer->terrain->getHeight(renderer, _startx + _tessellation_step * (double)i, _startz + _tessellation_step * (double)j, 1);
_tessellation[j * (_tessellation_max_size + 1) + i] = height; _tessellation[j * (_tessellation_max_size + 1) + i] = height;
} }
} }

View file

@ -43,7 +43,7 @@ protected:
{ {
Vector3 down = {0.0, -1.0, 0.0}; Vector3 down = {0.0, -1.0, 0.0};
Vector3 location; Vector3 location;
double height = _renderer.terrain->getHeight(&_renderer, x, y); double height = _renderer.terrain->getHeight(&_renderer, x, y, 1);
if (height < _water.height) if (height < _water.height)
{ {

View file

@ -49,8 +49,8 @@ FormTerrain::FormTerrain(QWidget *parent):
addPreview(previewColor, tr("Lighted preview (no texture)")); addPreview(previewColor, tr("Lighted preview (no texture)"));
//addInputNoise(tr("Noise"), _definition.height_noise); //addInputNoise(tr("Noise"), _definition.height_noise);
addInputDouble(tr("Height"), &_definition->height, 0.0, 20.0, 0.1, 1.0); addInputDouble(tr("Scaling"), &_definition->scaling, 0.1, 3.0, 0.03, 0.3);
addInputDouble(tr("Scaling"), &_definition->scaling, 20.0, 200.0, 1.0, 10.0); addInputDouble(tr("Height modifier"), &_definition->height, 0.0, 3.0, 0.01, 0.1);
addInputDouble(tr("Shadow smoothing"), &_definition->shadow_smoothing, 0.0, 0.3, 0.003, 0.03); addInputDouble(tr("Shadow smoothing"), &_definition->shadow_smoothing, 0.0, 0.3, 0.003, 0.03);
revertConfig(); revertConfig();

View file

@ -40,7 +40,7 @@ protected:
Vector3 location; Vector3 location;
double coverage; double coverage;
location.x = x; location.x = x;
location.y = _renderer.terrain->getHeight(&_renderer, x, y); location.y = _renderer.terrain->getHeight(&_renderer, x, y, 1);
location.z = y; location.z = y;
coverage = texturesGetLayerCoverage(_preview_layer, &_renderer, location, this->scaling); coverage = texturesGetLayerCoverage(_preview_layer, &_renderer, location, this->scaling);
return QColor::fromRgbF(coverage, coverage, coverage, 1.0); return QColor::fromRgbF(coverage, coverage, coverage, 1.0);

View file

@ -35,7 +35,7 @@ protected:
{ {
double height; double height;
height = _renderer.terrain->getHeight(&_renderer, x, -y); height = _renderer.terrain->getHeight(&_renderer, x, -y, 1);
if (height > _definition.height) if (height > _definition.height)
{ {
return colorToQColor(terrainGetPreviewColor(&_renderer, x, -y, scaling)); return colorToQColor(terrainGetPreviewColor(&_renderer, x, -y, scaling));

View file

@ -151,10 +151,10 @@ void WidgetHeightMap::timerEvent(QTimerEvent*)
double brush_strength; double brush_strength;
TerrainBrush brush; TerrainBrush brush;
brush.relative_x = (_brush_x + 40.0) / 80.0; brush.relative_x = _brush_x;
brush.relative_z = (_brush_z + 40.0) / 80.0; brush.relative_z = _brush_z;
brush.hard_radius = _brush_size * (1.0 - _brush_smoothing) / 80.0; brush.hard_radius = _brush_size * (1.0 - _brush_smoothing);
brush.smoothed_size = _brush_size * _brush_smoothing / 80.0; brush.smoothed_size = _brush_size * _brush_smoothing;
brush.total_radius = brush.hard_radius + brush.smoothed_size; brush.total_radius = brush.hard_radius + brush.smoothed_size;
brush_strength = _brush_strength * duration / 0.1; brush_strength = _brush_strength * duration / 0.1;
@ -363,10 +363,10 @@ void WidgetHeightMap::updateVertexInfo()
{ {
_VertexInfo* vertex = _vertices + z * rx + x; _VertexInfo* vertex = _vertices + z * rx + x;
vertex->point.x = 80.0 * (double)x / (double)(rx - 1) - 40.0; vertex->point.x = (double)x;
vertex->point.z = 80.0 * (double)z / (double)(rz - 1) - 40.0; vertex->point.z = (double)z;
vertex->point.y = _renderer.terrain->getHeight(&_renderer, vertex->point.x, vertex->point.z); vertex->point.y = terrainGetGridHeight(_terrain, x, z, 1);
} }
} }

View file

@ -76,7 +76,7 @@ void cameraValidateDefinition(CameraDefinition* definition, int check_above)
waterDeleteDefinition(&water); waterDeleteDefinition(&water);
renderer = sceneryCreateStandardRenderer(); renderer = sceneryCreateStandardRenderer();
terrain_height = renderer.terrain->getHeight(&renderer, definition->location.x, definition->location.z) + 0.5; terrain_height = renderer.terrain->getHeight(&renderer, definition->location.x, definition->location.z, 1) + 0.5;
rendererDelete(&renderer); rendererDelete(&renderer);
if (definition->location.y < water_height || definition->location.y < terrain_height) if (definition->location.y < water_height || definition->location.y < terrain_height)

View file

@ -84,25 +84,29 @@ StandardDefinition TerrainDefinitionClass = {
}; };
/******************** Binding ********************/ /******************** Binding ********************/
static double _fakeGetHeight(Renderer* renderer, double x, double z) static double _fakeGetHeight(Renderer* renderer, double x, double z, int with_painting)
{ {
UNUSED(renderer); UNUSED(renderer);
UNUSED(x); UNUSED(x);
UNUSED(z); UNUSED(z);
UNUSED(with_painting);
return 0.0; return 0.0;
} }
static double _getHeight(Renderer* renderer, double x, double z) static double _getHeight(Renderer* renderer, double x, double z, int with_painting)
{ {
double height;
TerrainDefinition* definition = renderer->terrain->definition; TerrainDefinition* definition = renderer->terrain->definition;
x /= definition->scaling; x /= definition->scaling;
z /= definition->scaling; z /= definition->scaling;
double height = noiseGet2DTotal(definition->_height_noise, x, z); if (!with_painting || !terrainHeightmapGetHeight(definition->height_map, x, z, &height))
/* TODO Apply paintings */ {
height = noiseGet2DTotal(definition->_height_noise, x, z);
}
return height * definition->height; return height * definition->height * definition->scaling;
} }
static Color _fakeGetFinalColor(Renderer* renderer, Vector3 location, double precision) static Color _fakeGetFinalColor(Renderer* renderer, Vector3 location, double precision)
@ -123,7 +127,7 @@ static Color _getFinalColor(Renderer* renderer, Vector3 location, double precisi
return color; return color;
} }
RayCastingResult _fakeCastRay(Renderer* renderer, Vector3 start, Vector3 direction) static RayCastingResult _fakeCastRay(Renderer* renderer, Vector3 start, Vector3 direction)
{ {
UNUSED(renderer); UNUSED(renderer);
UNUSED(start); UNUSED(start);
@ -134,7 +138,7 @@ RayCastingResult _fakeCastRay(Renderer* renderer, Vector3 start, Vector3 directi
return result; return result;
} }
RayCastingResult _castRay(Renderer* renderer, Vector3 start, Vector3 direction) static RayCastingResult _castRay(Renderer* renderer, Vector3 start, Vector3 direction)
{ {
RayCastingResult result; RayCastingResult result;
TerrainDefinition* definition = renderer->terrain->definition; TerrainDefinition* definition = renderer->terrain->definition;
@ -145,7 +149,7 @@ RayCastingResult _castRay(Renderer* renderer, Vector3 start, Vector3 direction)
inc_factor = (double)renderer->render_quality; inc_factor = (double)renderer->render_quality;
inc_base = 1.0; inc_base = 1.0;
inc_value = inc_base / inc_factor; inc_value = inc_base / inc_factor;
lastdiff = start.y - _getHeight(renderer, start.x, start.z); lastdiff = start.y - _getHeight(renderer, start.x, start.z, 1);
length = 0.0; length = 0.0;
do do
@ -153,14 +157,14 @@ RayCastingResult _castRay(Renderer* renderer, Vector3 start, Vector3 direction)
inc_vector = v3Scale(direction, inc_value); inc_vector = v3Scale(direction, inc_value);
length += v3Norm(inc_vector); length += v3Norm(inc_vector);
start = v3Add(start, inc_vector); start = v3Add(start, inc_vector);
height = _getHeight(renderer, start.x, start.z); height = _getHeight(renderer, start.x, start.z, 1);
diff = start.y - height; diff = start.y - height;
if (diff < 0.0) if (diff < 0.0)
{ {
if (fabs(diff - lastdiff) > 0.00001) if (fabs(diff - lastdiff) > 0.00001)
{ {
start = v3Add(start, v3Scale(inc_vector, -diff / (diff - lastdiff))); start = v3Add(start, v3Scale(inc_vector, -diff / (diff - lastdiff)));
start.y = _getHeight(renderer, start.x, start.z); start.y = _getHeight(renderer, start.x, start.z, 1);
} }
else else
{ {
@ -236,7 +240,7 @@ static LightDefinition _alterLight(Renderer* renderer, LightDefinition* light, V
inc_vector = v3Scale(direction_to_light, inc_value); inc_vector = v3Scale(direction_to_light, inc_value);
length += v3Norm(inc_vector); length += v3Norm(inc_vector);
location = v3Add(location, inc_vector); location = v3Add(location, inc_vector);
height = _getHeight(renderer, location.x, location.z); height = _getHeight(renderer, location.x, location.z, 1);
diff = location.y - height; diff = location.y - height;
if (diff < 0.0) if (diff < 0.0)
{ {
@ -279,6 +283,19 @@ static LightDefinition _alterLight(Renderer* renderer, LightDefinition* light, V
} }
} }
/******************** Public tools ********************/
double terrainGetGridHeight(TerrainDefinition* definition, int x, int z, int with_painting)
{
double height;
if (!with_painting || !terrainHeightmapGetHeight(definition->height_map, (double)x, (double)z, &height))
{
height = noiseGet2DTotal(definition->_height_noise, (double)x, (double)z);
}
return height;
}
/******************** Renderer ********************/ /******************** Renderer ********************/
static TerrainRenderer* _createRenderer() static TerrainRenderer* _createRenderer()
{ {

View file

@ -71,13 +71,13 @@ void terrainHeightmapCopy(TerrainHeightMap* source, TerrainHeightMap* destinatio
{ {
destination->fixed_data[i].xstart = source->fixed_data[i].xstart; destination->fixed_data[i].xstart = source->fixed_data[i].xstart;
destination->fixed_data[i].xsize = source->fixed_data[i].xsize; destination->fixed_data[i].xsize = source->fixed_data[i].xsize;
destination->fixed_data[i].ystart = source->fixed_data[i].ystart; destination->fixed_data[i].zstart = source->fixed_data[i].zstart;
destination->fixed_data[i].ysize = source->fixed_data[i].ysize; destination->fixed_data[i].zsize = source->fixed_data[i].zsize;
if (destination->fixed_data[i].xsize * destination->fixed_data[i].ysize > 0) if (destination->fixed_data[i].xsize * destination->fixed_data[i].zsize > 0)
{ {
destination->fixed_data[i].data = realloc(destination->fixed_data[i].data, sizeof(double) * destination->fixed_data[i].xsize * destination->fixed_data[i].ysize); destination->fixed_data[i].data = realloc(destination->fixed_data[i].data, sizeof(double) * destination->fixed_data[i].xsize * destination->fixed_data[i].zsize);
} }
memcpy(destination->fixed_data[i].data, source->fixed_data[i].data, sizeof(double) * destination->fixed_data[i].xsize * destination->fixed_data[i].ysize); memcpy(destination->fixed_data[i].data, source->fixed_data[i].data, sizeof(double) * destination->fixed_data[i].xsize * destination->fixed_data[i].zsize);
} }
destination->floating_used = 0; destination->floating_used = 0;
@ -92,9 +92,9 @@ void terrainHeightmapSave(PackStream* stream, TerrainHeightMap* heightmap)
{ {
packWriteInt(stream, &heightmap->fixed_data[i].xstart); packWriteInt(stream, &heightmap->fixed_data[i].xstart);
packWriteInt(stream, &heightmap->fixed_data[i].xsize); packWriteInt(stream, &heightmap->fixed_data[i].xsize);
packWriteInt(stream, &heightmap->fixed_data[i].ystart); packWriteInt(stream, &heightmap->fixed_data[i].zstart);
packWriteInt(stream, &heightmap->fixed_data[i].ysize); packWriteInt(stream, &heightmap->fixed_data[i].zsize);
for (j = 0; j < heightmap->fixed_data[i].xsize * heightmap->fixed_data[i].ysize; j++) for (j = 0; j < heightmap->fixed_data[i].xsize * heightmap->fixed_data[i].zsize; j++)
{ {
packWriteDouble(stream, &heightmap->fixed_data[i].data[j]); packWriteDouble(stream, &heightmap->fixed_data[i].data[j]);
} }
@ -112,13 +112,13 @@ void terrainHeightmapLoad(PackStream* stream, TerrainHeightMap* heightmap)
{ {
packReadInt(stream, &heightmap->fixed_data[i].xstart); packReadInt(stream, &heightmap->fixed_data[i].xstart);
packReadInt(stream, &heightmap->fixed_data[i].xsize); packReadInt(stream, &heightmap->fixed_data[i].xsize);
packReadInt(stream, &heightmap->fixed_data[i].ystart); packReadInt(stream, &heightmap->fixed_data[i].zstart);
packReadInt(stream, &heightmap->fixed_data[i].ysize); packReadInt(stream, &heightmap->fixed_data[i].zsize);
if (heightmap->fixed_data[i].xsize * heightmap->fixed_data[i].ysize > 0) if (heightmap->fixed_data[i].xsize * heightmap->fixed_data[i].zsize > 0)
{ {
heightmap->fixed_data[i].data = realloc(heightmap->fixed_data[i].data, sizeof(double) * heightmap->fixed_data[i].xsize * heightmap->fixed_data[i].ysize); heightmap->fixed_data[i].data = realloc(heightmap->fixed_data[i].data, sizeof(double) * heightmap->fixed_data[i].xsize * heightmap->fixed_data[i].zsize);
} }
for (j = 0; j < heightmap->fixed_data[i].xsize * heightmap->fixed_data[i].ysize; j++) for (j = 0; j < heightmap->fixed_data[i].xsize * heightmap->fixed_data[i].zsize; j++)
{ {
packReadDouble(stream, &heightmap->fixed_data[i].data[j]); packReadDouble(stream, &heightmap->fixed_data[i].data[j]);
} }
@ -127,32 +127,90 @@ void terrainHeightmapLoad(PackStream* stream, TerrainHeightMap* heightmap)
heightmap->floating_used = 0; heightmap->floating_used = 0;
} }
static inline int _checkDataHit(TerrainHeightMapData* data, double x, double z, double* result)
{
if (x > (double)data->xstart && x < (double)(data->xstart + data->xsize) && z > (double)data->zstart && z < (double)(data->zstart + data->zsize))
{
/* TODO Get interpolated value */
*result = 0.0;
return 1;
}
else
{
return 0;
}
}
int terrainHeightmapGetHeight(TerrainHeightMap* heightmap, double x, double z, double* result)
{
int i;
for (i = 0; i < heightmap->fixed_count; i++)
{
if (_checkDataHit(heightmap->fixed_data + i, x, z, result))
{
return 1;
}
}
if (heightmap->floating_used && _checkDataHit(&heightmap->floating_data, x, z, result))
{
return 1;
}
else
{
return 0;
}
}
static void _prepareBrushStroke(TerrainHeightMap* heightmap, TerrainBrush* brush) static void _prepareBrushStroke(TerrainHeightMap* heightmap, TerrainBrush* brush)
{ {
double cx = brush->relative_x / TERRAIN_HEIGHTMAP_DETAIL; double cx = brush->relative_x;
double cz = brush->relative_z / TERRAIN_HEIGHTMAP_DETAIL; double cz = brush->relative_z;
double s = brush->smoothed_size + brush->hard_radius; double s = brush->smoothed_size + brush->hard_radius;
double sx = s / TERRAIN_HEIGHTMAP_DETAIL; double sx = s;
double sz = s / TERRAIN_HEIGHTMAP_DETAIL; double sz = s;
int x1 = (int)floor(cx - sx); int x1 = (int)floor(cx - sx);
int x2 = (int)ceil(cx + sx); int x2 = (int)ceil(cx + sx);
int z1 = (int)floor(cz - sz); int z1 = (int)floor(cz - sz);
int z2 = (int)ceil(cz + sz); int z2 = (int)ceil(cz + sz);
/* TODO Prepare floating data */ /* Prepare floating data */
if (heightmap->floating_used)
{
/* Grow floating area */
/* TODO */
}
else
{
/* Init flaoting area */
heightmap->floating_used = 1;
heightmap->floating_data.xstart = x1;
heightmap->floating_data.xsize = x2 - x1 + 1;
heightmap->floating_data.zstart = z1;
heightmap->floating_data.zsize = z2 - z1 + 1;
size_t new_size;
new_size = sizeof(double) * heightmap->floating_data.xsize * heightmap->floating_data.zsize;
heightmap->floating_data.data = realloc(heightmap->floating_data.data, new_size);
memset(heightmap->floating_data.data, 0, new_size);
}
} }
void terrainBrushElevation(TerrainHeightMap* heightmap, TerrainBrush* brush, double value) void terrainBrushElevation(TerrainHeightMap* heightmap, TerrainBrush* brush, double value)
{ {
_prepareBrushStroke(heightmap, brush);
} }
void terrainBrushSmooth(TerrainHeightMap* heightmap, TerrainBrush* brush, double value) void terrainBrushSmooth(TerrainHeightMap* heightmap, TerrainBrush* brush, double value)
{ {
_prepareBrushStroke(heightmap, brush);
} }
void terrainBrushAddNoise(TerrainHeightMap* heightmap, TerrainBrush* brush, NoiseGenerator* generator, double value) void terrainBrushAddNoise(TerrainHeightMap* heightmap, TerrainBrush* brush, NoiseGenerator* generator, double value)
{ {
_prepareBrushStroke(heightmap, brush);
} }
void terrainBrushReset(TerrainHeightMap* heightmap, TerrainBrush* brush, double value) void terrainBrushReset(TerrainHeightMap* heightmap, TerrainBrush* brush, double value)

View file

@ -7,14 +7,15 @@
void terrainAutoPreset(TerrainDefinition* definition, TerrainPreset preset) void terrainAutoPreset(TerrainDefinition* definition, TerrainPreset preset)
{ {
int resolution = 8;
switch (preset) switch (preset)
{ {
case TERRAIN_PRESET_STANDARD: case TERRAIN_PRESET_STANDARD:
noiseClearLevels(definition->_height_noise); noiseClearLevels(definition->_height_noise);
noiseAddLevelsSimple(definition->_height_noise, 8, 12.8, 12.8); /* Detail = 0.1 */ noiseAddLevelsSimple(definition->_height_noise, resolution, pow(2.0, resolution - 1), 25.0);
noiseSetFunctionParams(definition->_height_noise, NOISE_FUNCTION_SIMPLEX, 0.0); noiseSetFunctionParams(definition->_height_noise, NOISE_FUNCTION_SIMPLEX, 0.0);
definition->height = 2.0; definition->scaling = 1.0;
definition->scaling = 10.0; definition->height = 1.0;
definition->shadow_smoothing = 0.03; definition->shadow_smoothing = 0.03;
break; break;
default: default:

View file

@ -88,7 +88,7 @@ Color terrainGetPreviewColor(Renderer* renderer, double x, double z, double deta
Vector3 point; Vector3 point;
point.x = x; point.x = x;
point.y = renderer->terrain->getHeight(renderer, x, z); point.y = renderer->terrain->getHeight(renderer, x, z, 1);
point.z = z; point.z = z;
return renderer->terrain->getFinalColor(renderer, point, detail); return renderer->terrain->getFinalColor(renderer, point, detail);

View file

@ -3,14 +3,12 @@
#include "public.h" #include "public.h"
#define TERRAIN_HEIGHTMAP_DETAIL 0.1
typedef struct typedef struct
{ {
int xstart; int xstart;
int ystart; int zstart;
int xsize; int xsize;
int ysize; int zsize;
double* data; double* data;
} TerrainHeightMapData; } TerrainHeightMapData;
@ -27,5 +25,6 @@ void terrainHeightmapDelete(TerrainHeightMap* heightmap);
void terrainHeightmapCopy(TerrainHeightMap* source, TerrainHeightMap* destination); void terrainHeightmapCopy(TerrainHeightMap* source, TerrainHeightMap* destination);
void terrainHeightmapSave(PackStream* stream, TerrainHeightMap* heightmap); void terrainHeightmapSave(PackStream* stream, TerrainHeightMap* heightmap);
void terrainHeightmapLoad(PackStream* stream, TerrainHeightMap* heightmap); void terrainHeightmapLoad(PackStream* stream, TerrainHeightMap* heightmap);
int terrainHeightmapGetHeight(TerrainHeightMap* heightmap, double x, double z, double* result);
#endif #endif

View file

@ -30,7 +30,7 @@ typedef struct
double _max_height; double _max_height;
} TerrainDefinition; } TerrainDefinition;
typedef double (*FuncTerrainGetHeight)(Renderer* renderer, double x, double z); typedef double (*FuncTerrainGetHeight)(Renderer* renderer, double x, double z, int with_painting);
typedef Color (*FuncTerrainGetFinalColor)(Renderer* renderer, Vector3 location, double precision); typedef Color (*FuncTerrainGetFinalColor)(Renderer* renderer, Vector3 location, double precision);
typedef struct typedef struct
@ -50,6 +50,7 @@ extern StandardRenderer TerrainRendererClass;
void terrainAutoPreset(TerrainDefinition* definition, TerrainPreset preset); void terrainAutoPreset(TerrainDefinition* definition, TerrainPreset preset);
void terrainRenderSurface(Renderer* renderer); void terrainRenderSurface(Renderer* renderer);
double terrainGetGridHeight(TerrainDefinition* definition, int x, int z, int with_painting);
Renderer terrainCreatePreviewRenderer(); Renderer terrainCreatePreviewRenderer();
Color terrainGetPreviewColor(Renderer* renderer, double x, double z, double detail); Color terrainGetPreviewColor(Renderer* renderer, double x, double z, double detail);

View file

@ -13,7 +13,7 @@ static inline Vector3 _getPoint(TerrainDefinition* definition, Renderer* rendere
Vector3 result; Vector3 result;
result.x = x; result.x = x;
result.y = renderer->terrain->getHeight(renderer, x, z); result.y = renderer->terrain->getHeight(renderer, x, z, 1);
result.z = z; result.z = z;
return result; return result;

View file

@ -178,25 +178,25 @@ static inline TextureResult _getTerrainResult(Renderer* renderer, double x, doub
center.x = x; center.x = x;
center.z = z; center.z = z;
center.y = renderer->terrain->getHeight(renderer, center.x, center.z); center.y = renderer->terrain->getHeight(renderer, center.x, center.z, 1);
east.x = x + detail; east.x = x + detail;
east.z = z; east.z = z;
east.y = renderer->terrain->getHeight(renderer, east.x, east.z); east.y = renderer->terrain->getHeight(renderer, east.x, east.z, 1);
south.x = x; south.x = x;
south.z = z + detail; south.z = z + detail;
south.y = renderer->terrain->getHeight(renderer, south.x, south.z); south.y = renderer->terrain->getHeight(renderer, south.x, south.z, 1);
if (renderer->render_quality > 5) if (renderer->render_quality > 5)
{ {
west.x = x - detail; west.x = x - detail;
west.z = z; west.z = z;
west.y = renderer->terrain->getHeight(renderer, west.x, west.z); west.y = renderer->terrain->getHeight(renderer, west.x, west.z, 1);
north.x = x; north.x = x;
north.z = z - detail; north.z = z - detail;
north.y = renderer->terrain->getHeight(renderer, north.x, north.z); north.y = renderer->terrain->getHeight(renderer, north.x, north.z, 1);
result.normal = _getNormal4(center, north, east, south, west); result.normal = _getNormal4(center, north, east, south, west);
} }