From d5af6f1cce8c31b95cc7d0d38b770280d4ab36af Mon Sep 17 00:00:00 2001 From: rdb Date: Sat, 11 Apr 2009 13:37:24 +0000 Subject: [PATCH] Overhauled T-Junction and border-decision math, added border stitching --- panda/src/grutil/geoMipTerrain.I | 99 +++++---------- panda/src/grutil/geoMipTerrain.cxx | 187 ++++++++++++++++++++--------- panda/src/grutil/geoMipTerrain.h | 10 +- 3 files changed, 170 insertions(+), 126 deletions(-) diff --git a/panda/src/grutil/geoMipTerrain.I b/panda/src/grutil/geoMipTerrain.I index d4d4ffc377..bbe85b9767 100644 --- a/panda/src/grutil/geoMipTerrain.I +++ b/panda/src/grutil/geoMipTerrain.I @@ -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 diff --git a/panda/src/grutil/geoMipTerrain.cxx b/panda/src/grutil/geoMipTerrain.cxx index eaffdfb70f..a4581b69c0 100644 --- a/panda/src/grutil/geoMipTerrain.cxx +++ b/panda/src/grutil/geoMipTerrain.cxx @@ -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 tvector; //create temporary row + pvector 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); + } +} + diff --git a/panda/src/grutil/geoMipTerrain.h b/panda/src/grutil/geoMipTerrain.h index 9898f2bef6..9b44693fed 100644 --- a/panda/src/grutil/geoMipTerrain.h +++ b/panda/src/grutil/geoMipTerrain.h @@ -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 > _blocks; pvector > _levels; pvector > _old_levels;