Overhauled T-Junction and border-decision math, added border stitching

This commit is contained in:
rdb 2009-04-11 13:37:24 +00:00
parent 92927fd25f
commit d5af6f1cce
3 changed files with 170 additions and 126 deletions

View File

@ -1,6 +1,5 @@
// Filename: geoMipTerrain.I
// Created by: pro-rsoft (29jun07)
// Last updated by: pro-rsoft (30oct08)
//
////////////////////////////////////////////////////////////////////
//
@ -40,6 +39,7 @@ GeoMipTerrain(const string &name) {
_focal_is_temporary = true;
_is_dirty = true;
_bruteforce = false;
_stitching = false;
}
////////////////////////////////////////////////////////////////////
@ -420,45 +420,6 @@ lod_decide(unsigned short mx, unsigned short my) {
// If the heightmap is not a power of two plus one,
// it is scaled up using a gaussian filter.
////////////////////////////////////////////////////////////////////
INLINE bool GeoMipTerrain::
set_heightfield(const Filename &filename, PNMFileType *ftype) {
// First, we need to load the header to determine the size and format.
PNMImageHeader imgheader;
if (imgheader.read_header(filename, ftype)) {
// Copy over the header to the heightfield image.
_heightfield.copy_header_from(imgheader);
if(!is_power_of_two(imgheader.get_x_size() - 1) || !is_power_of_two(imgheader.get_y_size() - 1)) {
// Calculate the nearest power-of-two-plus-one size.
unsigned int reqx, reqy;
reqx = max(3, (int) pow(2.0, ceil(log((double) max(2, imgheader.get_x_size() - 1)) / log(2.0))) + 1);
reqy = max(3, (int) pow(2.0, ceil(log((double) max(2, imgheader.get_y_size() - 1)) / log(2.0))) + 1);
// If it's not a valid size, tell PNMImage to resize it.
if (reqx != imgheader.get_x_size() || reqy != imgheader.get_y_size()) {
grutil_cat.warning() << "Rescaling heightfield image " << filename << " to "
<< reqx << " by " << reqy << " pixels\n";
_heightfield.set_read_size(reqx, reqy);
}
}
// Read the real image now
if (!_heightfield.read(filename, ftype)) {
_heightfield.clear_read_size();
grutil_cat.error() << "Failed to read heightfield image " << filename << "!\n";
return false;
}
_is_dirty = true;
_xsize = _heightfield.get_x_size();
_ysize = _heightfield.get_y_size();
return true;
} else {
grutil_cat.error() << "Failed to load heightfield image " << filename << "!\n";
}
return false;
}
INLINE bool GeoMipTerrain::
set_heightfield(const PNMImage &image) {
// Before we apply anything, validate the size.
@ -541,6 +502,37 @@ clear_color_map() {
}
}
////////////////////////////////////////////////////////////////////
// Function: GeoMipTerrain::set_border_stitching
// Access: Published
// Description: If this value is true, the LOD level at the
// borders of the terrain will be 0. This is useful
// if you have multiple terrains attached and you
// want to stitch them together, to fix seams.
// This setting also has effect when bruteforce is
// enabled, although in that case you are probably
// better off with setting the minlevels to the same
// value.
////////////////////////////////////////////////////////////////////
INLINE void GeoMipTerrain::
set_border_stitching(bool stitching) {
if (stitching && !_stitching) {
_is_dirty = true;
}
_stitching = stitching;
}
////////////////////////////////////////////////////////////////////
// Function: GeoMipTerrain::get_stitching
// Access: Published
// Description: Returns the current stitching setting. False by
// default, unless set_stitching has been set.
////////////////////////////////////////////////////////////////////
INLINE bool GeoMipTerrain::
get_border_stitching() {
return _stitching;
}
////////////////////////////////////////////////////////////////////
// Function: GeoMipTerrain::get_pixel_value
// Access: Private
@ -587,33 +579,6 @@ get_normal(unsigned short mx, unsigned short my, int x, int y) {
(my * _block_size + y));
}
////////////////////////////////////////////////////////////////////
// Function: GeoMipTerrain::int2str
// Access: Private
// Description: Converts the given int to a std::string.
////////////////////////////////////////////////////////////////////
INLINE std::string GeoMipTerrain::
int_to_str(int i) {
std::stringstream ss;
std::string str;
ss << i;
ss >> str;
return str;
}
////////////////////////////////////////////////////////////////////
// Function: GeoMipTerrain::str2int
// Access: Private
// Description: Converts the given std::string to an int.
////////////////////////////////////////////////////////////////////
INLINE int GeoMipTerrain::
str_to_int(std::string str) {
std::istringstream strin(str);
int i;
strin >> i;
return i;
}
////////////////////////////////////////////////////////////////////
// Function: GeoMipTerrain::is_power_of_two
// Access: Private

View File

@ -1,6 +1,5 @@
// Filename: geoMipTerrain.cxx
// Created by: pro-rsoft (29jun07)
// Last updated by: pro-rsoft (17jan09)
//
////////////////////////////////////////////////////////////////////
//
@ -82,17 +81,24 @@ generate_block(unsigned short mx,
if (_bruteforce) {
// LOD Level when rendering bruteforce is always 0 (no lod)
// Unless a minlevel is set- this is handled later.
level = 0;
}
// Do some calculations with the level
level = min(short(max(_min_level, level)), short(_max_level));
level = min(max(_min_level, level), _max_level);
unsigned short reallevel = level;
level = int(pow(2.0, int(level)));
// Stores the max and min heights.
float minh = 1.0f;
float maxh = 0.0f;
// Neighbor levels and junctions
unsigned short lnlevel = get_neighbor_level(mx, my, -1, 0);
unsigned short rnlevel = get_neighbor_level(mx, my, 1, 0);
unsigned short bnlevel = get_neighbor_level(mx, my, 0, -1);
unsigned short tnlevel = get_neighbor_level(mx, my, 0, 1);
bool ljunction = (lnlevel != reallevel);
bool rjunction = (rnlevel != reallevel);
bool bjunction = (bnlevel != reallevel);
bool tjunction = (tnlevel != reallevel);
// Confusing note:
// the variable level contains not the actual level as described
@ -101,49 +107,43 @@ generate_block(unsigned short mx,
// This is the number of vertices at the certain level.
unsigned short lowblocksize = _block_size / level + 1;
srand(time(NULL));
for (int x = 0; x <= _block_size; x++) {
for (int y = 0; y <= _block_size; y++) {
if ((x % level) == 0 && (y % level) == 0) {
if (_has_color_map) {
LVecBase4d color = _color_map.get_xel_a(int((mx * _block_size + x)
/ double(_xsize) * _color_map.get_x_size()),
int((my * _block_size + y)
/ double(_ysize) * _color_map.get_y_size()));
cwriter.add_data4f(color.get_x(), color.get_y(),
color.get_z(), color.get_w());
//LVecBase4d color = _color_map.get_xel_a(int((mx * _block_size + x)
/// double(_xsize) * _color_map.get_x_size()),
// int((my * _block_size + y)
// / double(_ysize) * _color_map.get_y_size()));
//cwriter.add_data4f(LCAST(float, color));
cwriter.add_data4f(rand() / float(RAND_MAX), rand() / float(RAND_MAX), rand() / float(RAND_MAX), 1);
}
float pval = get_pixel_value(mx, my, x, y);
if (pval < minh) minh = pval;
if (pval > maxh) maxh = pval;
vwriter.add_data3f(x - 0.5 * _block_size, y - 0.5 * _block_size, pval);
twriter.add_data2f((mx * _block_size + x) / double(_xsize - 1),
(my * _block_size + y) / double(_ysize - 1));
nwriter.add_data3f(get_normal(mx, my, x, y));
if (x > 0 && y > 0) {
//left border
if (!_bruteforce && x == level && mx > 0 && _levels[mx - 1][my] > reallevel) {
// Left border
if (x == level && ljunction) {
if (y > level && y < _block_size) {
prim->add_vertex(min(max(sfav(y / level, _levels[mx - 1][my], reallevel), 0), lowblocksize - 1));
prim->add_vertex(min(max(sfav(y / level, lnlevel, reallevel), 0), lowblocksize - 1));
prim->add_vertex(vcounter - 1);
prim->add_vertex(vcounter);
prim->close_primitive();
}
if (f_part((y / level) / float(pow(2.0, int(_levels[mx - 1][my] - reallevel)))) == 0.5) {
prim->add_vertex(min(max(sfav(y / level + 1, _levels[mx - 1][my], reallevel), 0), lowblocksize - 1));
prim->add_vertex(min(max(sfav(y / level - 1, _levels[mx - 1][my], reallevel), 0), lowblocksize - 1));
if (f_part((y / level) / float(pow(2.0, int(lnlevel - reallevel)))) == 0.5) {
prim->add_vertex(min(max(sfav(y / level + 1, lnlevel, reallevel), 0), lowblocksize - 1));
prim->add_vertex(min(max(sfav(y / level - 1, lnlevel, reallevel), 0), lowblocksize - 1));
prim->add_vertex(vcounter);
prim->close_primitive();
}
} else if (_bruteforce ||
(!(y == level && x > level && x < _block_size && my > 0
&& _levels[mx][my - 1] > reallevel) &&
!(x == _block_size && mx < (_xsize - 1) / (_block_size) - 1
&& _levels[mx + 1][my] > reallevel) &&
!(x == _block_size && y > level && y < _block_size && mx < (_xsize - 1) / (_block_size) - 1
&& _levels[mx + 1][my] > reallevel) &&
!(y == _block_size && x > level && x < _block_size && my < (_ysize - 1) / (_block_size) - 1
&& _levels[mx][my + 1] > reallevel))) {
} else if (
(!(bjunction && y == level && x > level && x < _block_size) &&
!(rjunction && x == _block_size) &&
!(tjunction && y == _block_size && x > level && x < _block_size))) {
if ((x <= center && y <= center) || (x > center && y > center)) {
if (x > center) {
prim->add_vertex(vcounter - lowblocksize - 1);
@ -167,36 +167,39 @@ generate_block(unsigned short mx,
}
prim->close_primitive();
}
//right border
if (!_bruteforce && x == _block_size - level && mx < (_xsize - 1) / (_block_size) - 1 && _levels[mx + 1][my] > reallevel) {
if (y > level && y < _block_size - level + 1) {
prim->add_vertex(lowblocksize * (lowblocksize - 1) + min(max(sfav(y / level, _levels[mx + 1][my], reallevel), 0), lowblocksize - 1));
// Right border
if (x == _block_size - level && rjunction) {
if (y > level && y < _block_size) {
prim->add_vertex(lowblocksize * (lowblocksize - 1) + min(max(sfav(y / level, rnlevel, reallevel), 0), lowblocksize - 1));
prim->add_vertex(vcounter);
prim->add_vertex(vcounter - 1);
prim->close_primitive();
}
if (f_part((y / level)/float(pow(2.0, int(_levels[mx + 1][my]-reallevel)))) == 0.5) {
prim->add_vertex(lowblocksize * (lowblocksize - 1) + min(max(sfav(y / level - 1, _levels[mx + 1][my], reallevel), 0), lowblocksize - 1));
prim->add_vertex(lowblocksize * (lowblocksize - 1) + min(max(sfav(y / level + 1, _levels[mx + 1][my], reallevel), 0), lowblocksize - 1));
if (f_part((y / level) / float(pow(2.0, int(rnlevel - reallevel)))) == 0.5) {
prim->add_vertex(lowblocksize * (lowblocksize - 1) + min(max(sfav(y / level - 1, rnlevel, reallevel), 0), lowblocksize - 1));
prim->add_vertex(lowblocksize * (lowblocksize - 1) + min(max(sfav(y / level + 1, rnlevel, reallevel), 0), lowblocksize - 1));
prim->add_vertex(vcounter);
prim->close_primitive();
}
}
//bottom border
if (!_bruteforce && y == level && my > 0 && _levels[mx][my - 1] > reallevel) {
// Bottom border
if (y == level && bjunction) {
if (x > level && x < _block_size) {
prim->add_vertex(vcounter);
prim->add_vertex(vcounter - lowblocksize);
prim->add_vertex(min(max(sfav(x / level, _levels[mx][my - 1], reallevel), 0), lowblocksize - 1) * lowblocksize);
prim->add_vertex(min(max(sfav(x / level, bnlevel, reallevel), 0), lowblocksize - 1) * lowblocksize);
prim->close_primitive();
}
if (f_part((x / level)/float(pow(2.0, int(_levels[mx][my - 1]-reallevel)))) == 0.5) {
prim->add_vertex(min(max(sfav(x / level - 1, _levels[mx][my - 1], reallevel), 0), lowblocksize - 1) * lowblocksize);
prim->add_vertex(min(max(sfav(x / level + 1, _levels[mx][my - 1], reallevel), 0), lowblocksize - 1) * lowblocksize);
if (f_part((x / level) / float(pow(2.0, int(bnlevel - reallevel)))) == 0.5) {
prim->add_vertex(min(max(sfav(x / level - 1, bnlevel, reallevel), 0), lowblocksize - 1) * lowblocksize);
prim->add_vertex(min(max(sfav(x / level + 1, bnlevel, reallevel), 0), lowblocksize - 1) * lowblocksize);
prim->add_vertex(vcounter);
prim->close_primitive();
}
} else if (_bruteforce || (!(x == level && y > level && y < _block_size && mx > 0 && _levels[mx - 1][my] > reallevel) && !(x == _block_size && y > level && y < _block_size && mx < (_xsize - 1) / (_block_size) - 1 && _levels[mx + 1][my] > reallevel) && !(x == _block_size && y > level && y < _block_size && mx < (_xsize - 1) / (_block_size) - 1 && _levels[mx + 1][my] > reallevel) && !(y == _block_size && my < (_ysize - 1) / (_block_size) - 1 && _levels[mx][my + 1] > reallevel))) {
} else if (
(!(ljunction && x == level && y > level && y < _block_size) &&
!(tjunction && y == _block_size) &&
!(rjunction && x == _block_size && y > level && y < _block_size))) {
if ((x <= center && y <= center) || (x > center && y > center)) {
if (y > center) {
prim->add_vertex(vcounter);
@ -220,17 +223,17 @@ generate_block(unsigned short mx,
}
prim->close_primitive();
}
//top border
if (!_bruteforce && y == _block_size - level && my < (_xsize - 1) / (_block_size) - 1 && _levels[mx][my + 1] > reallevel) {
if (x > level && x < _block_size - level + 1) {
prim->add_vertex(min(max(sfav(x / level, _levels[mx][my + 1], reallevel), 0), lowblocksize - 1) * lowblocksize + lowblocksize - 1);
// Top border
if (y == _block_size - level && tjunction) {
if (x > level && x < _block_size) {
prim->add_vertex(min(max(sfav(x / level, tnlevel, reallevel), 0), lowblocksize - 1) * lowblocksize + lowblocksize - 1);
prim->add_vertex(vcounter - lowblocksize);
prim->add_vertex(vcounter);
prim->close_primitive();
}
if (f_part((x / level)/float(pow(2.0, int(_levels[mx][my + 1]-reallevel)))) == 0.5) {
prim->add_vertex(min(max(sfav(x / level + 1, _levels[mx][my + 1], reallevel), 0), lowblocksize - 1) * lowblocksize + lowblocksize - 1);
prim->add_vertex(min(max(sfav(x / level - 1, _levels[mx][my + 1], reallevel), 0), lowblocksize - 1) * lowblocksize + lowblocksize - 1);
if (f_part((x / level) / float(pow(2.0, int(tnlevel - reallevel)))) == 0.5) {
prim->add_vertex(min(max(sfav(x / level + 1, tnlevel, reallevel), 0), lowblocksize - 1) * lowblocksize + lowblocksize - 1);
prim->add_vertex(min(max(sfav(x / level - 1, tnlevel, reallevel), 0), lowblocksize - 1) * lowblocksize + lowblocksize - 1);
prim->add_vertex(vcounter);
prim->close_primitive();
}
@ -244,8 +247,10 @@ generate_block(unsigned short mx,
PT(Geom) geom = new Geom(vdata);
geom->add_primitive(prim);
geom->set_bounds_type(BoundingVolume::BT_box);
PT(GeomNode) node = new GeomNode("gmm" + int_to_str(mx) + "x" + int_to_str(my));
ostringstream sname;
sname << "gmm" << mx << "x" << my;
PT(GeomNode) node = new GeomNode(sname.str());
node->add_geom(geom);
node->set_bounds_type(BoundingVolume::BT_box);
_old_levels.at(mx).at(my) = reallevel;
@ -381,7 +386,7 @@ generate() {
_root_flattened = false;
for (unsigned int mx = 0; mx < (_xsize - 1) / _block_size; mx++) {
_old_levels[mx].resize(int((_ysize - 1) / _block_size));
pvector<NodePath> tvector; //create temporary row
pvector<NodePath> tvector; // Create temporary row
for (unsigned int my = 0; my < (_ysize - 1) / _block_size; my++) {
if (_bruteforce) {
tvector.push_back(_root.attach_new_node(generate_block(mx, my, 0)));
@ -390,7 +395,7 @@ generate() {
}
tvector[my].set_pos((mx + 0.5) * _block_size, (my + 0.5) * _block_size, 0);
}
_blocks.push_back(tvector); //push the new row of NodePaths into the 2d vect
_blocks.push_back(tvector); // Push the new row of NodePaths into the 2d vect
tvector.clear();
}
auto_flatten();
@ -600,3 +605,77 @@ update_block(unsigned short mx, unsigned short my,
return false;
}
////////////////////////////////////////////////////////////////////
// Function: GeoMipTerrain::set_heightfield
// Access: Published
// Description: Loads the specified heightmap image file into
// the heightfield. Returns true if succeeded, or
// false if an error has occured.
// If the heightmap is not a power of two plus one,
// it is scaled up using a gaussian filter.
////////////////////////////////////////////////////////////////////
bool GeoMipTerrain::
set_heightfield(const Filename &filename, PNMFileType *ftype) {
// First, we need to load the header to determine the size and format.
PNMImageHeader imgheader;
if (imgheader.read_header(filename, ftype)) {
// Copy over the header to the heightfield image.
_heightfield.copy_header_from(imgheader);
if(!is_power_of_two(imgheader.get_x_size() - 1) || !is_power_of_two(imgheader.get_y_size() - 1)) {
// Calculate the nearest power-of-two-plus-one size.
unsigned int reqx, reqy;
reqx = max(3, (int) pow(2.0, ceil(log((double) max(2, imgheader.get_x_size() - 1)) / log(2.0))) + 1);
reqy = max(3, (int) pow(2.0, ceil(log((double) max(2, imgheader.get_y_size() - 1)) / log(2.0))) + 1);
// If it's not a valid size, tell PNMImage to resize it.
if (reqx != imgheader.get_x_size() || reqy != imgheader.get_y_size()) {
grutil_cat.warning() << "Rescaling heightfield image " << filename << " to "
<< reqx << " by " << reqy << " pixels\n";
_heightfield.set_read_size(reqx, reqy);
}
}
// Read the real image now
if (!_heightfield.read(filename, ftype)) {
_heightfield.clear_read_size();
grutil_cat.error() << "Failed to read heightfield image " << filename << "!\n";
return false;
}
_is_dirty = true;
_xsize = _heightfield.get_x_size();
_ysize = _heightfield.get_y_size();
return true;
} else {
grutil_cat.error() << "Failed to load heightfield image " << filename << "!\n";
}
return false;
}
////////////////////////////////////////////////////////////////////
// Function: GeoMipTerrain::get_neighbor_level
// Access: Private
// Description: Helper function for generate().
////////////////////////////////////////////////////////////////////
unsigned short GeoMipTerrain::
get_neighbor_level(unsigned short mx, unsigned short my, short dmx, short dmy) {
// If we're across the terrain border, check if we want stitching.
// If not, return the same level as this one - it won't have to make junctions.
if (mx + dmx < 0 || mx + dmx >= (_xsize - 1) / _block_size ||
my + dmy < 0 || my + dmy >= (_ysize - 1) / _block_size) {
return (_stitching) ? _max_level : min(max(_min_level, _levels[mx][my]), _max_level);
}
// If we're rendering bruteforce, the level must be the same as this one.
if (_bruteforce) {
return min(max(_min_level, _levels[mx][my]), _max_level);
}
// Only if the level is higher than the current.
// Otherwise, the junctions will be made for the other chunk.
if (_levels[mx + dmx][my + dmy] > _levels[mx][my]) {
return min(max(_min_level, _levels[mx + dmx][my + dmy]), _max_level);
} else {
return min(max(_min_level, _levels[mx][my]), _max_level);
}
}

View File

@ -1,6 +1,5 @@
// Filename: geoMipTerrain.h
// Created by: pro-rsoft (29jun07)
// Last updated by: pro-rsoft (25sep08)
//
////////////////////////////////////////////////////////////////////
//
@ -44,8 +43,7 @@ PUBLISHED:
INLINE ~GeoMipTerrain();
INLINE PNMImage &heightfield();
INLINE bool set_heightfield(const Filename &filename,
PNMFileType *type = NULL);
bool set_heightfield(const Filename &filename, PNMFileType *type = NULL);
INLINE bool set_heightfield(const PNMImage &image);
INLINE bool set_heightfield(const string &path);
INLINE PNMImage &color_map();
@ -104,6 +102,8 @@ PUBLISHED:
INLINE const NodePath get_block_node_path(unsigned short mx,
unsigned short my);
INLINE LVecBase2f get_block_from_pos(double x, double y);
INLINE void set_border_stitching(bool stitching);
INLINE bool get_border_stitching();
PNMImage make_slope_image();
void generate();
@ -118,8 +118,6 @@ private:
void auto_flatten();
bool root_flattened();
INLINE std::string int_to_str(int i);
INLINE int str_to_int(std::string str);
INLINE bool is_power_of_two(unsigned int i);
INLINE float f_part(float i);
INLINE double f_part(double i);
@ -127,6 +125,7 @@ private:
INLINE double get_pixel_value(int x, int y);
INLINE double get_pixel_value(unsigned short mx, unsigned short my, int x, int y);
INLINE unsigned short lod_decide(unsigned short mx, unsigned short my);
unsigned short get_neighbor_level(unsigned short mx, unsigned short my, short dmx, short dmy);
NodePath _root;
int _auto_flatten;
@ -147,6 +146,7 @@ private:
NodePath _focal_point;
bool _focal_is_temporary;
unsigned short _min_level;
bool _stitching;
pvector<pvector<NodePath> > _blocks;
pvector<pvector<unsigned short> > _levels;
pvector<pvector<unsigned short> > _old_levels;