Browse Source

Working, except that image upside-down

Josh Yelon 19 years ago
parent
commit
ba0a2427bf

+ 1 - 0
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"

+ 175 - 0
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
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     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<<scale;
+  int hsize = size >> 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;
+}
+
+

+ 436 - 0
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
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#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<ysize; y++) {
+    for (int x=0; x<xsize; x++) {
+      _vertex_index[y*xsize+x] = -1;
+    }
+  }
+  
+  if (!_radii_calculated) {
+    int saved_focal_x = _focal_x;
+    int saved_focal_y = _focal_y;
+
+    _focal_x = _heightfield.get_x_size() >> 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<ycells; y++) {
+    for (int x=0; x<xcells; x++) {
+      total += count_triangles(scale,x*size,y*size);
+    }
+  }
+  cerr << "Terrain mesh is " << total << " triangles.\n";
+  for (int y=0; y<ycells; y++) {
+    for (int x=0; x<xcells; x++) {
+      generate_square(root,scale,x*size,y*size,true);
+    }
+  }
+  delete _vertex_index;
+  delete _dirty_vertices;
+  delete _triangle_totals;
+  _vertex_index =0;
+  _dirty_vertices =0;
+  _triangle_totals =0;
+
+  return root;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HeightfieldTesselator::calculate_radii
+//       Access: Private
+//  Description: Sets the radii appropriately to achieve the
+//               desired polygon count.  This is achieved by binary
+//               search.
+////////////////////////////////////////////////////////////////////
+void HeightfieldTesselator::
+calculate_radii(int scale) {
+  int size = 1 << scale;
+  int xsize = _heightfield.get_x_size();
+  int ysize = _heightfield.get_y_size();
+  int xcells = (xsize + size - 2) / size;
+  int ycells = (ysize + size - 2) / size;
+  
+  double lo = 5.0;
+  double hi = _heightfield.get_x_size() + _heightfield.get_y_size();
+  while (1) {
+    double mid = (lo + hi) * 0.5;
+    for (int i=0; i<16; i++) {
+      _radii[i] = (int)(mid * (1<<i));
+    }
+    int total = 0;
+    for (int y=0; y<ycells; y++) {
+      for (int x=0; x<xcells; x++) {
+        total += count_triangles(scale,x*size,y*size);
+      }
+    }
+    if (total > _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<<i));
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HeightfieldTesselator::generate_square
+//       Access: Private
+//  Description: Adds a square region to the current geom.
+//               This relies on the following preconditions:
+//
+//               1. A square of scale N can be adjacent to
+//               a square of scale N or scale N-1, but not
+//               scale N-2 or smaller.
+//               
+//               2. A square of scale N can be adjacent to
+//               at most one square of scale N-1.
+//
+//               Precondition 1 is assured by spacing out the 
+//               detail radii sufficiently.  Precondition 2 is
+//               assured by using rectangular detail radii.
+//
+//               I may someday rewrite this code to eliminate
+//               precondition 2, to allow circular detail radii.
+////////////////////////////////////////////////////////////////////
+void HeightfieldTesselator::
+generate_square(NodePath root, int scale, int x, int y, bool forceclose) {
+  // There are nine possible vertices in the square,
+  // which are labeled as follows:
+  //
+  //    G--H--I
+  //    |     |
+  //    D  E  F
+  //    |     |
+  //    A--B--C
+
+  int size = 1<<scale;
+  int hsize = size>>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<<scale;
+  if (subdivide(scale, x, y)) {
+    int xc = x + (size>>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;
+}

+ 132 - 0
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
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#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
+