2012-12-10 22:05:53 +00:00
|
|
|
#include "private.h"
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Terrain height map painting.
|
|
|
|
*/
|
2012-07-13 12:23:58 +00:00
|
|
|
|
2013-01-14 13:28:42 +00:00
|
|
|
#include <assert.h>
|
2012-07-13 12:23:58 +00:00
|
|
|
#include <stdlib.h>
|
2013-01-10 15:41:14 +00:00
|
|
|
#include <string.h>
|
2013-01-10 21:21:56 +00:00
|
|
|
#include <math.h>
|
2013-01-14 13:28:42 +00:00
|
|
|
#include "../tools/memory.h"
|
2013-02-05 21:25:30 +00:00
|
|
|
#include "../tools.h"
|
2013-04-16 20:10:03 +00:00
|
|
|
#include "../tools/array.h"
|
2012-12-10 22:05:53 +00:00
|
|
|
|
2013-04-16 20:10:03 +00:00
|
|
|
typedef struct
|
2012-07-13 12:23:58 +00:00
|
|
|
{
|
2013-04-16 20:10:03 +00:00
|
|
|
int xstart;
|
|
|
|
int xend;
|
|
|
|
double* height;
|
|
|
|
} HeightMapPixelGroup;
|
2012-12-09 17:49:28 +00:00
|
|
|
|
2013-04-16 20:10:03 +00:00
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
int z;
|
|
|
|
int pixel_groups_count;
|
|
|
|
HeightMapPixelGroup* pixel_groups;
|
|
|
|
} HeightMapRow;
|
2012-12-09 17:49:28 +00:00
|
|
|
|
2013-04-16 20:10:03 +00:00
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
int memsize;
|
|
|
|
int rows_count;
|
|
|
|
HeightMapRow* rows;
|
|
|
|
} HeightMapData;
|
|
|
|
|
|
|
|
struct TerrainHeightMap
|
|
|
|
{
|
|
|
|
TerrainDefinition* terrain;
|
|
|
|
HeightMapData merged_data;
|
|
|
|
HeightMapData brush_data;
|
|
|
|
};
|
|
|
|
|
|
|
|
static void _initData(HeightMapData* data)
|
|
|
|
{
|
|
|
|
data->rows_count = 0;
|
|
|
|
data->rows = malloc(1);
|
2013-04-19 21:51:17 +00:00
|
|
|
data->memsize = 0;
|
2012-07-13 12:23:58 +00:00
|
|
|
}
|
|
|
|
|
2013-04-16 20:10:03 +00:00
|
|
|
static void _clearData(HeightMapData* data)
|
2012-07-13 12:23:58 +00:00
|
|
|
{
|
2013-04-16 20:10:03 +00:00
|
|
|
int i, j;
|
|
|
|
for (i = 0; i < data->rows_count; i++)
|
2013-01-10 15:41:14 +00:00
|
|
|
{
|
2013-04-16 20:10:03 +00:00
|
|
|
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);
|
2013-01-10 15:41:14 +00:00
|
|
|
}
|
2013-04-16 20:10:03 +00:00
|
|
|
data->rows_count = 0;
|
|
|
|
data->rows = realloc(data->rows, 1);
|
2013-04-19 21:51:17 +00:00
|
|
|
data->memsize = 0;
|
2013-01-10 15:41:14 +00:00
|
|
|
}
|
|
|
|
|
2013-04-16 20:10:03 +00:00
|
|
|
static void _deleteData(HeightMapData* data)
|
2013-01-10 15:41:14 +00:00
|
|
|
{
|
2013-04-16 20:10:03 +00:00
|
|
|
_clearData(data);
|
|
|
|
free(data->rows);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void _copyData(HeightMapData* source, HeightMapData* destination)
|
|
|
|
{
|
2013-04-19 21:51:17 +00:00
|
|
|
int i, j, n;
|
|
|
|
size_t size;
|
|
|
|
|
2013-04-16 20:10:03 +00:00
|
|
|
_clearData(destination);
|
|
|
|
|
2013-04-19 21:51:17 +00:00
|
|
|
destination->rows_count = source->rows_count;
|
|
|
|
if (destination->rows_count > 0)
|
|
|
|
{
|
|
|
|
size = sizeof(HeightMapRow) * destination->rows_count;
|
|
|
|
destination->rows = 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 = 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;
|
2013-04-20 09:32:59 +00:00
|
|
|
n = destination->rows[i].pixel_groups[j].xend - destination->rows[i].pixel_groups[j].xstart + 1;
|
2013-04-19 21:51:17 +00:00
|
|
|
size = sizeof(double) * n;
|
|
|
|
destination->rows[i].pixel_groups[j].height = malloc(size);
|
|
|
|
destination->memsize += size;
|
|
|
|
memcpy(destination->rows[i].pixel_groups[j].height, source->rows[i].pixel_groups[j].height, size);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-04-16 20:10:03 +00:00
|
|
|
}
|
2013-01-10 15:41:14 +00:00
|
|
|
|
2013-04-16 20:10:03 +00:00
|
|
|
static void _saveData(PackStream* stream, HeightMapData* data)
|
|
|
|
{
|
|
|
|
int i, j, k;
|
|
|
|
packWriteInt(stream, &data->rows_count);
|
|
|
|
for (i = 0; i < data->rows_count; i++)
|
2013-01-10 15:41:14 +00:00
|
|
|
{
|
2013-04-16 20:10:03 +00:00
|
|
|
packWriteInt(stream, &data->rows[i].z);
|
|
|
|
packWriteInt(stream, &data->rows[i].pixel_groups_count);
|
|
|
|
for (j = 0; j < data->rows[i].pixel_groups_count; j++)
|
2013-01-10 15:41:14 +00:00
|
|
|
{
|
2013-04-16 20:10:03 +00:00
|
|
|
packWriteInt(stream, &data->rows[i].pixel_groups[j].xstart);
|
|
|
|
packWriteInt(stream, &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++)
|
|
|
|
{
|
|
|
|
packWriteDouble(stream, &data->rows[i].pixel_groups[j].height[k]);
|
|
|
|
}
|
2013-01-10 15:41:14 +00:00
|
|
|
}
|
|
|
|
}
|
2013-04-16 20:10:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void _loadData(PackStream* stream, HeightMapData* data)
|
|
|
|
{
|
|
|
|
int i, j, k, n;
|
2013-04-19 21:51:17 +00:00
|
|
|
size_t size;
|
2013-04-16 20:10:03 +00:00
|
|
|
|
|
|
|
_clearData(data);
|
|
|
|
|
|
|
|
packReadInt(stream, &data->rows_count);
|
2013-04-19 21:51:17 +00:00
|
|
|
if (data->rows_count > 0)
|
2013-01-10 15:41:14 +00:00
|
|
|
{
|
2013-04-19 21:51:17 +00:00
|
|
|
size = sizeof(HeightMapRow) * data->rows_count;
|
|
|
|
data->rows = realloc(data->rows, size);
|
|
|
|
data->memsize += size;
|
|
|
|
for (i = 0; i < data->rows_count; i++)
|
2013-01-10 15:41:14 +00:00
|
|
|
{
|
2013-04-19 21:51:17 +00:00
|
|
|
packReadInt(stream, &data->rows[i].z);
|
|
|
|
packReadInt(stream, &data->rows[i].pixel_groups_count);
|
|
|
|
size = sizeof(HeightMapPixelGroup) * data->rows[i].pixel_groups_count;
|
|
|
|
data->rows[i].pixel_groups = malloc(size);
|
|
|
|
data->memsize += size;
|
|
|
|
for (j = 0; j < data->rows[i].pixel_groups_count; j++)
|
2013-04-16 20:10:03 +00:00
|
|
|
{
|
2013-04-19 21:51:17 +00:00
|
|
|
packReadInt(stream, &data->rows[i].pixel_groups[j].xstart);
|
|
|
|
packReadInt(stream, &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 = malloc(size);
|
|
|
|
data->memsize += size;
|
|
|
|
for (k = 0; k < n; k++)
|
|
|
|
{
|
|
|
|
packReadDouble(stream, &data->rows[i].pixel_groups[j].height[k]);
|
|
|
|
}
|
2013-04-16 20:10:03 +00:00
|
|
|
}
|
2013-01-10 15:41:14 +00:00
|
|
|
}
|
|
|
|
}
|
2012-07-13 12:23:58 +00:00
|
|
|
}
|
|
|
|
|
2013-04-16 20:10:03 +00:00
|
|
|
/*
|
|
|
|
* 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).
|
|
|
|
*/
|
2013-04-19 21:51:17 +00:00
|
|
|
static double* _getDataPointer(HeightMapData* data, int x, int z, HeightMapData* fallback, TerrainDefinition* terrain, int grow)
|
2012-07-13 12:23:58 +00:00
|
|
|
{
|
2013-01-10 21:21:56 +00:00
|
|
|
int i;
|
2013-01-10 15:41:14 +00:00
|
|
|
|
2013-04-16 20:10:03 +00:00
|
|
|
/* 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 = naiveArrayInsert((void**)&data->rows, sizeof(HeightMapRow), data->rows_count, i);
|
2013-01-14 13:28:42 +00:00
|
|
|
|
2013-04-16 20:10:03 +00:00
|
|
|
row->z = z;
|
|
|
|
row->pixel_groups_count = 0;
|
|
|
|
row->pixel_groups = malloc(1);
|
2013-01-10 15:41:14 +00:00
|
|
|
|
2013-04-16 20:10:03 +00:00
|
|
|
data->rows_count++;
|
2013-04-19 21:51:17 +00:00
|
|
|
data->memsize += sizeof(HeightMapRow);
|
2013-04-16 20:10:03 +00:00
|
|
|
}
|
|
|
|
else
|
2013-01-10 15:41:14 +00:00
|
|
|
{
|
2013-04-16 20:10:03 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
2013-01-14 13:28:42 +00:00
|
|
|
|
2013-04-20 09:32:59 +00:00
|
|
|
#ifndef NDEBUG
|
|
|
|
/* Check rows */
|
|
|
|
for (i = 1; i < data->rows_count; i++)
|
|
|
|
{
|
|
|
|
assert(data->rows[i].z > data->rows[i - 1].z);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2013-04-16 20:10:03 +00:00
|
|
|
/* 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;
|
|
|
|
}
|
2013-04-20 09:32:59 +00:00
|
|
|
else if (x <= row->pixel_groups[i].xend + 1)
|
2013-01-10 15:41:14 +00:00
|
|
|
{
|
2013-04-20 09:32:59 +00:00
|
|
|
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++;
|
|
|
|
}
|
2013-04-16 20:10:03 +00:00
|
|
|
pixel_group = row->pixel_groups + i;
|
2013-04-20 09:32:59 +00:00
|
|
|
break;
|
2013-01-10 15:41:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-04-16 20:10:03 +00:00
|
|
|
/* Alter pixel group */
|
|
|
|
double* pixel;
|
|
|
|
int added = 1;
|
|
|
|
if (!pixel_group)
|
2012-07-13 12:23:58 +00:00
|
|
|
{
|
2013-04-16 20:10:03 +00:00
|
|
|
if (!grow)
|
2013-01-10 15:41:14 +00:00
|
|
|
{
|
2013-04-16 20:10:03 +00:00
|
|
|
return NULL;
|
2013-01-10 15:41:14 +00:00
|
|
|
}
|
2012-07-13 12:23:58 +00:00
|
|
|
|
2013-04-16 20:10:03 +00:00
|
|
|
/* Create the pixel group with one pixel */
|
|
|
|
pixel_group = naiveArrayInsert((void**)&row->pixel_groups, sizeof(HeightMapPixelGroup), row->pixel_groups_count, i);
|
2013-01-10 15:41:14 +00:00
|
|
|
|
2013-04-16 20:10:03 +00:00
|
|
|
pixel_group->xstart = x;
|
|
|
|
pixel_group->xend = x;
|
|
|
|
pixel_group->height = malloc(sizeof(double));
|
2012-12-09 17:49:28 +00:00
|
|
|
|
2013-04-16 20:10:03 +00:00
|
|
|
pixel = pixel_group->height;
|
|
|
|
|
|
|
|
row->pixel_groups_count++;
|
2013-04-19 21:51:17 +00:00
|
|
|
data->memsize += sizeof(HeightMapPixelGroup) + sizeof(double);
|
2013-04-16 20:10:03 +00:00
|
|
|
}
|
|
|
|
else if (x == pixel_group->xstart - 1)
|
2012-07-13 12:23:58 +00:00
|
|
|
{
|
2013-04-16 20:10:03 +00:00
|
|
|
if (!grow)
|
2013-01-10 15:41:14 +00:00
|
|
|
{
|
2013-04-16 20:10:03 +00:00
|
|
|
return NULL;
|
2013-01-10 15:41:14 +00:00
|
|
|
}
|
2013-04-16 20:10:03 +00:00
|
|
|
|
|
|
|
/* Extend the rowgroup at start */
|
|
|
|
pixel_group->xstart--;
|
|
|
|
pixel = naiveArrayInsert((void**)&pixel_group->height, sizeof(double), pixel_group->xend - pixel_group->xstart, 0);
|
2013-04-19 21:51:17 +00:00
|
|
|
data->memsize += sizeof(double);
|
2013-04-16 20:10:03 +00:00
|
|
|
}
|
|
|
|
else if (x == pixel_group->xend + 1)
|
|
|
|
{
|
|
|
|
if (!grow)
|
2013-01-10 15:41:14 +00:00
|
|
|
{
|
2013-04-16 20:10:03 +00:00
|
|
|
return NULL;
|
2013-01-10 15:41:14 +00:00
|
|
|
}
|
2013-04-16 20:10:03 +00:00
|
|
|
|
|
|
|
/* Extend the rowgroup at end */
|
|
|
|
pixel_group->xend++;
|
|
|
|
pixel = naiveArrayInsert((void**)&pixel_group->height, sizeof(double), pixel_group->xend - pixel_group->xstart, pixel_group->xend - pixel_group->xstart);
|
2013-04-19 21:51:17 +00:00
|
|
|
data->memsize += sizeof(double);
|
2013-04-16 20:10:03 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
assert(x >= pixel_group->xstart);
|
|
|
|
assert(x <= pixel_group->xend);
|
|
|
|
pixel = pixel_group->height + x - pixel_group->xstart;
|
|
|
|
added = 0;
|
2013-01-10 15:41:14 +00:00
|
|
|
}
|
|
|
|
|
2013-04-20 09:32:59 +00:00
|
|
|
#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
|
|
|
|
|
2013-04-16 20:10:03 +00:00
|
|
|
/* Reset pixel if it had been added */
|
2013-04-19 21:51:17 +00:00
|
|
|
if (added && (terrain || fallback))
|
2013-04-16 20:10:03 +00:00
|
|
|
{
|
2013-04-19 21:51:17 +00:00
|
|
|
if (fallback)
|
|
|
|
{
|
|
|
|
double* dpointer = _getDataPointer(fallback, x, z, NULL, terrain, 0);
|
|
|
|
if (dpointer)
|
|
|
|
{
|
|
|
|
*pixel = *dpointer;
|
|
|
|
}
|
2013-04-20 09:32:59 +00:00
|
|
|
else if (terrain)
|
2013-04-19 21:51:17 +00:00
|
|
|
{
|
2013-06-09 21:03:16 +00:00
|
|
|
*pixel = terrainGetGridHeight(terrain, x, z, 0);
|
2013-04-19 21:51:17 +00:00
|
|
|
}
|
|
|
|
}
|
2013-04-20 09:32:59 +00:00
|
|
|
else if (terrain)
|
2013-04-19 21:51:17 +00:00
|
|
|
{
|
2013-06-09 21:03:16 +00:00
|
|
|
*pixel = terrainGetGridHeight(terrain, x, z, 0);
|
2013-04-19 21:51:17 +00:00
|
|
|
}
|
2013-04-16 20:10:03 +00:00
|
|
|
}
|
|
|
|
return pixel;
|
|
|
|
}
|
|
|
|
|
|
|
|
TerrainHeightMap* terrainHeightMapCreate(TerrainDefinition* terrain)
|
|
|
|
{
|
|
|
|
TerrainHeightMap* result;
|
|
|
|
|
|
|
|
result = malloc(sizeof(TerrainHeightMap));
|
|
|
|
result->terrain = terrain;
|
|
|
|
_initData(&result->merged_data);
|
|
|
|
_initData(&result->brush_data);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void terrainHeightmapDelete(TerrainHeightMap* heightmap)
|
|
|
|
{
|
|
|
|
_deleteData(&heightmap->merged_data);
|
|
|
|
_deleteData(&heightmap->brush_data);
|
|
|
|
free(heightmap);
|
|
|
|
}
|
|
|
|
|
|
|
|
void terrainHeightmapCopy(TerrainHeightMap* source, TerrainHeightMap* destination)
|
|
|
|
{
|
|
|
|
destination->terrain = source->terrain;
|
|
|
|
|
|
|
|
_copyData(&source->merged_data, &destination->merged_data);
|
|
|
|
_clearData(&destination->brush_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
void terrainHeightmapSave(PackStream* stream, TerrainHeightMap* heightmap)
|
|
|
|
{
|
|
|
|
_saveData(stream, &heightmap->merged_data);
|
2013-01-10 15:41:14 +00:00
|
|
|
}
|
|
|
|
|
2013-04-16 20:10:03 +00:00
|
|
|
void terrainHeightmapLoad(PackStream* stream, TerrainHeightMap* heightmap)
|
2013-01-14 12:08:38 +00:00
|
|
|
{
|
2013-04-16 20:10:03 +00:00
|
|
|
_loadData(stream, &heightmap->merged_data);
|
|
|
|
_clearData(&heightmap->brush_data);
|
|
|
|
}
|
|
|
|
|
2013-04-19 21:51:17 +00:00
|
|
|
int terrainHeightmapGetGridHeight(TerrainHeightMap* heightmap, int x, int z, double* result)
|
2013-04-16 20:10:03 +00:00
|
|
|
{
|
2013-04-19 21:51:17 +00:00
|
|
|
double* dpointer;
|
|
|
|
dpointer = _getDataPointer(&heightmap->brush_data, x, z, NULL, NULL, 0);
|
|
|
|
if (dpointer)
|
2013-01-14 12:08:38 +00:00
|
|
|
{
|
2013-04-19 21:51:17 +00:00
|
|
|
*result = *dpointer;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
dpointer = _getDataPointer(&heightmap->merged_data, x, z, NULL, NULL, 0);
|
|
|
|
if (dpointer)
|
|
|
|
{
|
|
|
|
*result = *dpointer;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-02-05 21:25:30 +00:00
|
|
|
|
2013-04-19 21:51:17 +00:00
|
|
|
int terrainHeightmapGetInterpolatedHeight(TerrainHeightMap* heightmap, double x, double z, double* result)
|
|
|
|
{
|
|
|
|
int ix, iz;
|
|
|
|
int xlow;
|
|
|
|
int zlow;
|
2013-02-05 21:25:30 +00:00
|
|
|
|
2013-04-19 21:51:17 +00:00
|
|
|
xlow = floor(x);
|
|
|
|
zlow = floor(z);
|
2013-02-05 21:25:30 +00:00
|
|
|
|
2013-04-19 21:51:17 +00:00
|
|
|
int hit = 0;
|
|
|
|
for (ix = xlow - 1; ix <= xlow + 2 && !hit; ix++)
|
|
|
|
{
|
|
|
|
for (iz = zlow - 1; iz <= zlow + 2 && !hit; iz++)
|
2013-02-05 21:25:30 +00:00
|
|
|
{
|
2013-04-19 21:51:17 +00:00
|
|
|
if (_getDataPointer(&heightmap->brush_data, x, z, NULL, NULL, 0) || _getDataPointer(&heightmap->merged_data, x, z, NULL, NULL, 0))
|
2013-02-05 21:25:30 +00:00
|
|
|
{
|
2013-04-19 21:51:17 +00:00
|
|
|
hit = 1;
|
2013-02-05 21:25:30 +00:00
|
|
|
}
|
|
|
|
}
|
2013-04-19 21:51:17 +00:00
|
|
|
}
|
2013-02-05 21:25:30 +00:00
|
|
|
|
2013-04-19 21:51:17 +00:00
|
|
|
if (hit && result)
|
|
|
|
{
|
|
|
|
double stencil[16];
|
|
|
|
double value;
|
|
|
|
for (ix = xlow - 1; ix <= xlow + 2; ix++)
|
2013-04-16 20:10:03 +00:00
|
|
|
{
|
2013-04-19 21:51:17 +00:00
|
|
|
for (iz = zlow - 1; iz <= zlow + 2; iz++)
|
|
|
|
{
|
|
|
|
if (!terrainHeightmapGetGridHeight(heightmap, ix, iz, &value))
|
|
|
|
{
|
2013-06-09 21:03:16 +00:00
|
|
|
value = terrainGetGridHeight(heightmap->terrain, ix, iz, 0);
|
2013-04-19 21:51:17 +00:00
|
|
|
}
|
|
|
|
stencil[(iz - (zlow - 1)) * 4 + ix - (xlow - 1)] = value;
|
|
|
|
}
|
2013-04-16 20:10:03 +00:00
|
|
|
}
|
2013-02-05 21:25:30 +00:00
|
|
|
|
2013-04-19 21:51:17 +00:00
|
|
|
*result = toolsBicubicInterpolate(stencil, x - (double)xlow, z - (double)zlow);
|
2013-01-14 12:08:38 +00:00
|
|
|
}
|
|
|
|
|
2013-04-19 21:51:17 +00:00
|
|
|
return hit;
|
2013-01-14 13:28:42 +00:00
|
|
|
}
|
|
|
|
|
2013-02-07 16:06:26 +00:00
|
|
|
static inline IntegerRect _getBrushRect(TerrainBrush* brush)
|
2013-01-10 21:21:56 +00:00
|
|
|
{
|
2013-02-07 16:06:26 +00:00
|
|
|
IntegerRect result;
|
2013-01-10 21:21:56 +00:00
|
|
|
double s = brush->smoothed_size + brush->hard_radius;
|
2013-02-07 16:06:26 +00:00
|
|
|
|
|
|
|
result.xstart = (int)floor(brush->relative_x - s);
|
|
|
|
result.xend = (int)ceil(brush->relative_x + s);
|
|
|
|
result.zstart = (int)floor(brush->relative_z - s);
|
|
|
|
result.zend = (int)ceil(brush->relative_z + s);
|
|
|
|
|
|
|
|
result.xsize = result.xend - result.xstart + 1;
|
|
|
|
result.zsize = result.zend - result.zstart + 1;
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int _isInRect(IntegerRect rect, int x, int z)
|
|
|
|
{
|
|
|
|
return (x >= rect.xstart && x <= rect.xend && z >= rect.zstart && z <= rect.zend);
|
|
|
|
}
|
|
|
|
|
2013-02-03 21:18:32 +00:00
|
|
|
size_t terrainGetMemoryStats(TerrainDefinition* definition)
|
|
|
|
{
|
2013-04-19 21:51:17 +00:00
|
|
|
return definition->height_map->merged_data.memsize + definition->height_map->brush_data.memsize;
|
2013-02-03 21:18:32 +00:00
|
|
|
}
|
|
|
|
|
2013-02-07 16:06:26 +00:00
|
|
|
int terrainIsPainted(TerrainHeightMap* heightmap, int x, int z)
|
|
|
|
{
|
2013-04-19 21:51:17 +00:00
|
|
|
return _getDataPointer(&heightmap->brush_data, x, z, NULL, NULL, 0) || _getDataPointer(&heightmap->merged_data, x, z, NULL, NULL, 0);
|
2013-02-07 16:06:26 +00:00
|
|
|
}
|
|
|
|
|
2013-06-09 17:36:30 +00:00
|
|
|
void terrainClearPainting(TerrainHeightMap* heightmap)
|
|
|
|
{
|
|
|
|
_clearData(&heightmap->merged_data);
|
|
|
|
_clearData(&heightmap->brush_data);
|
|
|
|
}
|
|
|
|
|
2013-02-07 16:06:26 +00:00
|
|
|
typedef double (*BrushCallback)(TerrainHeightMap* heightmap, TerrainBrush* brush, double x, double z, double basevalue, double influence, double force, void* data);
|
|
|
|
|
|
|
|
static inline void _applyBrush(TerrainHeightMap* heightmap, TerrainBrush* brush, double force, void* data, BrushCallback callback)
|
|
|
|
{
|
|
|
|
IntegerRect brush_rect = _getBrushRect(brush);
|
|
|
|
int x, z;
|
|
|
|
double dx, dz, distance, influence;
|
|
|
|
|
2013-06-09 21:03:16 +00:00
|
|
|
force /= heightmap->terrain->height;
|
2013-05-11 21:34:30 +00:00
|
|
|
|
2013-02-07 16:06:26 +00:00
|
|
|
for (x = brush_rect.xstart; x <= brush_rect.xend; x++)
|
|
|
|
{
|
|
|
|
dx = (double)x;
|
|
|
|
for (z = brush_rect.zstart; z <= brush_rect.zend; z++)
|
|
|
|
{
|
2013-04-16 20:10:03 +00:00
|
|
|
dz = (double)z;
|
|
|
|
distance = sqrt((brush->relative_x - dx) * (brush->relative_x - dx) + (brush->relative_z - dz) * (brush->relative_z - dz));
|
2013-02-07 16:06:26 +00:00
|
|
|
|
2013-04-16 20:10:03 +00:00
|
|
|
if (distance > brush->hard_radius)
|
|
|
|
{
|
|
|
|
if (distance <= brush->hard_radius + brush->smoothed_size)
|
2013-02-07 16:06:26 +00:00
|
|
|
{
|
2013-04-16 20:10:03 +00:00
|
|
|
influence = (1.0 - (distance - brush->hard_radius) / brush->smoothed_size);
|
2013-02-07 16:06:26 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2013-04-16 20:10:03 +00:00
|
|
|
continue;
|
2013-02-07 16:06:26 +00:00
|
|
|
}
|
|
|
|
}
|
2013-04-16 20:10:03 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
influence = 1.0;
|
|
|
|
}
|
|
|
|
|
2013-04-19 21:51:17 +00:00
|
|
|
double* dpointer = _getDataPointer(&heightmap->brush_data, x, z, &heightmap->merged_data, heightmap->terrain, 1);
|
2013-04-16 20:10:03 +00:00
|
|
|
*dpointer = callback(heightmap, brush, dx, dz, *dpointer, influence, force, data);
|
2013-02-07 16:06:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static double _applyBrushElevation(TerrainHeightMap* heightmap, TerrainBrush* brush, double x, double z, double basevalue, double influence, double force, void* data)
|
|
|
|
{
|
|
|
|
UNUSED(heightmap);
|
2013-04-16 20:10:03 +00:00
|
|
|
UNUSED(brush);
|
2013-02-07 16:06:26 +00:00
|
|
|
UNUSED(data);
|
2013-04-16 20:10:03 +00:00
|
|
|
UNUSED(x);
|
|
|
|
UNUSED(z);
|
|
|
|
|
2013-02-07 16:06:26 +00:00
|
|
|
return basevalue + influence * force;
|
|
|
|
}
|
|
|
|
|
2013-01-10 15:41:14 +00:00
|
|
|
void terrainBrushElevation(TerrainHeightMap* heightmap, TerrainBrush* brush, double value)
|
|
|
|
{
|
2013-02-07 16:06:26 +00:00
|
|
|
_applyBrush(heightmap, brush, value, NULL, _applyBrushElevation);
|
|
|
|
}
|
|
|
|
|
2013-05-05 16:42:55 +00:00
|
|
|
static double _applyBrushFlatten(TerrainHeightMap* heightmap, TerrainBrush* brush, double x, double z, double basevalue, double influence, double force, void* data)
|
|
|
|
{
|
|
|
|
UNUSED(heightmap);
|
|
|
|
UNUSED(brush);
|
|
|
|
UNUSED(data);
|
|
|
|
|
|
|
|
double ideal = *((double*)data);
|
|
|
|
return basevalue + (ideal - basevalue) * influence * force;
|
|
|
|
}
|
|
|
|
|
|
|
|
void terrainBrushFlatten(TerrainHeightMap* heightmap, TerrainBrush* brush, double height, double force)
|
|
|
|
{
|
|
|
|
_applyBrush(heightmap, brush, force, &height, _applyBrushFlatten);
|
|
|
|
}
|
|
|
|
|
2013-02-07 16:06:26 +00:00
|
|
|
static double _applyBrushSmooth(TerrainHeightMap* heightmap, TerrainBrush* brush, double x, double z, double basevalue, double influence, double force, void* data)
|
|
|
|
{
|
2013-04-16 20:10:03 +00:00
|
|
|
UNUSED(data);
|
|
|
|
|
2013-02-07 16:06:26 +00:00
|
|
|
double ideal, factor;
|
2013-06-09 21:03:16 +00:00
|
|
|
ideal = terrainGetInterpolatedHeight(heightmap->terrain, x + brush->total_radius * 0.5, z, 0, 1);
|
|
|
|
ideal += terrainGetInterpolatedHeight(heightmap->terrain, x - brush->total_radius * 0.5, z, 0, 1);
|
|
|
|
ideal += terrainGetInterpolatedHeight(heightmap->terrain, x, z - brush->total_radius * 0.5, 0, 1);
|
|
|
|
ideal += terrainGetInterpolatedHeight(heightmap->terrain, x, z + brush->total_radius * 0.5, 0, 1);
|
2013-02-07 16:06:26 +00:00
|
|
|
ideal /= 4.0;
|
|
|
|
factor = influence * force;
|
|
|
|
if (factor > 1.0)
|
|
|
|
{
|
|
|
|
factor = 0.0;
|
|
|
|
}
|
|
|
|
return basevalue + (ideal - basevalue) * factor;
|
2012-12-10 22:05:53 +00:00
|
|
|
}
|
|
|
|
|
2013-01-10 15:41:14 +00:00
|
|
|
void terrainBrushSmooth(TerrainHeightMap* heightmap, TerrainBrush* brush, double value)
|
2012-12-10 22:05:53 +00:00
|
|
|
{
|
2013-02-07 16:06:26 +00:00
|
|
|
_applyBrush(heightmap, brush, value, NULL, _applyBrushSmooth);
|
|
|
|
}
|
|
|
|
|
|
|
|
static double _applyBrushAddNoise(TerrainHeightMap* heightmap, TerrainBrush* brush, double x, double z, double basevalue, double influence, double force, void* data)
|
|
|
|
{
|
|
|
|
UNUSED(heightmap);
|
2013-04-16 20:10:03 +00:00
|
|
|
|
2013-02-07 16:06:26 +00:00
|
|
|
return basevalue + noiseGet2DTotal((NoiseGenerator*)data, x / brush->total_radius, z / brush->total_radius) * influence * force * brush->total_radius;
|
2012-12-10 22:05:53 +00:00
|
|
|
}
|
|
|
|
|
2013-01-10 15:41:14 +00:00
|
|
|
void terrainBrushAddNoise(TerrainHeightMap* heightmap, TerrainBrush* brush, NoiseGenerator* generator, double value)
|
2012-12-10 22:05:53 +00:00
|
|
|
{
|
2013-02-07 16:06:26 +00:00
|
|
|
_applyBrush(heightmap, brush, value, generator, _applyBrushAddNoise);
|
|
|
|
}
|
|
|
|
|
|
|
|
static double _applyBrushReset(TerrainHeightMap* heightmap, TerrainBrush* brush, double x, double z, double basevalue, double influence, double force, void* data)
|
|
|
|
{
|
|
|
|
UNUSED(brush);
|
|
|
|
UNUSED(data);
|
|
|
|
|
2013-06-16 14:03:17 +00:00
|
|
|
double ideal = terrainGetInterpolatedHeight(heightmap->terrain, x * heightmap->terrain->scaling, z * heightmap->terrain->scaling, 0, 0);
|
2013-02-07 16:06:26 +00:00
|
|
|
return basevalue + (ideal - basevalue) * influence * force;
|
2012-12-10 22:05:53 +00:00
|
|
|
}
|
|
|
|
|
2013-01-10 15:41:14 +00:00
|
|
|
void terrainBrushReset(TerrainHeightMap* heightmap, TerrainBrush* brush, double value)
|
2012-12-10 22:05:53 +00:00
|
|
|
{
|
2013-02-07 16:06:26 +00:00
|
|
|
_applyBrush(heightmap, brush, value, NULL, _applyBrushReset);
|
2012-07-13 12:23:58 +00:00
|
|
|
}
|
2012-07-13 21:24:19 +00:00
|
|
|
|
2013-02-03 21:18:32 +00:00
|
|
|
void terrainEndBrushStroke(TerrainHeightMap* heightmap)
|
|
|
|
{
|
2013-04-19 21:51:17 +00:00
|
|
|
int i, j, k;
|
|
|
|
HeightMapData* data = &heightmap->brush_data;
|
|
|
|
|
|
|
|
for (i = 0; i < data->rows_count; i++)
|
|
|
|
{
|
|
|
|
for (j = 0; j < data->rows[i].pixel_groups_count; j++)
|
|
|
|
{
|
2013-04-20 09:32:59 +00:00
|
|
|
for (k = 0; k < data->rows[i].pixel_groups[j].xend - data->rows[i].pixel_groups[j].xstart + 1; k++)
|
2013-04-19 21:51:17 +00:00
|
|
|
{
|
|
|
|
double* dpointer = _getDataPointer(&heightmap->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(&heightmap->brush_data);
|
2013-02-03 21:18:32 +00:00
|
|
|
}
|