mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-04 02:42:49 -04:00
588 lines
25 KiB
C++
588 lines
25 KiB
C++
// Filename: geoMipTerrain.cxx
|
|
// Created by: pro-rsoft (29jun07)
|
|
// Last updated by: pro-rsoft (25sep08)
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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."
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
|
|
#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 "config_grutil.h"
|
|
|
|
#include "sceneGraphReducer.h"
|
|
|
|
#include "collideMask.h"
|
|
|
|
TypeHandle GeoMipTerrain::_type_handle;
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// 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_stream);
|
|
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_stream);
|
|
|
|
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(_max_level));
|
|
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) {
|
|
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(get_normal(mx, my, x, y));
|
|
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(x, y));
|
|
// normal.set(normal.get_x() / root.get_sx(),
|
|
// normal.get_y() / root.get_sy(),
|
|
// normal.get_z() / root.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::make_slope_image
|
|
// Access: Published
|
|
// Description: Returns a new grayscale image containing the slope
|
|
// angles. A pixel value of 1.0 will mean 90 degrees,
|
|
// meaning, horizontal, a pixel value of 0.0 will
|
|
// mean 0 degrees, or entirely vertical.
|
|
// The resulting image will have the same size as the
|
|
// heightfield image.
|
|
// The scale will be taken into respect -- meaning,
|
|
// if you change the terrain scale, the slope image
|
|
// will need to be regenerated in order to be correct.
|
|
////////////////////////////////////////////////////////////////////
|
|
PNMImage GeoMipTerrain::
|
|
make_slope_image() {
|
|
PNMImage result (_xsize, _ysize);
|
|
result.make_grayscale();
|
|
for (int x = 0; x < _xsize; ++x) {
|
|
for (int y = 0; y < _ysize; ++y) {
|
|
LVector3f normal (get_normal(x, y));
|
|
normal.set(normal.get_x() / _root.get_sx(),
|
|
normal.get_y() / _root.get_sy(),
|
|
normal.get_z() / _root.get_sz());
|
|
normal.normalize();
|
|
result.set_gray(x, y, normal.angle_deg(LVector3f::up()) / 90.0);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// 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() {
|
|
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++) {
|
|
if (_bruteforce) {
|
|
tvector.push_back(generate_block(mx, my, 0));
|
|
} else {
|
|
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: " << total << " vs " << (_root.node()->get_num_children()) << "\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();
|
|
for (unsigned int mx = 0; mx < (_xsize - 1) / _block_size; mx++) {
|
|
pvector<unsigned short> tvector; //create temporary row
|
|
for (unsigned int my = 0; my < (_ysize - 1) / _block_size; my++) {
|
|
if(_bruteforce) {
|
|
tvector.push_back(0);
|
|
} else {
|
|
tvector.push_back(min(short(max(_min_level, lod_decide(mx, my))),
|
|
short(_max_level)));
|
|
}
|
|
}
|
|
_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;
|
|
}
|
|
|