historical/toontown-classic.git/panda/include/geoMipTerrain.I
2024-01-16 11:20:27 -06:00

545 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);
}