546 lines
15 KiB
Text
546 lines
15 KiB
Text
|
/**
|
||
|
* PANDA 3D SOFTWARE
|
||
|
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||
|
*
|
||
|
* All use of this software is subject to the terms of the revised BSD
|
||
|
* license. You should have received a copy of this license along
|
||
|
* with this source code in a file named "LICENSE."
|
||
|
*
|
||
|
* @file geoMipTerrain.I
|
||
|
* @author rdb
|
||
|
* @date 2007-06-29
|
||
|
*/
|
||
|
|
||
|
#include "config_grutil.h"
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
*/
|
||
|
INLINE GeoMipTerrain::
|
||
|
GeoMipTerrain(const std::string &name) {
|
||
|
_root = NodePath(name);
|
||
|
_root_flattened = false;
|
||
|
_xsize = 0;
|
||
|
_ysize = 0;
|
||
|
_block_size = 16;
|
||
|
_max_level = 4; // Always log(_block_size) / log(2.0)
|
||
|
_min_level = 0;
|
||
|
_factor = 100.0;
|
||
|
_near = 16.0;
|
||
|
_far = 128.0;
|
||
|
_use_near_far = false;
|
||
|
_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;
|
||
|
_stitching = false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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() {
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a boolean whether the terrain is rendered bruteforce or not. See
|
||
|
* set_bruteforce for more information.
|
||
|
*/
|
||
|
INLINE bool GeoMipTerrain::
|
||
|
get_bruteforce() {
|
||
|
return _bruteforce;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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(const LPoint2d &fp) {
|
||
|
set_focal_point(fp.get_x(), fp.get_y());
|
||
|
}
|
||
|
INLINE void GeoMipTerrain::
|
||
|
set_focal_point(const LPoint2f &fp) {
|
||
|
set_focal_point(double(fp.get_x()), double(fp.get_y()));
|
||
|
}
|
||
|
INLINE void GeoMipTerrain::
|
||
|
set_focal_point(const LPoint3d &fp) {
|
||
|
set_focal_point(fp.get_x(), fp.get_y());
|
||
|
}
|
||
|
INLINE void GeoMipTerrain::
|
||
|
set_focal_point(const 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;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the highest level possible for this block size. When a block is at
|
||
|
* this level, it will be the worst quality possible.
|
||
|
*/
|
||
|
INLINE unsigned short GeoMipTerrain::
|
||
|
get_max_level() {
|
||
|
return _max_level;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the block size.
|
||
|
*/
|
||
|
INLINE unsigned short GeoMipTerrain::
|
||
|
get_block_size() {
|
||
|
return _block_size;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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((double) newbs) / log(2.0) + 0.5));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
_max_level = (unsigned short) (log((double) _block_size) / log(2.0));
|
||
|
_is_dirty = true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* DEPRECATED method. Use set_near/far instead. 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(PN_stdfloat factor) {
|
||
|
grutil_cat.debug() << "Using deprecated method set_factor, use set_near and set_far instead!\n";
|
||
|
_use_near_far = false;
|
||
|
_factor = factor;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the near and far LOD distances in one call.
|
||
|
*/
|
||
|
INLINE void GeoMipTerrain::
|
||
|
set_near_far(double input_near, double input_far) {
|
||
|
_use_near_far = true;
|
||
|
_near = input_near;
|
||
|
_far = input_far;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the near LOD distance, at which the terrain will be rendered at
|
||
|
* highest quality. This distance is in the terrain's coordinate space!
|
||
|
*/
|
||
|
INLINE void GeoMipTerrain::
|
||
|
set_near(double input_near) {
|
||
|
_use_near_far = true;
|
||
|
_near = input_near;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the far LOD distance, at which the terrain will be rendered at lowest
|
||
|
* quality. This distance is in the terrain's coordinate space!
|
||
|
*/
|
||
|
INLINE void GeoMipTerrain::
|
||
|
set_far(double input_far) {
|
||
|
_use_near_far = true;
|
||
|
_far = input_far;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the far LOD distance in the terrain coordinate space
|
||
|
*/
|
||
|
INLINE double GeoMipTerrain::
|
||
|
get_far() {
|
||
|
return _far;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the near LOD distance in the terrain coordinate space
|
||
|
*/
|
||
|
INLINE double GeoMipTerrain::
|
||
|
get_near() {
|
||
|
return _near;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the automatic-flatten mode (e.g., off, flatten_light,
|
||
|
* flatten_medium, or flatten_strong)
|
||
|
*/
|
||
|
INLINE int GeoMipTerrain::
|
||
|
get_flatten_mode() {
|
||
|
return _auto_flatten;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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) {
|
||
|
nassertr(mx < _blocks.size(), NodePath::fail());
|
||
|
nassertr(my < _blocks[mx].size(), NodePath::fail());
|
||
|
return _blocks[mx][my];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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 LVecBase2 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 LVecBase2(x, y);
|
||
|
}
|
||
|
/**
|
||
|
* Calculates the level for the given mipmap.
|
||
|
*/
|
||
|
INLINE unsigned short GeoMipTerrain::
|
||
|
lod_decide(unsigned short mx, unsigned short my) {
|
||
|
PN_stdfloat cx = mx;
|
||
|
PN_stdfloat cy = my;
|
||
|
cx = (cx * _block_size + _block_size / 2) * _root.get_sx();
|
||
|
cy = (cy * _block_size + _block_size / 2) * _root.get_sy();
|
||
|
PN_stdfloat d;
|
||
|
if (_use_near_far) {
|
||
|
d = sqrt(pow(_focal_point.get_x(_root) - cx, 2) +
|
||
|
pow(_focal_point.get_y(_root) - cy, 2));
|
||
|
if (d < _near) {
|
||
|
return 0;
|
||
|
} else if (d > _far) {
|
||
|
return _max_level;
|
||
|
} else {
|
||
|
return (unsigned short)((d - _near) / (_far - _near) * _max_level * (1.0 - (_min_level / _max_level)) + _min_level);
|
||
|
}
|
||
|
} else {
|
||
|
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 = _max_level;
|
||
|
}
|
||
|
return short(floor(d));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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 PNMImage &image) {
|
||
|
if (image.get_color_space() == CS_sRGB) {
|
||
|
// Probably a mistaken metadata setting on the file.
|
||
|
grutil_cat.warning()
|
||
|
<< "Heightfield image is specified to have sRGB color space!\n"
|
||
|
"Panda applies gamma correction, which will probably cause "
|
||
|
"it to produce incorrect results.\n";
|
||
|
}
|
||
|
|
||
|
// Before we apply anything, validate the size.
|
||
|
if (is_power_of_two(image.get_x_size() - 1) &&
|
||
|
is_power_of_two(image.get_y_size() - 1)) {
|
||
|
_heightfield = image;
|
||
|
_is_dirty = true;
|
||
|
_xsize = _heightfield.get_x_size();
|
||
|
_ysize = _heightfield.get_y_size();
|
||
|
return true;
|
||
|
} else {
|
||
|
grutil_cat.error()
|
||
|
<< "Specified image does not have a power-of-two-plus-one size!\n";
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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;
|
||
|
}
|
||
|
|
||
|
INLINE bool GeoMipTerrain::
|
||
|
set_color_map(const std::string &path) {
|
||
|
return set_color_map(Filename(path));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns whether a color map has been set.
|
||
|
*/
|
||
|
INLINE bool GeoMipTerrain::
|
||
|
has_color_map() const {
|
||
|
return _has_color_map;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Clears the color map.
|
||
|
*/
|
||
|
INLINE void GeoMipTerrain::
|
||
|
clear_color_map() {
|
||
|
if (_has_color_map) {
|
||
|
_color_map.clear();
|
||
|
_has_color_map = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* If this value is true, the LOD level at the borders of the terrain will be
|
||
|
* 0. This is useful if you have multiple terrains attached and you want to
|
||
|
* stitch them together, to fix seams. This setting also has effect when
|
||
|
* bruteforce is enabled, although in that case you are probably better off
|
||
|
* with setting the minlevels to the same value.
|
||
|
*/
|
||
|
INLINE void GeoMipTerrain::
|
||
|
set_border_stitching(bool stitching) {
|
||
|
if (stitching && !_stitching) {
|
||
|
_is_dirty = true;
|
||
|
}
|
||
|
_stitching = stitching;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the current stitching setting. False by default, unless
|
||
|
* set_stitching has been set.
|
||
|
*/
|
||
|
INLINE bool GeoMipTerrain::
|
||
|
get_border_stitching() {
|
||
|
return _stitching;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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 = std::max(std::min(x,int(_xsize-1)),0);
|
||
|
y = std::max(std::min(y,int(_ysize-1)),0);
|
||
|
if (_heightfield.is_grayscale()) {
|
||
|
return double(_heightfield.get_bright(x, y));
|
||
|
} else {
|
||
|
return double(_heightfield.get_red(x, y))
|
||
|
+ double(_heightfield.get_green(x, y)) / 256.0
|
||
|
+ double(_heightfield.get_blue(x, y)) / 65536.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));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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 LVector3 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));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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 /= pow(2.0, powlevel - mypowlevel);
|
||
|
t = double(int(t > 0.0 ? t + 0.5 : t - 0.5));
|
||
|
t *= pow(2.0, powlevel - mypowlevel);
|
||
|
return int(t);
|
||
|
}
|