Added GeoMipTerrain

This commit is contained in:
Josh Yelon 2008-04-10 18:07:21 +00:00
parent 9191f85b85
commit 5000a700f1
4 changed files with 1300 additions and 0 deletions

View File

@ -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
// panda3d-general@lists.sourceforge.net .
//
////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////
// 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);
}

View File

@ -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
// panda3d-general@lists.sourceforge.net .
//
////////////////////////////////////////////////////////////////////
#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;
}

View File

@ -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
// panda3d-general@lists.sourceforge.net .
//
////////////////////////////////////////////////////////////////////
#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*/

View File

@ -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"