From ba0a2427bff9ceefedcc252bc1094a27daf7f940 Mon Sep 17 00:00:00 2001 From: Josh Yelon Date: Thu, 27 Jul 2006 20:13:51 +0000 Subject: [PATCH] Working, except that image upside-down --- panda/src/grutil/grutil_composite1.cxx | 1 + panda/src/grutil/heightfieldTesselator.I | 175 +++++++++ panda/src/grutil/heightfieldTesselator.cxx | 436 +++++++++++++++++++++ panda/src/grutil/heightfieldTesselator.h | 132 +++++++ 4 files changed, 744 insertions(+) create mode 100644 panda/src/grutil/heightfieldTesselator.I create mode 100644 panda/src/grutil/heightfieldTesselator.cxx create mode 100644 panda/src/grutil/heightfieldTesselator.h diff --git a/panda/src/grutil/grutil_composite1.cxx b/panda/src/grutil/grutil_composite1.cxx index 2f47eb24da..e8273b6a2c 100644 --- a/panda/src/grutil/grutil_composite1.cxx +++ b/panda/src/grutil/grutil_composite1.cxx @@ -1,4 +1,5 @@ #include "cardMaker.cxx" +#include "heightfieldTesselator.cxx" #include "config_grutil.cxx" #include "lineSegs.cxx" #include "fisheyeMaker.cxx" diff --git a/panda/src/grutil/heightfieldTesselator.I b/panda/src/grutil/heightfieldTesselator.I new file mode 100644 index 0000000000..fa9a20d66f --- /dev/null +++ b/panda/src/grutil/heightfieldTesselator.I @@ -0,0 +1,175 @@ +// Filename: heightfieldTesselator.I +// Created by: jyelon (17jul06) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved +// +// All use of this software is subject to the terms of the Panda 3d +// Software license. You should have received a copy of this license +// along with this source code; you will also find a current copy of +// the license at http://etc.cmu.edu/panda3d/docs/license/ . +// +// To contact the maintainers of this program write to +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: HeightfieldTesselator::Constructor +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE HeightfieldTesselator:: +HeightfieldTesselator(const string &name) : Namable(name) { + _poly_count = 10000; + _visibility_radius = 32768; + _focal_x = 0; + _focal_y = 0; + _horizontal_scale = 1.0; + _vertical_scale = 255.0; + _max_triangles = 512; + _radii_calculated = false; +} + +//////////////////////////////////////////////////////////////////// +// Function: HeightfieldTesselator::Destructor +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE HeightfieldTesselator:: +~HeightfieldTesselator() { +} + +//////////////////////////////////////////////////////////////////// +// Function: HeightfieldTesselator::heightfield +// Access: Published +// Description: Returns a reference to the heightfield (a PNMImage) +// contained inside the HeightfieldTesselator. You +// can use the reference to alter the heightfield. +//////////////////////////////////////////////////////////////////// +INLINE PNMImage &HeightfieldTesselator:: +heightfield() { + return _heightfield; +} + +//////////////////////////////////////////////////////////////////// +// Function: HeightfieldTesselator::set_heightfield +// Access: Published +// Description: Loads the specified greyscale image file into +// the heightfield. +//////////////////////////////////////////////////////////////////// +INLINE bool HeightfieldTesselator:: +set_heightfield(const Filename &filename, PNMFileType *ftype) { + _radii_calculated = false; + return _heightfield.read(filename, ftype); +} + +//////////////////////////////////////////////////////////////////// +// Function: HeightfieldTesselator::set_poly_count +// Access: Public +// Description: Sets the polygon-count target. The tesselator +// usually manages to come within about 20% of the +// target, plus or minus. +//////////////////////////////////////////////////////////////////// +INLINE void HeightfieldTesselator:: +set_poly_count(int n) { + _radii_calculated = false; + _poly_count = n; +} + +//////////////////////////////////////////////////////////////////// +// Function: HeightfieldTesselator::set_visibility_radius +// Access: Published +// Description: Sets the visibility radius. Polygons that +// are completely outside the radius (relative to +// the focal point) are cropped away. The cropping +// is imperfect (all approximations are conservative), +// so this should be used in conjunction with a far +// clipping plane, fog, or some other visibility +// limiting mechanism. The units are in pixels. +//////////////////////////////////////////////////////////////////// +INLINE void HeightfieldTesselator:: +set_visibility_radius(int radius) { + _radii_calculated = false; + if (radius < 1) radius = 1; + if (radius > 32768) radius = 32768; + _visibility_radius = radius; +} + +//////////////////////////////////////////////////////////////////// +// Function: HeightfieldTesselator::set_focal_point +// Access: Published +// Description: Sets the focal point. The tesselator generates +// high-resolution terrain around the focal point, and +// progressively lower and lower resolution terrain +// as you get farther away. The units are in pixels. +//////////////////////////////////////////////////////////////////// +INLINE void HeightfieldTesselator:: +set_focal_point(int x, int y) { + _focal_x = x; + _focal_y = y; +} + +//////////////////////////////////////////////////////////////////// +// Function: HeightfieldTesselator::set_horizontal_scale +// Access: Published +// Description: Sets the horizontal scale. The default scale is 1.0, +// meaning that each pixel in the heightfield is +// 1x1 panda units wide. +//////////////////////////////////////////////////////////////////// +INLINE void HeightfieldTesselator:: +set_horizontal_scale(double h) { + _horizontal_scale = h; +} + +//////////////////////////////////////////////////////////////////// +// Function: HeightfieldTesselator::set_vertical_scale +// Access: Published +// Description: Sets the vertical scale. The default scale is 255.0, +// meaning that each as the gray value ranges from (0-1), +// the elevation ranges from (0-255) feet. +//////////////////////////////////////////////////////////////////// +INLINE void HeightfieldTesselator:: +set_vertical_scale(double v) { + _vertical_scale = v; +} + +//////////////////////////////////////////////////////////////////// +// Function: HeightfieldTesselator::set_max_triangles +// Access: Published +// Description: Sets the max triangles per geom. +//////////////////////////////////////////////////////////////////// +INLINE void HeightfieldTesselator:: +set_max_triangles(int n) { + _max_triangles = n; +} + +//////////////////////////////////////////////////////////////////// +// Function: HeightfieldTesselator::subdivide +// Access: Private +// Description: Returns true if the given square should be subdivided. +//////////////////////////////////////////////////////////////////// +INLINE bool HeightfieldTesselator:: +subdivide(int scale, int x, int y) { + if (scale == 0) { + return false; + } + int size = 1<> 1; + int xcenter = x+hsize; + int ycenter = y+hsize; + int deltax = x - _focal_x; + int deltay = y - _focal_y; + if (deltax < 0) deltax = -deltax; + if (deltay < 0) deltay = -deltay; + int dist = (deltax > deltay) ? deltax : deltay; + if (dist < _radii[scale-1]) { + return true; + } + return false; +} + + diff --git a/panda/src/grutil/heightfieldTesselator.cxx b/panda/src/grutil/heightfieldTesselator.cxx new file mode 100644 index 0000000000..848a8b43ef --- /dev/null +++ b/panda/src/grutil/heightfieldTesselator.cxx @@ -0,0 +1,436 @@ +// Filename: heightfieldTesselator.cxx +// Created by: jyelon (17jul06) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved +// +// All use of this software is subject to the terms of the Panda 3d +// Software license. You should have received a copy of this license +// along with this source code; you will also find a current copy of +// the license at http://etc.cmu.edu/panda3d/docs/license/ . +// +// To contact the maintainers of this program write to +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + +#include "heightfieldTesselator.h" +#include "geomNode.h" +#include "transformState.h" +#include "sceneGraphReducer.h" +#include "lvector3.h" + +//////////////////////////////////////////////////////////////////// +// Function: HeightfieldTesselator::fix_heightfield +// Access: Published +// Description: Makes sure that the heightfield is a grayscale +// image of valid dimensions. If necessary, adds a +// band of zeros onto the top and right side of +// the heightfield, so as to make the size of the +// heightfield a multiple of the given size plus one. +//////////////////////////////////////////////////////////////////// +void HeightfieldTesselator:: +fix_heightfield(int size) { + + // Calculate the padded size of the heightfield. + int xsize = _heightfield.get_x_size(); + int ysize = _heightfield.get_y_size(); + int xcells = (xsize + size - 2) / size; + int ycells = (ysize + size - 2) / size; + int xpadded = xcells * size + 1; + int ypadded = ycells * size + 1; + + // If the heightfield is already in good shape, done. + if ((xpadded == _heightfield.get_x_size()) && + (ypadded == _heightfield.get_y_size()) && + (_heightfield.is_grayscale())) { + return; + } + + // Pad the heightfield, and convert to grey. + PNMImage unfixed(_heightfield); + _heightfield.clear(xpadded, ypadded, 1, + unfixed.get_maxval(), + unfixed.get_type()); + for (int y = 0; y < ysize; y++) { + for (int x = 0; x < xsize; x++) { + _heightfield.set_gray_val(x, y, unfixed.get_gray_val(x, y)); + } + } + for (int y = ysize; y < ypadded; y++) { + for (int x = xsize; x < xpadded; x++) { + _heightfield.set_gray_val(x, y, 0); + } + } +} + +//////////////////////////////////////////////////////////////////// +// Function: HeightfieldTesselator::get_vertex +// Access: Private +// Description: Fetches the vertex at (x,y), or if the vertex +// does not exist, creates it. +//////////////////////////////////////////////////////////////////// +int HeightfieldTesselator:: +get_vertex(int x, int y) { + int xsize = _heightfield.get_x_size(); + int vtx = _vertex_index[x+y*xsize]; + if (vtx >= 0) { + return vtx; + } + int nx = x-1; + int px = x+1; + int ny = y-1; + int py = y+1; + if (nx < 0) nx++; + if (ny < 0) ny++; + if (px >= _heightfield.get_x_size()) px--; + if (py >= _heightfield.get_y_size()) py--; + double drx = _heightfield.get_gray(px,y) - _heightfield.get_gray(nx,y); + double dry = _heightfield.get_gray(x,py) - _heightfield.get_gray(x,ny); + LVector3f normal(drx * _vertical_scale * 0.5, dry * _vertical_scale * 0.5, _horizontal_scale); + normal.normalize(); + double z = _heightfield.get_gray(x,y); + if (z > 2.0) { + cerr << "What the heck?"; + } + _vertex_writer->add_data3f(x*_horizontal_scale,y*_horizontal_scale,z*_vertical_scale); + _normal_writer->add_data3f(normal); + vtx = _next_index++; + _dirty_vertices[vtx] = x+y*xsize; + _vertex_index[x+y*xsize] = vtx; + return vtx; +} + + +//////////////////////////////////////////////////////////////////// +// Function: HeightfieldTesselator::generate +// Access: Published +// Description: Generates a tree of nodes that represents the +// heightfield. This can be reparented into the scene. +//////////////////////////////////////////////////////////////////// +NodePath HeightfieldTesselator:: +generate() { + int scale = 7; + int size = 1 << scale; + fix_heightfield(size); + int xsize = _heightfield.get_x_size(); + int ysize = _heightfield.get_y_size(); + int xcells = (xsize + size - 2) / size; + int ycells = (ysize + size - 2) / size; + + _vertex_index = new int[xsize * ysize]; + _dirty_vertices = new int[xsize * ysize]; + _triangle_totals = new int[xsize * ysize]; + for (int y=0; y> 1; + _focal_y = _heightfield.get_y_size() >> 1; + + calculate_radii(scale); + + _focal_x = saved_focal_x; + _focal_y = saved_focal_y; + + _radii_calculated = true; + } + + PT(PandaNode) result = new PandaNode(get_name()); + NodePath root(result); + + int total = 0; + for (int y=0; y _poly_count) { + hi = mid; + } else { + lo = mid; + } + if (hi - lo < 1.0) { + break; + } + } + double mid = (lo + hi) * 0.5; + for (int i=0; i<16; i++) { + _radii[i] = (int)(mid * (1<>1; + +#define POINTA get_vertex(x ,y) +#define POINTB get_vertex(x+hsize,y) +#define POINTC get_vertex(x+size ,y) +#define POINTD get_vertex(x ,y+hsize) +#define POINTE get_vertex(x+hsize,y+hsize) +#define POINTF get_vertex(x+size ,y+hsize) +#define POINTG get_vertex(x ,y+size) +#define POINTH get_vertex(x+hsize,y+size) +#define POINTI get_vertex(x+size ,y+size) + + if (_triangles == 0) { + open_geom(); + } + if (subdivide(scale, x, y)) { + int xc = x+(size>>1); + int yc = y+(size>>1); + if (_triangle_totals[yc*_heightfield.get_x_size()+xc] > _max_triangles) { + if (_next_index) close_geom(root); + NodePath subroot = root.attach_new_node(get_name() + " interior"); + generate_square(subroot, scale-1, x, y , true); + generate_square(subroot, scale-1, xc, y , true); + generate_square(subroot, scale-1, xc, yc, true); + generate_square(subroot, scale-1, x, yc, true); + } else { + generate_square(root, scale-1, x, y , false); + generate_square(root, scale-1, xc, y , false); + generate_square(root, scale-1, xc, yc, false); + generate_square(root, scale-1, x, yc, false); + } + } else if (subdivide(scale, x+size, y)) { + add_quad(POINTG,POINTA,POINTI,POINTF); + add_quad(POINTA,POINTA,POINTF,POINTC); + } else if (subdivide(scale, x-size, y)) { + add_quad(POINTG,POINTD,POINTI,POINTI); + add_quad(POINTD,POINTA,POINTI,POINTC); + } else if (subdivide(scale, x, y+size)) { + add_quad(POINTG,POINTA,POINTH,POINTA); + add_quad(POINTH,POINTA,POINTI,POINTC); + } else if (subdivide(scale, x, y-size)) { + add_quad(POINTG,POINTA,POINTI,POINTB); + add_quad(POINTI,POINTB,POINTI,POINTC); + } else { + add_quad(POINTG,POINTA,POINTI,POINTC); + } + if (forceclose || (_next_index > _max_triangles)) { + close_geom(root); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: HeightfieldTesselator::count_triangles +// Access: Private +// Description: Calculates how many triangles are inside +// the given region. The result is stored in +// the _poly_totals array, in the center of the +// square. +//////////////////////////////////////////////////////////////////// +int HeightfieldTesselator:: +count_triangles(int scale, int x, int y) { + int size = 1<>1); + int yc = y + (size>>1); + int n = 0; + n += count_triangles(scale-1, x, y ); + n += count_triangles(scale-1, xc, y ); + n += count_triangles(scale-1, x, yc); + n += count_triangles(scale-1, xc, yc); + _triangle_totals[yc*_heightfield.get_x_size() + xc] = n; + return n; + } else if (subdivide(scale, x+size, y)) { + return 3; + } else if (subdivide(scale, x-size, y)) { + return 3; + } else if (subdivide(scale, x, y+size)) { + return 3; + } else if (subdivide(scale, x, y-size)) { + return 3; + } else { + return 2; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: HeightfieldTesselator::add_quad_to_strip +// Access: Private +// Description: Adds a quad to the current triangle strip. +// +// If the quad is adjacent to the previous quad, +// then the two quads are stitched together into +// a strip. If not, degenerate triangles are +// inserted into the strip to separate the two. +//////////////////////////////////////////////////////////////////// +void HeightfieldTesselator:: +add_quad_to_strip(int v1, int v2, int v3, int v4) { + if ((v1 != v2)&&(v2 != v3)&&(v1 != v3)) { + _triangles->add_vertices(v1,v2,v3); + } + if ((v3 != v2)&&(v2 != v4)&&(v4 != v3)) { + _triangles->add_vertices(v3,v2,v4); + } + // if ((v1 == _last_vertex_a)&&(v2 == _last_vertex_b)) { + // _tristrip->add_vertices(v3,v4); + // _last_vertex_a = v3; + // _last_vertex_b = v4; + // } else { + // if ((_last_vertex_a == -1)&&(_last_vertex_b == -1)) { + // _tristrip->add_vertices(v1,v2,v3,v4); + // _last_vertex_a = v3; + // _last_vertex_b = v4; + // } else { + // _tristrip->add_vertices(_last_vertex_b, v1, v1, v2); + // _tristrip->add_vertices(v1,v2,v3,v4); + // _last_vertex_a = v3; + // _last_vertex_b = v4; + // } + // } +} + +//////////////////////////////////////////////////////////////////// +// Function: HeightfieldTesselator::add_quad +// Access: Private +// Description: Adds a quad to the current geom. +// +// Eventually, I plan to reimplement this. It is +// going to add a quad to a table of quads. A +// post-processing pass is going to traverse the +// table, calling add_quad_to_strip in the optimal +// order. For now, though, this routine just calls +// add_quad_to_strip directly, which is quite +// inefficient. +// +//////////////////////////////////////////////////////////////////// +void HeightfieldTesselator:: +add_quad(int v1, int v2, int v3, int v4) { + add_quad_to_strip(v1, v2, v3, v4); +} + + +//////////////////////////////////////////////////////////////////// +// Function: HeightfieldTesselator::open_geom +// Access: Private +// Description: Initiates the construction of a geom. +//////////////////////////////////////////////////////////////////// +void HeightfieldTesselator:: +open_geom() { + _vdata = new GeomVertexData + ("heightfield", GeomVertexFormat::get_v3n3(), Geom::UH_static); + _vertex_writer = new GeomVertexWriter(_vdata, InternalName::get_vertex()); + _normal_writer = new GeomVertexWriter(_vdata, InternalName::get_normal()); + _triangles = new GeomTriangles(Geom::UH_static); + _triangles->set_shade_model(Geom::SM_uniform); + + _next_index = 0; + _last_vertex_a = -1; + _last_vertex_b = -1; +} + +//////////////////////////////////////////////////////////////////// +// Function: HeightfieldTesselator::close_geom +// Access: Private +// Description: Completes the construction of a geom. +//////////////////////////////////////////////////////////////////// +void HeightfieldTesselator:: +close_geom(NodePath root) { + if (_triangles == 0) { + return; + } + _triangles->close_primitive(); + PT(Geom) geom = new Geom(_vdata); + geom->add_primitive(_triangles); + PT(GeomNode) gnode = new GeomNode(get_name() + " patch"); + gnode->add_geom(geom); + root.attach_new_node(gnode); + delete _vertex_writer; + delete _normal_writer; + + for (int i=0; i<_next_index; i++) { + _vertex_index[_dirty_vertices[i]] = -1; + } + + _next_index = 0; + _last_vertex_a = -1; + _last_vertex_b = -1; + _vertex_writer = 0; + _normal_writer = 0; + _triangles = 0; +} diff --git a/panda/src/grutil/heightfieldTesselator.h b/panda/src/grutil/heightfieldTesselator.h new file mode 100644 index 0000000000..62dab8ca08 --- /dev/null +++ b/panda/src/grutil/heightfieldTesselator.h @@ -0,0 +1,132 @@ +// Filename: heightfieldTesselator.h +// Created by: jyelon (17jul06) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved +// +// All use of this software is subject to the terms of the Panda 3d +// Software license. You should have received a copy of this license +// along with this source code; you will also find a current copy of +// the license at http://etc.cmu.edu/panda3d/docs/license/ . +// +// To contact the maintainers of this program write to +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + +#ifndef HEIGHTFIELDTESSELATOR_H +#define HEIGHTFIELDTESSELATOR_H + +#include "pandabase.h" + +#include "luse.h" +#include "pandaNode.h" +#include "pointerTo.h" +#include "namable.h" +#include "pnmImage.h" +#include "geom.h" +#include "geomTristrips.h" +#include "geomTriangles.h" +#include "geomVertexWriter.h" +#include "geomVertexFormat.h" +#include "nodePath.h" + +//////////////////////////////////////////////////////////////////// +// Class : HeightfieldTesselator +// Description : Converts a height field in the form of a greyscale +// image into a scene consisting of a number of GeomNodes. +// +// The tesselation uses an LOD algorithm. You +// supply a "focal point" (X,Y) which tells the +// tesselator where the bulk of the detail should +// be concentrated. The intent is that as the player +// walks around the terrain, you should occasionally +// move the focal point to wherever the player is. +// You should not move the focal point every frame: +// tesselation is not that fast. Also, changing the +// focal point may cause popping, so it is best to +// minimize the number of changes. There are a number +// of parameters that you can use to control tesselation, +// such as a target polygon count, and a visibility +// radius. +// +// The heightfield needs to be a multiple of 128 pixels +// in each dimension. It does not need to be square, +// and it does not need to be a power of two. For +// example, a 384 x 640 heightfield is fine. +// Be aware that tesselation time is proportional to +// heightfield area, so if you plan to use a size larger +// than about 512x512, it may be desirable to benchmark. +// +// Altering parameters, such as the poly count, the +// view radius, or the focal point, does not alter any +// GeomNodes already generated. Parameter changes only +// affect subsequently-generated GeomNodes. It is +// possible to cache many different tesselations of the +// same terrain. +// +//////////////////////////////////////////////////////////////////// + +class EXPCL_PANDA HeightfieldTesselator : public Namable { +PUBLISHED: + INLINE HeightfieldTesselator(const string &name); + INLINE ~HeightfieldTesselator(); + + INLINE PNMImage &heightfield(); + INLINE bool set_heightfield(const Filename &filename, PNMFileType *type = NULL); + INLINE void set_poly_count(int n); + INLINE void set_visibility_radius(int r); + INLINE void set_focal_point(int x, int y); + INLINE void set_horizontal_scale(double h); + INLINE void set_vertical_scale(double v); + INLINE void set_max_triangles(int n); + + NodePath generate(); + +private: + + // These are initialized during the first 'generate' + int _radii[16]; + bool _radii_calculated; + + // These are only valid during the generate process. + int *_triangle_totals; + int *_vertex_index; + int *_dirty_vertices; + + // These are only valid when a geom is open. + int _next_index; + int _last_vertex_a; + int _last_vertex_b; + PT(GeomVertexData) _vdata; + GeomVertexWriter *_vertex_writer; + GeomVertexWriter *_normal_writer; + PT(GeomTriangles) _triangles; + + INLINE bool subdivide(int scale, int x, int y); + void calculate_radii(int scale); + void generate_square(NodePath root, int scale, int x, int y, bool forceclose); + int count_triangles(int scale, int x, int y); + int get_vertex(int x, int y); + void add_quad_to_strip(int v1, int v2, int v3, int v4); + void add_quad(int v1, int v2, int v3, int v4); + void fix_heightfield(int size); + void open_geom(); + void close_geom(NodePath root); + + PNMImage _heightfield; + int _poly_count; + int _visibility_radius; + int _focal_x; + int _focal_y; + double _horizontal_scale; + double _vertical_scale; + int _max_triangles; +}; + +#include "heightfieldTesselator.I" + +#endif +