/** * 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); }