paysages3d/src/definition/TerrainHeightMap.cpp

485 lines
13 KiB
C++

#include "TerrainHeightMap.h"
#include "PackStream.h"
#include "Memory.h"
#include "TerrainDefinition.h"
#include "TerrainHeightMapBrush.h"
#include "Interpolation.h"
#include "NoiseGenerator.h"
#include <cassert>
typedef struct
{
int xstart;
int xend;
double* height;
} HeightMapPixelGroup;
typedef struct
{
int z;
int pixel_groups_count;
HeightMapPixelGroup* pixel_groups;
} HeightMapRow;
namespace paysages {
namespace definition {
class TerrainHeightMapData
{
public:
int memsize;
int rows_count;
HeightMapRow* rows;
};
}
}
static void _initData(TerrainHeightMapData* data)
{
data->rows_count = 0;
data->rows = new HeightMapRow[1];
data->memsize = 0;
}
static void _clearData(TerrainHeightMapData* data)
{
int i, j;
for (i = 0; i < data->rows_count; i++)
{
for (j = 0; j < data->rows[i].pixel_groups_count; j++)
{
free(data->rows[i].pixel_groups[j].height);
}
free(data->rows[i].pixel_groups);
}
data->rows_count = 0;
delete[] data->rows;
data->rows = new HeightMapRow[1];
data->memsize = 0;
}
static void _deleteData(TerrainHeightMapData* data)
{
_clearData(data);
delete[] data->rows;
delete data;
}
static void _copyData(TerrainHeightMapData* source, TerrainHeightMapData* destination)
{
int i, j, n;
size_t size;
_clearData(destination);
destination->rows_count = source->rows_count;
if (destination->rows_count > 0)
{
size = sizeof(HeightMapRow) * destination->rows_count;
destination->rows = (HeightMapRow*)realloc(destination->rows, size);
destination->memsize += size;
for (i = 0; i < destination->rows_count; i++)
{
destination->rows[i].z = source->rows[i].z;
destination->rows[i].pixel_groups_count = source->rows[i].pixel_groups_count;
size = sizeof(HeightMapPixelGroup) * destination->rows[i].pixel_groups_count;
destination->rows[i].pixel_groups = (HeightMapPixelGroup*)malloc(size);
destination->memsize += size;
for (j = 0; j < destination->rows[i].pixel_groups_count; j++)
{
destination->rows[i].pixel_groups[j].xstart = source->rows[i].pixel_groups[j].xstart;
destination->rows[i].pixel_groups[j].xend = source->rows[i].pixel_groups[j].xend;
n = destination->rows[i].pixel_groups[j].xend - destination->rows[i].pixel_groups[j].xstart + 1;
size = sizeof(double) * n;
destination->rows[i].pixel_groups[j].height = (double*)malloc(size);
destination->memsize += size;
memcpy(destination->rows[i].pixel_groups[j].height, source->rows[i].pixel_groups[j].height, size);
}
}
}
}
static void _saveData(PackStream* stream, TerrainHeightMapData* data)
{
int i, j, k;
stream->write(&data->rows_count);
for (i = 0; i < data->rows_count; i++)
{
stream->write(&data->rows[i].z);
stream->write(&data->rows[i].pixel_groups_count);
for (j = 0; j < data->rows[i].pixel_groups_count; j++)
{
stream->write(&data->rows[i].pixel_groups[j].xstart);
stream->write(&data->rows[i].pixel_groups[j].xend);
for (k = 0; k < data->rows[i].pixel_groups[j].xend - data->rows[i].pixel_groups[j].xstart; k++)
{
stream->write(&data->rows[i].pixel_groups[j].height[k]);
}
}
}
}
static void _loadData(PackStream* stream, TerrainHeightMapData* data)
{
int i, j, k, n;
size_t size;
_clearData(data);
stream->read(&data->rows_count);
if (data->rows_count > 0)
{
size = sizeof(HeightMapRow) * data->rows_count;
data->rows = (HeightMapRow*)realloc(data->rows, size);
data->memsize += size;
for (i = 0; i < data->rows_count; i++)
{
stream->read(&data->rows[i].z);
stream->read(&data->rows[i].pixel_groups_count);
size = sizeof(HeightMapPixelGroup) * data->rows[i].pixel_groups_count;
data->rows[i].pixel_groups = (HeightMapPixelGroup*)malloc(size);
data->memsize += size;
for (j = 0; j < data->rows[i].pixel_groups_count; j++)
{
stream->read(&data->rows[i].pixel_groups[j].xstart);
stream->read(&data->rows[i].pixel_groups[j].xend);
n = data->rows[i].pixel_groups[j].xend - data->rows[i].pixel_groups[j].xstart;
size = sizeof(double) * n;
data->rows[i].pixel_groups[j].height = (double*)malloc(size);
data->memsize += size;
for (k = 0; k < n; k++)
{
stream->read(&data->rows[i].pixel_groups[j].height[k]);
}
}
}
}
}
/*
* Get a pointer to the data in a heightmap, to a certain location.
* If the location is not already in the heightmap, it is initialized with the terrain height.
* This method will grow the heightmap as necessary (if 'grow' is set to false, NULL will be returned on missing pixels).
*/
inline double* TerrainHeightMap::getDataPointer(TerrainHeightMapData* data, int x, int z, TerrainHeightMapData* fallback, TerrainDefinition* terrain, int grow)
{
int i;
/* Find row */
/* TODO Dichotomic search */
HeightMapRow* row;
i = 0;
while (i < data->rows_count && data->rows[i].z < z)
{
i++;
}
if (i < data->rows_count && data->rows[i].z == z)
{
row = data->rows + i;
}
else if (grow)
{
row = (HeightMapRow*)Memory::naiveArrayInsert((void**)&data->rows, sizeof(HeightMapRow), data->rows_count, i);
row->z = z;
row->pixel_groups_count = 0;
row->pixel_groups = (HeightMapPixelGroup*)malloc(1);
data->rows_count++;
data->memsize += sizeof(HeightMapRow);
}
else
{
return NULL;
}
#ifndef NDEBUG
/* Check rows */
for (i = 1; i < data->rows_count; i++)
{
assert(data->rows[i].z > data->rows[i - 1].z);
}
#endif
/* Find pixel group */
HeightMapPixelGroup* pixel_group = NULL;
for (i = 0; i < row->pixel_groups_count; i++)
{
if (x < row->pixel_groups[i].xstart - 1)
{
break;
}
else if (x <= row->pixel_groups[i].xend + 1)
{
if (x == row->pixel_groups[i].xend + 1 && i < row->pixel_groups_count - 1 && x == row->pixel_groups[i + 1].xstart)
{
/* Choose next group if it already includes the pixel */
i++;
}
pixel_group = row->pixel_groups + i;
break;
}
}
/* Alter pixel group */
double* pixel;
int added = 1;
if (!pixel_group)
{
if (!grow)
{
return NULL;
}
/* Create the pixel group with one pixel */
pixel_group = (HeightMapPixelGroup*)Memory::naiveArrayInsert((void**)&row->pixel_groups, sizeof(HeightMapPixelGroup), row->pixel_groups_count, i);
pixel_group->xstart = x;
pixel_group->xend = x;
pixel_group->height = (double*)malloc(sizeof(double));
pixel = pixel_group->height;
row->pixel_groups_count++;
data->memsize += sizeof(HeightMapPixelGroup) + sizeof(double);
}
else if (x == pixel_group->xstart - 1)
{
if (!grow)
{
return NULL;
}
/* Extend the rowgroup at start */
pixel_group->xstart--;
pixel = (double*)Memory::naiveArrayInsert((void**)&pixel_group->height, sizeof(double), pixel_group->xend - pixel_group->xstart, 0);
data->memsize += sizeof(double);
}
else if (x == pixel_group->xend + 1)
{
if (!grow)
{
return NULL;
}
/* Extend the rowgroup at end */
pixel_group->xend++;
pixel = (double*)Memory::naiveArrayInsert((void**)&pixel_group->height, sizeof(double), pixel_group->xend - pixel_group->xstart, pixel_group->xend - pixel_group->xstart);
data->memsize += sizeof(double);
}
else
{
assert(x >= pixel_group->xstart);
assert(x <= pixel_group->xend);
pixel = pixel_group->height + x - pixel_group->xstart;
added = 0;
}
#ifndef NDEBUG
/* Check pixel groups */
for (i = 0; i < row->pixel_groups_count; i++)
{
if (i > 0)
{
assert(row->pixel_groups[i].xstart > row->pixel_groups[i - 1].xend);
}
if (i < row->pixel_groups_count - 1)
{
assert(row->pixel_groups[i].xend < row->pixel_groups[i + 1].xstart);
}
assert(row->pixel_groups[i].xend >= row->pixel_groups[i].xstart);
}
#endif
/* Reset pixel if it had been added */
if (added && (terrain || fallback))
{
if (fallback)
{
double* dpointer = getDataPointer(fallback, x, z, NULL, terrain, 0);
if (dpointer)
{
*pixel = *dpointer;
}
else if (terrain)
{
*pixel = terrain->getGridHeight(x, z, 0);
}
}
else if (terrain)
{
*pixel = terrain->getGridHeight(x, z, 0);
}
}
return pixel;
}
TerrainHeightMap::TerrainHeightMap(TerrainDefinition* terrain):
BaseDefinition(terrain), terrain(terrain)
{
merged_data = new TerrainHeightMapData;
brush_data = new TerrainHeightMapData;
_initData(merged_data);
_initData(brush_data);
}
TerrainHeightMap::~TerrainHeightMap()
{
_deleteData(merged_data);
_deleteData(brush_data);
}
void TerrainHeightMap::copy(BaseDefinition* _destination) const
{
TerrainHeightMap* destination = (TerrainHeightMap*)_destination;
destination->terrain = terrain;
_copyData(merged_data, destination->merged_data);
_clearData(destination->brush_data);
}
void TerrainHeightMap::save(PackStream* stream) const
{
_saveData(stream, merged_data);
}
void TerrainHeightMap::load(PackStream* stream)
{
_loadData(stream, merged_data);
_clearData(brush_data);
}
int TerrainHeightMap::getGridHeight(int x, int z, double* result)
{
double* dpointer;
dpointer = getDataPointer(brush_data, x, z, NULL, NULL, 0);
if (dpointer)
{
*result = *dpointer;
return 1;
}
else
{
dpointer = getDataPointer(merged_data, x, z, NULL, NULL, 0);
if (dpointer)
{
*result = *dpointer;
return 1;
}
else
{
return 0;
}
}
}
int TerrainHeightMap::getInterpolatedHeight(double x, double z, double* result)
{
int ix, iz;
int xlow;
int zlow;
xlow = floor(x);
zlow = floor(z);
int hit = 0;
for (ix = xlow - 1; ix <= xlow + 2 && !hit; ix++)
{
for (iz = zlow - 1; iz <= zlow + 2 && !hit; iz++)
{
if (getDataPointer(brush_data, x, z, NULL, NULL, 0) || getDataPointer(merged_data, x, z, NULL, NULL, 0))
{
hit = 1;
}
}
}
if (hit && result)
{
double stencil[16];
double value;
for (ix = xlow - 1; ix <= xlow + 2; ix++)
{
for (iz = zlow - 1; iz <= zlow + 2; iz++)
{
if (!getGridHeight(ix, iz, &value))
{
value = terrain->getGridHeight(ix, iz, 0);
}
stencil[(iz - (zlow - 1)) * 4 + ix - (xlow - 1)] = value;
}
}
*result = Interpolation::bicubic(stencil, x - (double)xlow, z - (double)zlow);
}
return hit;
}
unsigned long TerrainHeightMap::getMemoryStats() const
{
return merged_data->memsize + brush_data->memsize;
}
bool TerrainHeightMap::isPainted(int x, int z)
{
return getDataPointer(brush_data, x, z, NULL, NULL, 0) || getDataPointer(merged_data, x, z, NULL, NULL, 0);
}
void TerrainHeightMap::clearPainting()
{
_clearData(merged_data);
_clearData(brush_data);
}
void TerrainHeightMap::brushElevation(const TerrainHeightMapBrush &brush, double value)
{
TerrainHeightMapBrushElevation sbrush(brush);
sbrush.apply(this, value);
}
void TerrainHeightMap::brushFlatten(const TerrainHeightMapBrush &brush, double height, double force)
{
TerrainHeightMapBrushFlatten sbrush(brush, height);
sbrush.apply(this, force);
}
void TerrainHeightMap::brushSmooth(const TerrainHeightMapBrush &brush, double value)
{
TerrainHeightMapBrushSmooth sbrush(brush);
sbrush.apply(this, value);
}
void TerrainHeightMap::brushAddNoise(const TerrainHeightMapBrush &brush, NoiseGenerator* generator, double value)
{
TerrainHeightMapBrushAddNoise sbrush(brush, generator);
sbrush.apply(this, value);
}
void TerrainHeightMap::brushReset(const TerrainHeightMapBrush &brush, double value)
{
TerrainHeightMapBrushReset sbrush(brush);
sbrush.apply(this, value);
}
void TerrainHeightMap::endBrushStroke()
{
int i, j, k;
TerrainHeightMapData* data = brush_data;
for (i = 0; i < data->rows_count; i++)
{
for (j = 0; j < data->rows[i].pixel_groups_count; j++)
{
for (k = 0; k < data->rows[i].pixel_groups[j].xend - data->rows[i].pixel_groups[j].xstart + 1; k++)
{
double* dpointer = getDataPointer(merged_data, data->rows[i].pixel_groups[j].xstart + k, data->rows[i].z, NULL, NULL, 1);
*dpointer = data->rows[i].pixel_groups[j].height[k];
}
}
}
_clearData(brush_data);
}