Quellcode durchsuchen

Added GeoMipTerrain

Josh Yelon vor 17 Jahren
Ursprung
Commit
5000a700f1

+ 582 - 0
panda/src/grutil/geoMipTerrain.I

@@ -0,0 +1,582 @@
+// Filename: geoMipTerrain.I
+// Created by:  pro-rsoft (29jun07)
+// Last updated by: pro-rsoft (03mar08)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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: GeoMipTerrain::Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE GeoMipTerrain::
+GeoMipTerrain(const string &name) {
+  _root = NodePath(name);
+  _root_flattened = false;
+  _xsize = 0;
+  _ysize = 0;
+  _min_level = 0;
+  _block_size = 16;
+  _factor = 100.0;
+  _has_color_map = false;
+  PT(PandaNode) tmpnode = new PandaNode("tmp_focal");
+  _auto_flatten = AFM_off;
+  _focal_point = NodePath(tmpnode);
+  _focal_is_temporary = true;
+  _is_dirty = true;
+  _bruteforce = false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::Destructor
+//       Access: Published
+//  Description: This will not remove the terrain node itself.
+//               To have the terrain itself also deleted, please
+//               call remove_node() prior to destruction.
+////////////////////////////////////////////////////////////////////
+INLINE GeoMipTerrain::
+~GeoMipTerrain() { 
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::heightfield
+//       Access: Published
+//  Description: Returns a reference to the heightfield (a PNMImage)
+//               contained inside GeoMipTerrain.  You can use
+//               the reference to alter the heightfield.
+////////////////////////////////////////////////////////////////////
+INLINE PNMImage &GeoMipTerrain::
+heightfield() {
+  return _heightfield;
+}
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::color_map
+//       Access: Published
+//  Description: Returns a reference to the color map (a PNMImage)
+//               contained inside GeoMipTerrain.  You can use
+//               the reference to alter the color map.
+////////////////////////////////////////////////////////////////////
+INLINE PNMImage &GeoMipTerrain::
+color_map() {
+  return _color_map;
+}
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::set_bruteforce
+//       Access: Published
+//  Description: Sets a boolean specifying whether the terrain will
+//               be rendered bruteforce. If the terrain is rendered
+//               bruteforce, there will be no Level of Detail, and
+//               the update() call will only update the
+//               terrain if it is marked dirty.
+////////////////////////////////////////////////////////////////////
+INLINE void GeoMipTerrain::
+set_bruteforce(bool bf) {
+  if (bf == true && _bruteforce == false) {
+    _is_dirty = true;
+  }
+  _bruteforce = bf;
+}
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::get_bruteforce
+//       Access: Published
+//  Description: Returns a boolean whether the terrain is rendered
+//               bruteforce or not. See set_bruteforce for more
+//               information.
+////////////////////////////////////////////////////////////////////
+INLINE bool GeoMipTerrain::
+get_bruteforce() {
+  return _bruteforce;
+}
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::set_auto_flatten
+//       Access: Private
+//  Description: The terrain can be automatically flattened (using
+//               flatten_light, flatten_medium, or flatten_strong)
+//               after each update.  This only affects future
+//               updates, it doesn't flatten the current terrain.
+//                
+////////////////////////////////////////////////////////////////////
+INLINE void GeoMipTerrain::
+set_auto_flatten(int mode) {
+  _auto_flatten = mode;
+}
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::set_focal_point
+//       Access: Published
+//  Description: Sets the focal point.  GeoMipTerrain generates
+//               high-resolution terrain around the focal point, and
+//               progressively lower and lower resolution terrain
+//               as you get farther away. If a point is supplied
+//               and not a NodePath, make sure it's relative to
+//               the terrain. Only the x and y coordinates of
+//               the focal point are taken in respect.
+////////////////////////////////////////////////////////////////////
+INLINE void GeoMipTerrain::
+set_focal_point(double x, double y) {
+  if (!_focal_is_temporary) {
+    PT(PandaNode) tmpnode = new PandaNode("tmp_focal");
+    _focal_point = NodePath(tmpnode);
+  }
+  _focal_point.set_pos(_root, x, y, 0);
+  _focal_is_temporary = true;
+}
+INLINE void GeoMipTerrain::
+set_focal_point(LPoint2d fp) {
+  set_focal_point(fp.get_x(), fp.get_y());
+}
+INLINE void GeoMipTerrain::
+set_focal_point(LPoint2f fp) {
+  set_focal_point(double(fp.get_x()), double(fp.get_y()));
+}
+INLINE void GeoMipTerrain::
+set_focal_point(LPoint3d fp) {
+  set_focal_point(fp.get_x(), fp.get_y());
+}
+INLINE void GeoMipTerrain::
+set_focal_point(LPoint3f fp) {
+  set_focal_point(double(fp.get_x()), double(fp.get_y()));
+}
+INLINE void GeoMipTerrain::
+set_focal_point(NodePath fp) {
+  if (_focal_is_temporary) {
+    _focal_point.remove_node();
+  }
+  _focal_point = fp;
+  _focal_is_temporary = false;
+}
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::get_focal_point
+//       Access: Published
+//  Description: Returns the focal point, as a NodePath.
+//               If you have set it to be just a point, it will
+//               return an empty node at the focal position.
+////////////////////////////////////////////////////////////////////
+INLINE NodePath GeoMipTerrain::
+get_focal_point() const {
+  return _focal_point;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::get_root
+//       Access: Published
+//  Description: Returns the root of the terrain.  This is a
+//               single PandaNode to which all the rest of the
+//               terrain is parented.  The generate and update
+//               operations replace the nodes which are parented
+//               to this root, but they don't replace this root
+//               itself.
+////////////////////////////////////////////////////////////////////
+INLINE NodePath GeoMipTerrain::
+get_root() const {
+  return _root;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::set_min_level
+//       Access: Published
+//  Description: Sets the minimum level of detail at which blocks
+//               may be generated by generate() or update().
+//               The default value is 0, which is the highest
+//               quality. This value is also taken in respect when
+//               generating the terrain bruteforce.
+////////////////////////////////////////////////////////////////////
+INLINE void GeoMipTerrain::
+set_min_level(unsigned short minlevel) {
+  _min_level = minlevel;
+}
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::get_min_level
+//       Access: Published
+//  Description: Gets the minimum level of detail at which blocks
+//               may be generated by generate() or update().
+//               The default value is 0, which is the highest
+//               quality.
+////////////////////////////////////////////////////////////////////
+INLINE unsigned short GeoMipTerrain::
+get_min_level() {
+  return _min_level;
+}
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::get_block_size
+//       Access: Published
+//  Description: Gets the block size.
+////////////////////////////////////////////////////////////////////
+INLINE unsigned short GeoMipTerrain::
+get_block_size() {
+  return _block_size;
+}
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::set_block_size
+//       Access: Published
+//  Description: Sets the block size. If it is not a power of two,
+//               the closest power of two is used.
+////////////////////////////////////////////////////////////////////
+INLINE void GeoMipTerrain::
+set_block_size(unsigned short newbs) {
+  if (is_power_of_two(newbs)) {
+    _block_size = newbs;
+  } else {
+    if (is_power_of_two(newbs - 1)) {
+      _block_size = newbs - 1;
+    } else {
+      if (is_power_of_two(newbs + 1)) {
+        _block_size = newbs + 1;
+      } else {
+        _block_size = (unsigned short) pow(2.0,
+                            floor(log(float(newbs)) / log(2.0) + 0.5));
+      }
+    }
+  }
+  _is_dirty = true;
+}
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::is_dirty
+//       Access: Published
+//  Description: Returns a bool indicating whether the terrain is
+//               marked 'dirty', that means the terrain has to be
+//               regenerated on the next update() call, because
+//               for instance the heightfield has changed.
+//               Once the terrain has been regenerated, the dirty
+//               flag automatically gets reset internally.
+////////////////////////////////////////////////////////////////////
+INLINE bool GeoMipTerrain::
+is_dirty() {
+  return _is_dirty;
+}
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::set_factor
+//       Access: Published
+//  Description: Sets the quality factor at which blocks must be
+//               generated. The higher this level, the better
+//               quality the terrain will be, but more expensive
+//               to render. A value of 0 makes the terrain the
+//               lowest quality possible, depending on blocksize.
+//               The default value is 100.
+////////////////////////////////////////////////////////////////////
+INLINE void GeoMipTerrain::
+set_factor(float factor) {
+  _factor = factor;
+}
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::get_factor
+//       Access: Published
+//  Description: Gets the quality factor at which blocks must be
+//               generated. The higher this level, the better
+//               quality the terrain will be, but more expensive
+//               to render. A value of 0 makes the terrain the
+//               lowest quality possible, depending on blocksize.
+//               The default value is 100.
+////////////////////////////////////////////////////////////////////
+INLINE float GeoMipTerrain::
+get_factor() {
+  return _factor;
+}
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::get_block_node_path
+//       Access: Published
+//  Description: Returns the NodePath of the specified block.
+//               If auto-flatten is enabled and the node is
+//               getting removed during the flattening process,
+//               it will still return a NodePath with the
+//               appropriate terrain chunk, but it will be in
+//               a temporary scenegraph.
+//               Please note that this returns a const object and
+//               you can not modify the node. Modify the heightfield
+//               instead.
+////////////////////////////////////////////////////////////////////
+INLINE const NodePath GeoMipTerrain::
+get_block_node_path(unsigned short mx, unsigned short my) {
+  return _blocks[mx][my];
+}
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::get_block_from_pos
+//       Access: Published
+//  Description: Gets the coordinates of the block at the specified
+//               position. This position must be relative to the
+//               terrain, not to render. Returns an array containing
+//               two values: the block x and the block y coords.
+//               If the positions are out of range, the closest
+//               block is taken.
+//               Note that the VecBase returned does not represent
+//               a vector, position, or rotation, but it contains
+//               the block index of the block which you can use
+//               in GeoMipTerrain::get_block_node_path.
+////////////////////////////////////////////////////////////////////
+INLINE LVecBase2f GeoMipTerrain::
+get_block_from_pos(double x, double y) {
+  if (x < 0) x = 0;
+  if (y < 0) y = 0;
+  if (x > _xsize - 1) x = _xsize - 1;
+  if (y > _ysize - 1) y = _ysize - 1;
+  x = floor(x / _block_size);
+  y = floor(y / _block_size);
+  return LVecBase2f(x, y);
+}
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::lod_decide
+//       Access: Private
+//  Description: Calculates the level for the given mipmap.
+////////////////////////////////////////////////////////////////////
+INLINE unsigned short GeoMipTerrain::
+lod_decide(unsigned short mx, unsigned short my) {
+  float cx = mx; 
+  float cy = my;
+  cx = (cx * _block_size + _block_size / 2) * _root.get_sx();
+  cy = (cy * _block_size + _block_size / 2) * _root.get_sy();
+  float d;
+  if (_factor > 0.0) {
+    d = sqrt(pow(_focal_point.get_x(_root) - cx, 2) +
+             pow(_focal_point.get_y(_root) - cy, 2)) / _factor;
+  } else {
+    d = log(float(_block_size)) / log(2.0);
+  }
+  return short(floor(d));
+}
+
+////////////////////////////////////////////////////////////////////
+//     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.
+////////////////////////////////////////////////////////////////////
+INLINE bool GeoMipTerrain::
+set_heightfield(const Filename &filename, PNMFileType *ftype) {
+  PNMImage image;
+  if (image.read(filename, ftype)) {
+    _is_dirty = true;
+    _heightfield = PNMImage(
+              max(3, (int) pow(2.0, ceil(log(float(max(2, image.get_x_size())))
+                                                             / log(2.0))) + 1),
+              max(3, (int) pow(2.0, ceil(log(float(max(2, image.get_y_size())))
+                                                             / log(2.0))) + 1));
+    // Make sure not to apply gaussian when it's already the right size
+    if (_heightfield.get_x_size() == image.get_x_size() &&
+        _heightfield.get_y_size() == image.get_y_size()) {
+      _heightfield.copy_from(image);
+    } else {
+      _heightfield.gaussian_filter_from(1.0, image);
+    }
+    _xsize = _heightfield.get_x_size();
+    _ysize = _heightfield.get_y_size();
+    return true;
+  }
+  return false;
+}
+INLINE bool GeoMipTerrain::
+set_heightfield(const PNMImage &image) {
+  _heightfield = PNMImage(
+            max(3, (int) pow(2.0, ceil(log(float(max(2, image.get_x_size())))
+                                                           / log(2.0))) + 1),
+            max(3, (int) pow(2.0, ceil(log(float(max(2, image.get_y_size())))
+                                                           / log(2.0))) + 1));
+  // Make sure not to apply gaussian when it's already the right size
+  if (_heightfield.get_x_size() == image.get_x_size() &&
+      _heightfield.get_y_size() == image.get_y_size()) {
+    _heightfield.copy_from(image);
+  } else {
+    _heightfield.gaussian_filter_from(1.0, image);
+  }
+  _is_dirty = true;
+  _xsize = _heightfield.get_x_size();
+  _ysize = _heightfield.get_y_size();
+  return true;
+}
+INLINE bool GeoMipTerrain::
+set_heightfield(const Texture *tex) {
+  _heightfield = PNMImage(
+            max(3, (int) pow(2.0, ceil(log(float(max(2, tex->get_x_size())))
+                                                           / log(2.0))) + 1),
+            max(3, (int) pow(2.0, ceil(log(float(max(2, tex->get_y_size())))
+                                                           / log(2.0))) + 1));
+  PNMImage image;
+  tex->store(image);
+  // Make sure not to apply gaussian when it's already the right size
+  if (_heightfield.get_x_size() == image.get_x_size() &&
+      _heightfield.get_y_size() == image.get_y_size()) {
+    _heightfield.copy_from(image);
+  } else {
+    _heightfield.gaussian_filter_from(1.0, image);
+  }
+  _is_dirty = true;
+  _xsize = _heightfield.get_x_size();
+  _ysize = _heightfield.get_y_size();
+  return true;
+}
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::set_color_map
+//       Access: Published
+//  Description: Loads the specified image as color map. The next
+//               time generate() is called, the terrain is painted
+//               with this color map using the vertex color column.
+//               Returns a boolean indicating whether the operation
+//               has succeeded.
+////////////////////////////////////////////////////////////////////
+INLINE bool GeoMipTerrain::
+set_color_map(const Filename &filename, PNMFileType *ftype) {
+  if (_color_map.read(filename, ftype)) {
+    _is_dirty = true;
+    _has_color_map = true;
+    return true;
+  }
+  return false;
+}
+INLINE bool GeoMipTerrain::
+set_color_map(const PNMImage &image) {
+  _color_map.copy_from(image);
+  _is_dirty = true;
+  _has_color_map = true;
+  return true;
+}
+INLINE bool GeoMipTerrain::
+set_color_map(const Texture *tex) {
+  tex->store(_color_map);
+  _is_dirty = true;
+  return true;
+}
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::has_color_map
+//       Access: Published
+//  Description: Returns whether a color map has been set.
+////////////////////////////////////////////////////////////////////
+INLINE bool GeoMipTerrain::
+has_color_map() {
+  return _has_color_map;
+}
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::clear_color_map
+//       Access: Published
+//  Description: Clears the color map.
+////////////////////////////////////////////////////////////////////
+INLINE void GeoMipTerrain::
+clear_color_map() {
+  if (_has_color_map) {
+    _color_map.clear();
+    _has_color_map = false;
+  }
+}
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::get_pixel_value
+//       Access: Private
+//  Description: Get the elevation at a certain pixel of the image.
+//               This function does NOT linearly interpolate.
+//               For that, use GeoMipTerrain::get_elevation() instead.
+////////////////////////////////////////////////////////////////////
+INLINE double GeoMipTerrain::
+get_pixel_value(int x, int y) {
+  x = max(min(x,int(_xsize-1)),0);
+  y = max(min(y,int(_ysize-1)),0);
+  return double(_heightfield.get_bright(int(x),int(y)));
+/*  return double(_heightfield.get_red_val(int(x),int(y))
+              + _heightfield.get_green_val(int(x),int(y)) * 256
+              + _heightfield.get_blue_val(int(x),int(y)) * 65536) / 16777215.0;
+*/
+}
+INLINE double GeoMipTerrain::
+get_pixel_value(unsigned short mx, unsigned short my, int x, int y) {
+  nassertr_always(mx < (_xsize - 1) / _block_size, false);
+  nassertr_always(my < (_ysize - 1) / _block_size, false);
+  return get_pixel_value(mx * _block_size + x, (_ysize - 1) -
+                         (my * _block_size + y));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::get_normal
+//       Access: Published
+//  Description: Fetches the terrain normal at (x,y), where the input
+//               coordinate is specified in pixels. This ignores the
+//               current LOD level and instead provides an
+//               accurate number.
+//               Terrain scale is NOT taken into account! To get
+//               accurate normals, please divide it by the
+//               terrain scale and normalize it again!
+////////////////////////////////////////////////////////////////////
+INLINE LVector3f GeoMipTerrain::
+get_normal(unsigned short mx, unsigned short my, int x, int y) {
+  nassertr_always(mx < (_xsize - 1) / _block_size, false);
+  nassertr_always(my < (_ysize - 1) / _block_size, false);
+  return get_normal(mx * _block_size + x, (_ysize - 1) -
+                    (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
+//  Description: Returns a bool whether the given int i is a
+//               power of two or not.
+////////////////////////////////////////////////////////////////////
+INLINE bool GeoMipTerrain::
+is_power_of_two(unsigned int i) {
+  return !((i - 1) & i);
+}
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::f_part
+//       Access: Private
+//  Description: Returns the part of the number right of the
+//               floating-point.
+////////////////////////////////////////////////////////////////////
+INLINE float GeoMipTerrain::
+f_part(float i) {
+  return i - floor(i);
+}
+INLINE double GeoMipTerrain::
+f_part(double i) {
+  return i - floor(i);
+}
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::sfav
+//       Access: Private
+//  Description: Used to calculate vertex numbers. Only to
+//               be used internally.
+////////////////////////////////////////////////////////////////////
+INLINE int GeoMipTerrain::
+sfav(int n, int powlevel, int mypowlevel) {
+  double t = n - 1;
+  t /= float(pow(2.0, powlevel - mypowlevel));
+  t = double(int(t > 0.0 ? t + 0.5 : t - 0.5));
+  t *= float(pow(2.0, powlevel - mypowlevel));
+  return int(t);
+}
+

+ 561 - 0
panda/src/grutil/geoMipTerrain.cxx

@@ -0,0 +1,561 @@
+// Filename: geoMipTerrain.cxx
+// Created by:  pro-rsoft (29jun07)
+// Last updated by: pro-rsoft (08mar08)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "geoMipTerrain.h"
+
+#include "geomVertexFormat.h"
+#include "geomVertexArrayFormat.h"
+#include "internalName.h"
+#include "geomVertexData.h"
+#include "geomVertexWriter.h"
+#include "geomTristrips.h"
+#include "geomTriangles.h"
+#include "geom.h"
+#include "geomNode.h"
+
+#include "sceneGraphReducer.h"
+
+#include "collideMask.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::generate_block
+//       Access: Private
+//  Description: Generates a chunk of terrain based on the level
+//               specified. As arguments it takes the x and y coords
+//               of the mipmap to be generated, and the level of
+//               detail. T-Junctions for neighbor-mipmaps with
+//               different levels are also taken into account.
+////////////////////////////////////////////////////////////////////
+NodePath GeoMipTerrain::
+generate_block(unsigned short mx,
+               unsigned short my,
+               unsigned short level) {
+  
+  nassertr(mx < (_xsize - 1) / _block_size, NodePath::fail());
+  nassertr(my < (_ysize - 1) / _block_size, NodePath::fail());
+
+  unsigned short center = _block_size / 2;
+  unsigned int vcounter = 0;
+  
+  // Create the format
+  PT(GeomVertexArrayFormat) array = new GeomVertexArrayFormat();
+  if (_has_color_map) {
+    array->add_column(InternalName::make("color"), 4,
+                                            Geom::NT_float32, Geom::C_color);
+  }
+  array->add_column(InternalName::make("vertex"), 3,
+                                            Geom::NT_float32, Geom::C_point);
+  array->add_column(InternalName::make("texcoord"), 2,
+                                            Geom::NT_float32, Geom::C_texcoord);
+  array->add_column(InternalName::make("normal"), 3,
+                                            Geom::NT_float32, Geom::C_vector);
+  PT(GeomVertexFormat) format = new GeomVertexFormat();
+  format->add_array(array);
+
+  // Create vertex data and writers
+  PT(GeomVertexData) vdata = new GeomVertexData(_root.get_name(),
+                   GeomVertexFormat::register_format(format), Geom::UH_dynamic);
+  GeomVertexWriter cwriter;
+  if (_has_color_map) {
+    cwriter=GeomVertexWriter(vdata, "color"  );
+  }
+  GeomVertexWriter vwriter (vdata, "vertex"  );
+  GeomVertexWriter twriter (vdata, "texcoord");
+  GeomVertexWriter nwriter (vdata, "normal"  );
+  PT(GeomTriangles) prim = new GeomTriangles(Geom::UH_dynamic);
+
+  if (_bruteforce) {
+    // LOD Level when rendering bruteforce is always 0 (no lod)
+    level = 0;
+  }
+
+  // Do some calculations with the level
+  level = min(short(max(_min_level, level)), short(log(float(_block_size))
+                                                                  / log(2.0)));
+  unsigned short reallevel = level;
+  level = int(pow(2.0, int(level)));
+  
+  // Confusing note:
+  // the variable level contains not the actual level as described
+  // in the GeoMipMapping paper. That is stored in reallevel,
+  // while the variable level contains 2^reallevel.
+
+  // This is the number of vertices at the certain level.
+  unsigned short lowblocksize = _block_size / level + 1;
+  
+  for (int x = 0; x <= _block_size; x++) {
+    for (int y = 0; y <= _block_size; y++) {
+      if ((x % level) == 0 && (y % level) == 0) {
+        LVector3f normal (get_normal(mx, my, x, y));
+        normal.set(normal.get_x() / _root.get_sx(),
+                   normal.get_y() / _root.get_sy(),
+                   normal.get_z() / _root.get_sz());
+        normal.normalize();
+        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());
+        }
+        vwriter.add_data3f(x - 0.5 * _block_size, y - 0.5 * _block_size,
+                                                get_pixel_value(mx, my, x, y));
+        twriter.add_data2f((mx * _block_size + x) / double(_xsize - 1),
+                           (my * _block_size + y) / double(_ysize - 1));
+        nwriter.add_data3f(normal);
+        if (x > 0 && y > 0) {
+          //left border
+          if (!_bruteforce && x == level && mx > 0 && _levels[mx - 1][my] > reallevel) {
+            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(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));
+              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))) {
+            if ((x <= center && y <= center) || (x > center && y > center)) {
+              if (x > center) {
+                prim->add_vertex(vcounter - lowblocksize - 1);
+                prim->add_vertex(vcounter - 1);
+                prim->add_vertex(vcounter);
+              } else {
+                prim->add_vertex(vcounter);
+                prim->add_vertex(vcounter - lowblocksize);
+                prim->add_vertex(vcounter - lowblocksize - 1);
+              }
+            } else {
+              if (x > center) {
+                prim->add_vertex(vcounter);
+                prim->add_vertex(vcounter - lowblocksize);
+                prim->add_vertex(vcounter - 1);
+              } else {
+                prim->add_vertex(vcounter - 1);
+                prim->add_vertex(vcounter - lowblocksize);
+                prim->add_vertex(vcounter - lowblocksize - 1);
+              }
+            }
+            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));
+              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));
+              prim->add_vertex(vcounter);
+              prim->close_primitive();
+            }
+          }
+          //bottom border
+          if (!_bruteforce && y == level && my > 0 && _levels[mx][my - 1] > reallevel) {
+            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->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);
+              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))) {
+            if ((x <= center && y <= center) || (x > center && y > center)) {
+              if (y > center) {
+                prim->add_vertex(vcounter);
+                prim->add_vertex(vcounter - lowblocksize);//
+                prim->add_vertex(vcounter - lowblocksize - 1);
+              } else {
+                prim->add_vertex(vcounter - lowblocksize - 1);
+                prim->add_vertex(vcounter - 1);//
+                prim->add_vertex(vcounter);
+              }
+            } else {
+              if (y > center) {
+                prim->add_vertex(vcounter);//
+                prim->add_vertex(vcounter - lowblocksize);
+                prim->add_vertex(vcounter - 1);
+              } else {
+                prim->add_vertex(vcounter - 1);
+                prim->add_vertex(vcounter - lowblocksize);
+                prim->add_vertex(vcounter - lowblocksize - 1);//
+              }
+            }
+            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);
+              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);
+              prim->add_vertex(vcounter);
+              prim->close_primitive();
+            }
+          }
+        }
+        vcounter++;
+      }
+    }
+  }
+
+  PT(Geom) geom = new Geom(vdata);
+  geom->add_primitive(prim);
+
+  PT(GeomNode) node = new GeomNode("gmm" + int_to_str(mx) + "x" + int_to_str(my));
+  node->add_geom(geom);
+  _old_levels.at(mx).at(my) = reallevel;
+  return NodePath(node);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::get_elevation
+//       Access: Published
+//  Description: Fetches the elevation at (x, y), where the input
+//               coordinate is specified in pixels. This ignores
+//               the current LOD level and instead provides an
+//               accurate number. Linear blending is used for 
+//               non-integral coordinates.
+//               Terrain scale is NOT taken into account! To get
+//               accurate normals, please multiply this with the
+//               terrain Z scale!
+//
+//               trueElev = terr.get_elevation(x,y) * terr.get_sz();
+////////////////////////////////////////////////////////////////////
+double GeoMipTerrain::
+get_elevation(double x, double y) {
+  y = (_ysize - 1) - y;
+  unsigned int xlo = (unsigned int) x;
+  unsigned int ylo = (unsigned int) y;
+  if (xlo < 0) xlo = 0;
+  if (ylo < 0) ylo = 0;
+  if (xlo > _xsize - 2)
+    xlo = _xsize - 2;
+  if (ylo > _ysize - 2)
+    ylo = _ysize - 2;
+  unsigned int xhi = xlo + 1;
+  unsigned int yhi = ylo + 1;
+  double xoffs = x - xlo;
+  double yoffs = y - ylo;
+  double grayxlyl = get_pixel_value(xlo, ylo);
+  double grayxhyl = get_pixel_value(xhi, ylo);
+  double grayxlyh = get_pixel_value(xlo, yhi);
+  double grayxhyh = get_pixel_value(xhi, yhi);
+  double lerpyl = grayxhyl * xoffs + grayxlyl * (1.0 - xoffs);
+  double lerpyh = grayxhyh * xoffs + grayxlyh * (1.0 - xoffs);
+  return lerpyh * yoffs + lerpyl * (1.0 - yoffs);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::get_normal
+//       Access: Published
+//  Description: Fetches the terrain normal at (x, y), where the
+//               input coordinate is specified in pixels. This
+//               ignores the current LOD level and instead provides
+//               an accurate number.
+//               Terrain scale is NOT taken into account! To get
+//               accurate normals, please divide it by the
+//               terrain scale and normalize it again, like this:
+//
+//               LVector3f normal (terr.get_normal(mx, my, x, y));
+//               normal.set(normal.get_x() / terr.get_sx(),
+//                          normal.get_y() / terr.get_sy(),
+//                          normal.get_z() / terr.get_sz());
+//               normal.normalize();
+////////////////////////////////////////////////////////////////////
+LVector3f GeoMipTerrain::
+get_normal(int x, int y) {
+  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 >= int(_xsize)) px--;
+  if (py >= int(_ysize)) py--;
+  double drx = get_pixel_value(px, y) - get_pixel_value(nx, y);
+  double dry = get_pixel_value(x, py) - get_pixel_value(x, ny);
+  LVector3f normal(drx * 0.5, dry * 0.5, 1);
+  normal.normalize();
+
+  return normal;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::generate
+//       Access: Published
+//  Description: (Re)generates the entire terrain, erasing the
+//               current.
+//               This call un-flattens the terrain, so make sure
+//               you have set auto-flatten if you want to keep
+//               your terrain flattened.
+////////////////////////////////////////////////////////////////////
+void GeoMipTerrain::
+generate() {
+  if (!_bruteforce) {
+    calc_levels();
+  }
+  _root.node()->remove_all_children();
+  _blocks.clear();
+  _old_levels.clear();
+  _old_levels.resize(int((_xsize - 1) / _block_size));
+  _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
+    for (unsigned int my = 0; my < (_ysize - 1) / _block_size; my++) {
+      tvector.push_back(generate_block(mx, my, _levels[mx][my]));
+      tvector[my].reparent_to(_root);
+      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
+    tvector.clear();
+  }
+  auto_flatten();
+  _is_dirty = false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::update
+//       Access: Published
+//  Description: Loops through all of the terrain blocks, and
+//               checks whether they need to be updated.
+//               If that is indeed the case, it regenerates the
+//               mipmap. Returns a true when the terrain has
+//               changed. Returns false when the terrain isn't
+//               updated at all. If there is no terrain yet,
+//               it generates the entire terrain.
+//               This call un-flattens the terrain, so make sure
+//               you have set auto-flatten if you want to keep
+//               your terrain flattened.
+////////////////////////////////////////////////////////////////////
+bool GeoMipTerrain::
+update() {
+  if (_is_dirty) {
+    generate();
+    return true;
+  } else if (!_bruteforce) {
+    calc_levels();
+    if (root_flattened()) {
+      _root.node()->remove_all_children();
+      unsigned int xsize = _blocks.size();
+      for (unsigned int tx = 0; tx < xsize; tx++) {
+        unsigned int ysize = _blocks[tx].size();
+        for (unsigned int ty = 0;ty < ysize; ty++) {
+          _blocks[tx][ty].reparent_to(_root);
+        }
+      }
+      _root_flattened = false;
+    }
+    bool returnVal = false;
+    for (unsigned int mx = 0; mx < (_xsize - 1) / _block_size; mx++) {
+      for (unsigned int my = 0; my < (_ysize - 1) / _block_size; my++) {
+        bool isUpd (update_block(mx, my));
+        if (isUpd && mx > 0 && _old_levels[mx - 1][my] == _levels[mx - 1][my]) {
+          if (update_block(mx - 1, my, -1, true)) {
+            returnVal = true;
+          }
+        }
+        if (isUpd && mx < (_ysize - 1)/_block_size - 1
+                  && _old_levels[mx + 1][my] == _levels[mx + 1][my]) {
+          if (update_block(mx + 1, my, -1, true)) {
+            returnVal = true;
+          }
+        }
+        if (isUpd && my > 0 && _old_levels[mx][my - 1] == _levels[mx][my - 1]) {
+          if (update_block(mx, my - 1, -1, true)) {
+            returnVal = true;
+          }
+        }
+        if (isUpd && my < (_ysize - 1)/_block_size - 1
+                  && _old_levels[mx][my + 1] == _levels[mx][my + 1]) {
+          if (update_block(mx, my + 1, -1, true)) {
+            returnVal = true;
+          }
+        }
+        if (isUpd) {
+          returnVal = true;
+        }
+      }
+    }
+    auto_flatten();
+    return returnVal;
+  }
+  return false;
+}
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::root_flattened
+//       Access: Private
+//  Description: Normally, the root's children are the terrain blocks.
+//               However, if we call flatten_strong on the root,
+//               then the root will contain unpredictable stuff.
+//               This function returns true if the root has been
+//               flattened, and therefore, does not contain the 
+//               terrain blocks.
+////////////////////////////////////////////////////////////////////
+bool GeoMipTerrain::
+root_flattened() {
+  if (_root_flattened) {
+    return true;
+  }
+  
+  // The following code is error-checking code.  It actually verifies
+  // that the terrain blocks are underneath the root, and that nothing
+  // else is underneath the root.  It is not very efficient, and should
+  // eventually be removed once we're sure everything works.
+  
+  int total = 0;
+  unsigned int xsize = _blocks.size();
+  for (unsigned int tx = 0; tx < xsize; tx++) {
+    unsigned int ysize = _blocks[tx].size();
+    for (unsigned int ty = 0;ty < ysize; ty++) {
+      if (_blocks[tx][ty].get_node(1) != _root.node()) {
+        grutil_cat.error() << "GeoMipTerrain: root node unexpectedly mangled!\n";
+        return true;
+        total += 1;
+      }
+    }
+  }
+  if (total != _root.node()->get_num_children()) {
+    grutil_cat.error() << "GeoMipTerrain: root node unexpectedly mangled!\n";
+    return true;
+  }    
+  
+  // The default.
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::auto_flatten
+//       Access: Private
+//  Description: Flattens the geometry under the root.
+////////////////////////////////////////////////////////////////////
+void GeoMipTerrain::
+auto_flatten() {
+  if (_auto_flatten == AFM_off) {
+    return;
+  }
+  
+  // Creating a backup node causes the SceneGraphReducer
+  // to operate in a nondestructive manner.  This protects
+  // the terrain blocks themselves from the flattener.
+
+  NodePath np("Backup Node");
+  np.node()->copy_children(_root.node());
+  
+  // Check if the root's children have changed unexpectedly.
+  switch(_auto_flatten) {
+  case AFM_light:  _root.flatten_light();  break;
+  case AFM_medium: _root.flatten_medium(); break;
+  case AFM_strong: _root.flatten_strong(); break;
+  }
+  
+  _root_flattened = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::calc_levels
+//       Access: Private
+//  Description: Loops through all of the terrain blocks, and
+//               calculates on what level they should be generated.
+////////////////////////////////////////////////////////////////////
+void GeoMipTerrain::
+calc_levels() {
+  _levels.clear();
+  unsigned short t;
+  for (unsigned int mx = 0; mx < (_xsize - 1) / _block_size; mx++) {
+    pvector<unsigned short> tvector; //create temporary row
+    pvector<unsigned short> tvector2; //create temporary row
+    for (unsigned int my = 0; my < (_ysize - 1) / _block_size; my++) {
+      t = min(short(max(_min_level, lod_decide(mx, my))),
+              short(log(float(_block_size)) / log(2.0)));
+      tvector.push_back(t);
+    }
+    _levels.push_back(tvector); //push the new row of levels into the 2d vector
+    tvector.clear();
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GeoMipTerrain::update_block
+//       Access: Private
+//  Description: Checks whether the specified mipmap at (mx,my)
+//               needs to be updated, if so, it regenerates the
+//               mipmap. Returns a true when it has generated
+//               a mipmap. Returns false when the mipmap is already
+//               at the desired level, or when there is no terrain
+//               to update. Note: This does not affect neighboring
+//               blocks, so does NOT fix t-junctions. You will have
+//               to fix that by forced updating the neighboring
+//               chunks as well, with the same levels.
+//               NOTE: do NOT call this when the terrain is marked
+//               dirty. If the terrain is dirty, you will need to
+//               call update() or generate() first.
+//               You can check this by calling GeoMipTerrain::is_dirty().
+////////////////////////////////////////////////////////////////////
+bool GeoMipTerrain::
+update_block(unsigned short mx, unsigned short my,
+                                  signed short level, bool forced) {
+  nassertr_always(!_is_dirty, false);
+  nassertr_always(mx < (_xsize - 1) / _block_size, false);
+  nassertr_always(my < (_ysize - 1) / _block_size, false);
+  if (level == -1) {
+    level = _levels[mx][my];
+  }
+  if (forced || _old_levels[mx][my] != level) { // if the level has changed...
+    // this code copies the collision mask, removes the chunk and
+    // replaces it with a regenerated one.
+    CollideMask mask = _blocks[mx][my].get_collide_mask();
+    _blocks[mx][my].remove_node();
+    _blocks[mx][my] = generate_block(mx, my, level);
+    _blocks[mx][my].set_collide_mask(mask);
+    _blocks[mx][my].reparent_to(_root);
+    _blocks[mx][my].set_pos((mx + 0.5) * _block_size,
+                            (my + 0.5) * _block_size, 0);
+    return true;
+  }
+  return false;
+}
+

+ 156 - 0
panda/src/grutil/geoMipTerrain.h

@@ -0,0 +1,156 @@
+// Filename: geoMipTerrain.h
+// Created by:  pro-rsoft (29jun07)
+// Last updated by: pro-rsoft (03mar08)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 GEOMIPTERRAIN_H
+#define GEOMIPTERRAIN_H
+
+#include "pandabase.h"
+
+#include "luse.h"
+#include "pandaNode.h"
+#include "pointerTo.h"
+
+#include "pnmImage.h"
+#include "nodePath.h"
+
+#include "texture.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : GeoMipTerrain
+// Description : GeoMipTerrain, meaning Panda3D GeoMipMapping, can convert
+//               a heightfield image into a 3D terrain, consisting
+//               of several GeomNodes. It uses the GeoMipMapping
+//                algorithm, or Geometrical MipMapping, based on
+//               the LOD (Level of Detail) algorithm. For more
+//               information about the GeoMipMapping algoritm, see
+//               this paper, written by Willem H. de Boer:
+//               http://flipcode.com/articles/article_geomipmaps.pdf
+//               
+////////////////////////////////////////////////////////////////////
+class GeoMipTerrain {
+PUBLISHED:
+  INLINE GeoMipTerrain(const string &name);
+  INLINE ~GeoMipTerrain();
+  
+  INLINE PNMImage &heightfield();
+  INLINE bool set_heightfield(const Filename &filename,
+                                    PNMFileType *type = NULL);
+  INLINE bool set_heightfield(const PNMImage &image);
+  INLINE bool set_heightfield(const Texture *image);
+  INLINE PNMImage &color_map();
+  INLINE bool set_color_map(const Filename &filename,
+                                  PNMFileType *type = NULL);
+  INLINE bool set_color_map(const PNMImage &image);
+  INLINE bool set_color_map(const Texture *image);
+  INLINE bool has_color_map();
+  INLINE void clear_color_map();
+  double get_elevation(double x, double y);
+  LVector3f get_normal(int x, int y);
+  INLINE LVector3f get_normal(unsigned short mx, unsigned short my, 
+                                                          int x,int y);
+  INLINE void set_bruteforce(bool bf);
+  INLINE bool get_bruteforce();
+
+  // The flatten mode specifies whether the terrain nodes are flattened
+  // together after each terrain update.
+  enum AutoFlattenMode {
+    // FM_off: don't ever flatten the terrain.
+    AFM_off     = 0,
+    // FM_light: the terrain is flattened using flatten_light.
+    AFM_light   = 1,
+    // FM_medium: the terrain is flattened using flatten_medium.
+    AFM_medium  = 2,
+    // FM_strong: the terrain is flattened using flatten_strong.
+    AFM_strong  = 3,
+  };
+
+  INLINE void set_auto_flatten(int mode);
+
+  // The focal point is the point at which the terrain will have the
+  // lowest level of detail (highest quality). Parts farther away
+  // from the focal point will hae a higher level of detail. The
+  // focal point is not taken in respect if bruteforce is set true.
+  INLINE void set_focal_point(LPoint2d fp);
+  INLINE void set_focal_point(LPoint2f fp);
+  INLINE void set_focal_point(LPoint3d fp);
+  INLINE void set_focal_point(LPoint3f fp);
+  INLINE void set_focal_point(double x, double y);
+  INLINE void set_focal_point(NodePath fnp);
+  INLINE NodePath get_focal_point() const;
+  INLINE NodePath get_root() const;
+
+  INLINE void set_min_level(unsigned short minlevel);
+  INLINE unsigned short get_min_level();
+  INLINE unsigned short get_block_size();
+  INLINE void set_block_size(unsigned short newbs);
+  INLINE bool is_dirty();
+  INLINE float get_factor();
+  INLINE void  set_factor(float factor);
+  INLINE const NodePath get_block_node_path(unsigned short mx,
+                                            unsigned short my);
+  INLINE LVecBase2f get_block_from_pos(double x, double y);
+  
+  void generate();
+  bool update();
+  
+private:
+
+
+  NodePath generate_block(unsigned short mx, unsigned short my, unsigned short level);
+  bool update_block(unsigned short mx, unsigned short my,
+                    signed short level = -1, bool forced = false);
+  void calc_levels();
+  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);
+  INLINE int sfav(int n, int powlevel, int mypowlevel);
+  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);
+
+  NodePath _root;
+  int _auto_flatten;
+  bool _root_flattened;
+  PNMImage _heightfield;
+  PNMImage _color_map;
+  bool _is_dirty;
+  bool _has_color_map;
+  unsigned int _xsize;
+  unsigned int _ysize;
+  float _factor;
+  unsigned short _block_size;
+  bool _bruteforce;
+  NodePath _focal_point;
+  bool _focal_is_temporary;
+  unsigned short _min_level;
+  pvector<pvector<NodePath> > _blocks;
+  pvector<pvector<unsigned short> > _levels;
+  pvector<pvector<unsigned short> > _old_levels;
+  
+};
+
+#include "geoMipTerrain.I"
+
+#endif /*GEOMIPTERRAIN_H*/
+

+ 1 - 0
panda/src/grutil/grutil_composite1.cxx

@@ -1,6 +1,7 @@
 #include "cardMaker.cxx"
 #include "arToolKit.cxx"
 #include "heightfieldTesselator.cxx"
+#include "geoMipTerrain.cxx"
 #include "config_grutil.cxx"
 #include "lineSegs.cxx"
 #include "fisheyeMaker.cxx"