minetest_nmpr/src/heightmap.cpp

438 lines
9.7 KiB
C++

/*
(c) 2010 Perttu Ahola <celeron55@gmail.com>
*/
#include "heightmap.h"
bool g_heightmap_debugprint = false;
f32 FixedHeightmap::avgNeighbours(v2s16 p, s16 d)
{
//printf("avgNeighbours((%i,%i), %i): ", p.X, p.Y, d);
v2s16 dirs[4] = {
v2s16(1,0),
v2s16(0,1),
v2s16(-1,0),
v2s16(0,-1)
};
f32 sum = 0.0;
f32 count = 0.0;
for(u16 i=0; i<4; i++){
v2s16 p2 = p + dirs[i] * d;
f32 n = getGroundHeightParent(p2);
if(n < GROUNDHEIGHT_VALID_MINVALUE)
continue;
sum += n;
count += 1.0;
//printf("(%i,%i)=%f ", p2.X, p2.Y, n);
}
//printf("\n");
assert(count > 0.001);
return sum / count;
}
f32 FixedHeightmap::avgDiagNeighbours(v2s16 p, s16 d)
{
/*printf("avgDiagNeighbours((%i,%i), %i): ",
p.X, p.Y, d);*/
v2s16 dirs[4] = {
v2s16(1,1),
v2s16(-1,-1),
v2s16(-1,1),
v2s16(1,-1)
};
f32 sum = 0.0;
f32 count = 0.0;
for(u16 i=0; i<4; i++){
v2s16 p2 = p + dirs[i] * d;
f32 n = getGroundHeightParent(p2);
if(n < GROUNDHEIGHT_VALID_MINVALUE)
continue;
sum += n;
count += 1.0;
//printf("(%i,%i)=%f ", p2.X, p2.Y, n);
}
//printf("\n");
assert(count > 0.001);
return sum / count;
}
/*
Adds a point to transform into a diamond pattern
a = 2, 4, 8, 16, ...
*/
void FixedHeightmap::makeDiamond(v2s16 center, s16 a, f32 randmax)
{
f32 n = avgDiagNeighbours(center, a/2);
// Add (-1.0...1.0) * randmax
n += ((float)rand() / (float)(RAND_MAX/2) - 1.0)*randmax;
setGroundHeightParent(center, n);
}
/*
Adds points to transform into a diamond pattern
a = 2, 4, 8, 16, ...
*/
void FixedHeightmap::makeDiamonds(s16 a, f32 randmax)
{
s16 count_y = (H-1) / a;
s16 count_x = (W-1) / a;
for(s32 yi=0; yi<count_y; yi++){
for(s32 xi=0; xi<count_x; xi++){
v2s16 center(xi*a + a/2, yi*a + a/2);
makeDiamond(center, a, randmax);
}
}
}
/*
Adds a point to transform into a square pattern
a = 2, 4, 8, 16, ...
*/
void FixedHeightmap::makeSquare(v2s16 center, s16 a, f32 randmax)
{
f32 n = avgNeighbours(center, a/2);
// Add (-1.0...1.0) * randmax
n += ((float)rand() / (float)(RAND_MAX/2) - 1.0)*randmax;
setGroundHeightParent(center, n);
}
/*
Adds points to transform into a square pattern
a = 2, 4, 8, 16, ...
*/
void FixedHeightmap::makeSquares(s16 a, f32 randmax)
{
/*
This is a bit tricky; the points have to be kind of
interlaced. (but not exactly)
On even rows (0, 2, 4, ...) the nodes are horizontally
at a*n+a/2 positions, vertically at a*n.
On odd rows (1, 3, 5, ...) the nodes are horizontally
at a*n positions, vertically at a*n+a/2.
*/
s16 count_y = (H-1) / a + 1;
for(s32 yi=0; yi<=count_y; yi++){
s16 count_x;
// Even rows
count_x = (W-1) / a;
for(s32 xi=0; xi<count_x; xi++){
v2s16 center(xi*a + a/2, yi*a);
makeSquare(center, a, randmax);
}
if(yi >= count_y - 1)
break;
// odd rows
count_x = (W-1) / a + 1;
for(s32 xi=0; xi<count_x; xi++){
v2s16 center(xi*a, yi*a + a/2);
makeSquare(center, a, randmax);
}
}
}
void FixedHeightmap::DiamondSquare(f32 randmax, f32 randfactor)
{
u16 a;
if(W < H)
a = W-1;
else
a = H-1;
// Check that a is a power of two
if((a & (a-1)) != 0)
throw;
while(a >= 2)
{
makeDiamonds(a, randmax);
if(HEIGHTMAP_DEBUGPRINT){
printf("MakeDiamonds a=%i result:\n", a);
print();
}
makeSquares(a, randmax);
if(HEIGHTMAP_DEBUGPRINT){
printf("MakeSquares a=%i result:\n", a);
print();
}
a /= 2;
randmax *= randfactor;
}
}
void UnlimitedHeightmap::print()
{
s16 minx = 10000;
s16 miny = 10000;
s16 maxx = -10000;
s16 maxy = -10000;
core::map<v2s16, FixedHeightmap*>::Iterator i;
i = m_heightmaps.getIterator();
if(i.atEnd()){
printf("UnlimitedHeightmap::print(): empty.\n");
return;
}
for(; i.atEnd() == false; i++)
{
v2s16 p = i.getNode()->getValue()->getPosOnMaster();
if(p.X < minx) minx = p.X;
if(p.Y < miny) miny = p.Y;
if(p.X > maxx) maxx = p.X;
if(p.Y > maxy) maxy = p.Y;
}
minx = minx * m_blocksize;
miny = miny * m_blocksize;
maxx = (maxx+1) * m_blocksize;
maxy = (maxy+1) * m_blocksize;
printf("UnlimitedHeightmap::print(): from (%i,%i) to (%i,%i)\n",
minx, miny, maxx, maxy);
for(s32 y=miny; y<=maxy; y++){
for(s32 x=minx; x<=maxx; x++){
f32 n = getGroundHeight(v2s16(x,y), false);
if(n < GROUNDHEIGHT_VALID_MINVALUE)
printf(" - ");
else
printf("% -5.1f ", getGroundHeight(v2s16(x,y), false));
}
printf("\n");
}
}
FixedHeightmap * UnlimitedHeightmap::getHeightmap(v2s16 p, bool generate)
{
core::map<v2s16, FixedHeightmap*>::Node *n = m_heightmaps.find(p);
if(n != NULL)
{
return n->getValue();
}
/*std::cout<<"UnlimitedHeightmap::getHeightmap(("
<<p.X<<","<<p.Y<<"), generate="
<<generate<<")"<<std::endl;*/
if(generate == false)
throw InvalidPositionException();
// If heightmap doesn't exist, generate one
FixedHeightmap *heightmap = new FixedHeightmap(
this, p, m_blocksize);
m_heightmaps.insert(p, heightmap);
f32 corners[] = {m_basevalue, m_basevalue, m_basevalue, m_basevalue};
heightmap->generateContinued(m_randmax, m_randfactor, corners);
return heightmap;
}
f32 UnlimitedHeightmap::getGroundHeight(v2s16 p, bool generate)
{
v2s16 heightmappos = getNodeHeightmapPos(p);
v2s16 relpos = p - heightmappos*m_blocksize;
try{
FixedHeightmap * href = getHeightmap(heightmappos, generate);
f32 h = href->getGroundHeight(relpos);
if(h > GROUNDHEIGHT_VALID_MINVALUE)
return h;
}
catch(InvalidPositionException){}
/*
OK, wasn't there.
Mercilessly try to get it somewhere.
*/
#if 1
if(relpos.X == 0){
try{
FixedHeightmap * href = getHeightmap(
heightmappos-v2s16(1,0), false);
f32 h = href->getGroundHeight(v2s16(m_blocksize, relpos.Y));
if(h > GROUNDHEIGHT_VALID_MINVALUE)
return h;
}
catch(InvalidPositionException){}
}
if(relpos.Y == 0){
try{
FixedHeightmap * href = getHeightmap(
heightmappos-v2s16(0,1), false);
f32 h = href->getGroundHeight(v2s16(relpos.X, m_blocksize));
if(h > GROUNDHEIGHT_VALID_MINVALUE)
return h;
}
catch(InvalidPositionException){}
}
if(relpos.X == 0 && relpos.Y == 0){
try{
FixedHeightmap * href = getHeightmap(
heightmappos-v2s16(1,1), false);
f32 h = href->getGroundHeight(v2s16(m_blocksize, m_blocksize));
if(h > GROUNDHEIGHT_VALID_MINVALUE)
return h;
}
catch(InvalidPositionException){}
}
#endif
return GROUNDHEIGHT_NOTFOUND_SETVALUE;
}
void UnlimitedHeightmap::setGroundHeight(v2s16 p, f32 y, bool generate)
{
v2s16 heightmappos = getNodeHeightmapPos(p);
v2s16 relpos = p - heightmappos*m_blocksize;
/*std::cout<<"UnlimitedHeightmap::setGroundHeight(("
<<p.X<<","<<p.Y<<"), "<<y<<"): "
<<"heightmappos=("<<heightmappos.X<<","
<<heightmappos.Y<<") relpos=("
<<relpos.X<<","<<relpos.Y<<")"
<<std::endl;*/
try{
FixedHeightmap * href = getHeightmap(heightmappos, generate);
href->setGroundHeight(relpos, y);
}catch(InvalidPositionException){}
// Update in neighbour heightmap if it's at border
if(relpos.X == 0){
try{
FixedHeightmap * href = getHeightmap(
heightmappos-v2s16(1,0), generate);
href->setGroundHeight(v2s16(m_blocksize, relpos.Y), y);
}catch(InvalidPositionException){}
}
if(relpos.Y == 0){
try{
FixedHeightmap * href = getHeightmap(
heightmappos-v2s16(0,1), generate);
href->setGroundHeight(v2s16(relpos.X, m_blocksize), y);
}catch(InvalidPositionException){}
}
if(relpos.X == m_blocksize && relpos.Y == m_blocksize){
try{
FixedHeightmap * href = getHeightmap(
heightmappos-v2s16(1,1), generate);
href->setGroundHeight(v2s16(m_blocksize, m_blocksize), y);
}catch(InvalidPositionException){}
}
}
void FixedHeightmap::generateContinued(f32 randmax, f32 randfactor,
f32 *corners)
{
if(HEIGHTMAP_DEBUGPRINT){
std::cout<<"FixedHeightmap("<<m_pos_on_master.X
<<","<<m_pos_on_master.Y
<<")::generateContinued()"<<std::endl;
}
/*
TODO: Implement changing neighboring heightmaps when needed
*/
// Works only with blocksize=2,4,8,16,32,64,...
s16 a = m_blocksize;
// Check that a is a power of two
if((a & (a-1)) != 0)
throw;
// Overwrite with GROUNDHEIGHT_NOTFOUND_SETVALUE
for(s16 y=0; y<=a; y++){
for(s16 x=0; x<=a; x++){
v2s16 p(x,y);
setGroundHeight(p, GROUNDHEIGHT_NOTFOUND_SETVALUE);
}
}
#if 1
/*
Seed borders from master heightmap
TODO: Check that this works properly
*/
struct SeedSpec
{
v2s16 neighbour_start;
v2s16 heightmap_start;
v2s16 dir;
};
SeedSpec seeds[4] =
{
{ // Z- edge on X-axis
v2s16(0, -1), // neighbour_start
v2s16(0, 0), // heightmap_start
v2s16(1, 0) // dir
},
{ // Z+ edge on X-axis
v2s16(0, m_blocksize),
v2s16(0, m_blocksize),
v2s16(1, 0)
},
{ // X- edge on Z-axis
v2s16(-1, 0),
v2s16(0, 0),
v2s16(0, 1)
},
{ // X+ edge on Z-axis
v2s16(m_blocksize, 0),
v2s16(m_blocksize, 0),
v2s16(0, 1)
},
};
for(s16 i=0; i<4; i++){
v2s16 npos = seeds[i].neighbour_start + m_pos_on_master * m_blocksize;
v2s16 hpos = seeds[i].heightmap_start;
for(s16 s=0; s<m_blocksize+1; s++){
f32 h = m_master->getGroundHeight(npos, false);
//std::cout<<"h="<<h<<std::endl;
if(h < GROUNDHEIGHT_VALID_MINVALUE)
continue;
//break;
setGroundHeight(hpos, h);
hpos += seeds[i].dir;
npos += seeds[i].dir;
}
}
if(HEIGHTMAP_DEBUGPRINT){
std::cout<<"borders seeded:"<<std::endl;
print();
}
#endif
/*
Fill with corners[] (if not already set)
*/
v2s16 dirs[4] = {
v2s16(0,0),
v2s16(1,0),
v2s16(1,1),
v2s16(0,1),
};
for(u16 i=0; i<4; i++){
v2s16 npos = dirs[i] * a;
// Don't replace already seeded corners
f32 h = getGroundHeight(npos);
if(h > GROUNDHEIGHT_VALID_MINVALUE)
continue;
setGroundHeight(dirs[i] * a, corners[i]);
}
if(HEIGHTMAP_DEBUGPRINT){
std::cout<<"corners filled:"<<std::endl;
print();
}
/*std::cout<<"Seeded heightmap:"<<std::endl;
print();*/
DiamondSquare(randmax, randfactor);
}