Panda3D
geoMipTerrain.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file geoMipTerrain.cxx
10  * @author rdb
11  * @date 2007-06-29
12  */
13 
14 #include "geoMipTerrain.h"
15 
16 #include "geomVertexFormat.h"
17 #include "geomVertexArrayFormat.h"
18 #include "internalName.h"
19 #include "geomVertexData.h"
20 #include "geomVertexWriter.h"
21 #include "geomTristrips.h"
22 #include "geomTriangles.h"
23 #include "geom.h"
24 #include "geomNode.h"
25 #include "boundingBox.h"
26 #include "config_grutil.h"
27 
28 #include "sceneGraphReducer.h"
29 
30 #include "collideMask.h"
31 
32 using std::max;
33 using std::min;
34 
35 static ConfigVariableBool geomipterrain_incorrect_normals
36 ("geomipterrain-incorrect-normals", false,
37  PRC_DESC("If true, uses the incorrect normal vector calculation that "
38  "was used in Panda3D versions 1.9.0 and earlier. If false, "
39  "uses the correct calculation. For backward compatibility, "
40  "the default value is true in 1.9 releases, and false in "
41  "Panda3D 1.10.0 and above."));
42 
43 TypeHandle GeoMipTerrain::_type_handle;
44 
45 /**
46  * Generates a chunk of terrain based on the level specified. As arguments it
47  * takes the x and y coords of the mipmap to be generated, and the level of
48  * detail. T-Junctions for neighbor-mipmaps with different levels are also
49  * taken into account.
50  */
51 PT(GeomNode) GeoMipTerrain::
52 generate_block(unsigned short mx,
53  unsigned short my,
54  unsigned short level) {
55 
56  nassertr(mx < (_xsize - 1) / _block_size, nullptr);
57  nassertr(my < (_ysize - 1) / _block_size, nullptr);
58 
59  unsigned short center = _block_size / 2;
60  unsigned int vcounter = 0;
61 
62  // Create the format
64  if (_has_color_map) {
65  array->add_column(InternalName::get_color(), 4,
66  Geom::NT_stdfloat, Geom::C_color);
67  }
68  array->add_column(InternalName::get_vertex(), 3,
69  Geom::NT_stdfloat, Geom::C_point);
70  array->add_column(InternalName::get_texcoord(), 2,
71  Geom::NT_stdfloat, Geom::C_texcoord);
72  array->add_column(InternalName::get_normal(), 3,
73  Geom::NT_stdfloat, Geom::C_normal);
74 
75  PT(GeomVertexFormat) format = new GeomVertexFormat();
76  format->add_array(array);
77 
78  // Create vertex data and writers
79  PT(GeomVertexData) vdata = new GeomVertexData(_root.get_name(),
80  GeomVertexFormat::register_format(format), Geom::UH_stream);
81  vdata->unclean_set_num_rows((_block_size + 1) * (_block_size + 1));
82  GeomVertexWriter cwriter;
83  if (_has_color_map) {
84  cwriter = GeomVertexWriter(vdata, InternalName::get_color());
85  }
86  GeomVertexWriter vwriter (vdata, InternalName::get_vertex());
87  GeomVertexWriter twriter (vdata, InternalName::get_texcoord());
88  GeomVertexWriter nwriter (vdata, InternalName::get_normal());
89  PT(GeomTriangles) prim = new GeomTriangles(Geom::UH_stream);
90 
91  if (_bruteforce) {
92  // LOD Level when rendering bruteforce is always 0 (no lod) Unless a
93  // minlevel is set- this is handled later.
94  level = 0;
95  }
96 
97  // Do some calculations with the level
98  level = min(max(_min_level, level), _max_level);
99  unsigned short reallevel = level;
100  level = int(pow(2.0, int(level)));
101 
102  // Neighbor levels and junctions
103  unsigned short lnlevel = get_neighbor_level(mx, my, -1, 0);
104  unsigned short rnlevel = get_neighbor_level(mx, my, 1, 0);
105  unsigned short bnlevel = get_neighbor_level(mx, my, 0, -1);
106  unsigned short tnlevel = get_neighbor_level(mx, my, 0, 1);
107  bool ljunction = (lnlevel != reallevel);
108  bool rjunction = (rnlevel != reallevel);
109  bool bjunction = (bnlevel != reallevel);
110  bool tjunction = (tnlevel != reallevel);
111 
112  // Confusing note: the variable level contains not the actual level as
113  // described in the GeoMipMapping paper. That is stored in reallevel, while
114  // the variable level contains 2^reallevel.
115 
116  // This is the number of vertices at the certain level.
117  unsigned short lowblocksize = _block_size / level + 1;
118 
119  PN_stdfloat cmap_xratio = _color_map.get_x_size() / (PN_stdfloat)_xsize;
120  PN_stdfloat cmap_yratio = _color_map.get_y_size() / (PN_stdfloat)_ysize;
121 
122  PN_stdfloat tc_xscale = 1.0f / PN_stdfloat(_xsize - 1);
123  PN_stdfloat tc_yscale = 1.0f / PN_stdfloat(_ysize - 1);
124 
125  for (int x = 0; x <= _block_size; x++) {
126  for (int y = 0; y <= _block_size; y++) {
127  if ((x % level) == 0 && (y % level) == 0) {
128  if (_has_color_map) {
129  LVecBase4f color = _color_map.get_xel_a(
130  int((mx * _block_size + x) * cmap_xratio),
131  int((my * _block_size + y) * cmap_yratio));
132  cwriter.set_data4f(color);
133  }
134  vwriter.set_data3(x - 0.5 * _block_size, y - 0.5 * _block_size, get_pixel_value(mx, my, x, y));
135  twriter.set_data2((mx * _block_size + x) * tc_xscale,
136  (my * _block_size + y) * tc_yscale);
137  nwriter.set_data3(get_normal(mx, my, x, y));
138 
139  if (x > 0 && y > 0) {
140  // Left border
141  if (x == level && ljunction) {
142  if (y > level && y < _block_size) {
143  prim->add_vertex(min(max(sfav(y / level, lnlevel, reallevel), 0), lowblocksize - 1));
144  prim->add_vertex(vcounter - 1);
145  prim->add_vertex(vcounter);
146  prim->close_primitive();
147  }
148  if (f_part((y / level) / PN_stdfloat(pow(2.0, int(lnlevel - reallevel)))) == 0.5) {
149  prim->add_vertex(min(max(sfav(y / level + 1, lnlevel, reallevel), 0), lowblocksize - 1));
150  prim->add_vertex(min(max(sfav(y / level - 1, lnlevel, reallevel), 0), lowblocksize - 1));
151  prim->add_vertex(vcounter);
152  prim->close_primitive();
153  }
154  } else if (
155  (!(bjunction && y == level && x > level && x < _block_size) &&
156  !(rjunction && x == _block_size) &&
157  !(tjunction && y == _block_size && x > level && x < _block_size))) {
158  if ((x <= center && y <= center) || (x > center && y > center)) {
159  if (x > center) {
160  prim->add_vertex(vcounter - lowblocksize - 1);
161  prim->add_vertex(vcounter - 1);
162  prim->add_vertex(vcounter);
163  } else {
164  prim->add_vertex(vcounter);
165  prim->add_vertex(vcounter - lowblocksize);
166  prim->add_vertex(vcounter - lowblocksize - 1);
167  }
168  } else {
169  if (x > center) {
170  prim->add_vertex(vcounter);
171  prim->add_vertex(vcounter - lowblocksize);
172  prim->add_vertex(vcounter - 1);
173  } else {
174  prim->add_vertex(vcounter - 1);
175  prim->add_vertex(vcounter - lowblocksize);
176  prim->add_vertex(vcounter - lowblocksize - 1);
177  }
178  }
179  prim->close_primitive();
180  }
181  // Right border
182  if (x == _block_size - level && rjunction) {
183  if (y > level && y < _block_size) {
184  prim->add_vertex(lowblocksize * (lowblocksize - 1) + min(max(sfav(y / level, rnlevel, reallevel), 0), lowblocksize - 1));
185  prim->add_vertex(vcounter);
186  prim->add_vertex(vcounter - 1);
187  prim->close_primitive();
188  }
189  if (f_part((y / level) / PN_stdfloat(pow(2.0, int(rnlevel - reallevel)))) == 0.5) {
190  prim->add_vertex(lowblocksize * (lowblocksize - 1) + min(max(sfav(y / level - 1, rnlevel, reallevel), 0), lowblocksize - 1));
191  prim->add_vertex(lowblocksize * (lowblocksize - 1) + min(max(sfav(y / level + 1, rnlevel, reallevel), 0), lowblocksize - 1));
192  prim->add_vertex(vcounter);
193  prim->close_primitive();
194  }
195  }
196  // Bottom border
197  if (y == level && bjunction) {
198  if (x > level && x < _block_size) {
199  prim->add_vertex(vcounter);
200  prim->add_vertex(vcounter - lowblocksize);
201  prim->add_vertex(min(max(sfav(x / level, bnlevel, reallevel), 0), lowblocksize - 1) * lowblocksize);
202  prim->close_primitive();
203  }
204  if (f_part((x / level) / PN_stdfloat(pow(2.0, int(bnlevel - reallevel)))) == 0.5) {
205  prim->add_vertex(min(max(sfav(x / level - 1, bnlevel, reallevel), 0), lowblocksize - 1) * lowblocksize);
206  prim->add_vertex(min(max(sfav(x / level + 1, bnlevel, reallevel), 0), lowblocksize - 1) * lowblocksize);
207  prim->add_vertex(vcounter);
208  prim->close_primitive();
209  }
210  } else if (
211  (!(ljunction && x == level && y > level && y < _block_size) &&
212  !(tjunction && y == _block_size) &&
213  !(rjunction && x == _block_size && y > level && y < _block_size))) {
214  if ((x <= center && y <= center) || (x > center && y > center)) {
215  if (y > center) {
216  prim->add_vertex(vcounter);
217  prim->add_vertex(vcounter - lowblocksize);//
218  prim->add_vertex(vcounter - lowblocksize - 1);
219  } else {
220  prim->add_vertex(vcounter - lowblocksize - 1);
221  prim->add_vertex(vcounter - 1);//
222  prim->add_vertex(vcounter);
223  }
224  } else {
225  if (y > center) {
226  prim->add_vertex(vcounter);//
227  prim->add_vertex(vcounter - lowblocksize);
228  prim->add_vertex(vcounter - 1);
229  } else {
230  prim->add_vertex(vcounter - 1);
231  prim->add_vertex(vcounter - lowblocksize);
232  prim->add_vertex(vcounter - lowblocksize - 1);//
233  }
234  }
235  prim->close_primitive();
236  }
237  // Top border
238  if (y == _block_size - level && tjunction) {
239  if (x > level && x < _block_size) {
240  prim->add_vertex(min(max(sfav(x / level, tnlevel, reallevel), 0), lowblocksize - 1) * lowblocksize + lowblocksize - 1);
241  prim->add_vertex(vcounter - lowblocksize);
242  prim->add_vertex(vcounter);
243  prim->close_primitive();
244  }
245  if (f_part((x / level) / PN_stdfloat(pow(2.0, int(tnlevel - reallevel)))) == 0.5) {
246  prim->add_vertex(min(max(sfav(x / level + 1, tnlevel, reallevel), 0), lowblocksize - 1) * lowblocksize + lowblocksize - 1);
247  prim->add_vertex(min(max(sfav(x / level - 1, tnlevel, reallevel), 0), lowblocksize - 1) * lowblocksize + lowblocksize - 1);
248  prim->add_vertex(vcounter);
249  prim->close_primitive();
250  }
251  }
252  }
253  vcounter++;
254  }
255  }
256  }
257 
258  PT(Geom) geom = new Geom(vdata);
259  geom->add_primitive(prim);
260  geom->set_bounds_type(BoundingVolume::BT_box);
261 
262  std::ostringstream sname;
263  sname << "gmm" << mx << "x" << my;
264  PT(GeomNode) node = new GeomNode(sname.str());
265  node->add_geom(geom);
266  node->set_bounds_type(BoundingVolume::BT_box);
267  _old_levels.at(mx).at(my) = reallevel;
268 
269  return node;
270 }
271 
272 /**
273  * Fetches the elevation at (x, y), where the input coordinate is specified in
274  * pixels. This ignores the current LOD level and instead provides an
275  * accurate number. Linear blending is used for non-integral coordinates.
276  * Terrain scale is NOT taken into account! To get accurate normals, please
277  * multiply this with the terrain Z scale!
278  *
279  * trueElev = terr.get_elevation(x,y) * terr.get_sz();
280  */
282 get_elevation(double x, double y) {
283  y = (_ysize - 1) - y;
284  if (x < 0.0) x = 0.0;
285  if (y < 0.0) y = 0.0;
286  unsigned int xlo = (unsigned int) x;
287  unsigned int ylo = (unsigned int) y;
288  if (xlo > _xsize - 2)
289  xlo = _xsize - 2;
290  if (ylo > _ysize - 2)
291  ylo = _ysize - 2;
292  unsigned int xhi = xlo + 1;
293  unsigned int yhi = ylo + 1;
294  double xoffs = x - xlo;
295  double yoffs = y - ylo;
296  double grayxlyl = get_pixel_value(xlo, ylo);
297  double grayxhyl = get_pixel_value(xhi, ylo);
298  double grayxlyh = get_pixel_value(xlo, yhi);
299  double grayxhyh = get_pixel_value(xhi, yhi);
300  double lerpyl = grayxhyl * xoffs + grayxlyl * (1.0 - xoffs);
301  double lerpyh = grayxhyh * xoffs + grayxlyh * (1.0 - xoffs);
302  return lerpyh * yoffs + lerpyl * (1.0 - yoffs);
303 }
304 
305 /**
306  * Fetches the terrain normal at (x, y), where the input coordinate is
307  * specified in pixels. This ignores the current LOD level and instead
308  * provides an accurate number. Terrain scale is NOT taken into account! To
309  * get accurate normals, please divide it by the terrain scale and normalize
310  * it again, like this:
311  *
312  * LVector3 normal (terr.get_normal(x, y)); normal.set(normal.get_x() /
313  * root.get_sx(), normal.get_y() / root.get_sy(), normal.get_z() /
314  * root.get_sz()); normal.normalize();
315  */
317 get_normal(int x, int y) {
318  int nx = x - 1;
319  int px = x + 1;
320  int ny = y - 1;
321  int py = y + 1;
322  if (nx < 0) nx++;
323  if (ny < 0) ny++;
324  if (px >= int(_xsize)) px--;
325  if (py >= int(_ysize)) py--;
326  double drx = get_pixel_value(nx, y) - get_pixel_value(px, y);
327  double dry = get_pixel_value(x, py) - get_pixel_value(x, ny);
328  LVector3 normal(drx * 0.5, dry * 0.5, 1);
329  normal.normalize();
330 
331  if (geomipterrain_incorrect_normals) {
332  normal[0] = -normal[0];
333  }
334 
335  return normal;
336 }
337 
338 /**
339  * Returns a new grayscale image containing the slope angles. A white pixel
340  * value means a vertical slope, while a black pixel will mean that the
341  * terrain is entirely flat at that pixel. You can translate it to degrees by
342  * mapping the greyscale values from 0 to 90 degrees. The resulting image
343  * will have the same size as the heightfield image. The scale will be taken
344  * into respect -- meaning, if you change the terrain scale, the slope image
345  * will need to be regenerated in order to be correct.
346  */
349  PNMImage result (_xsize, _ysize);
350  result.make_grayscale();
351  for (unsigned int x = 0; x < _xsize; ++x) {
352  for (unsigned int y = 0; y < _ysize; ++y) {
353  LVector3 normal (get_normal(x, y));
354  normal.set(normal.get_x() / _root.get_sx(),
355  normal.get_y() / _root.get_sy(),
356  normal.get_z() / _root.get_sz());
357  normal.normalize();
358  result.set_xel(x, y, normal.angle_deg(LVector3::up()) / 90.0);
359  }
360  }
361  return result;
362 }
363 
364 /**
365  * Calculates an approximate for the ambient occlusion and stores it in the
366  * color map, so that it will be written to the vertex colors. Any existing
367  * color map will be discarded. You need to call this before generating the
368  * geometry.
369  */
371 calc_ambient_occlusion(PN_stdfloat radius, PN_stdfloat contrast, PN_stdfloat brightness) {
372  _color_map = PNMImage(_xsize, _ysize);
373  _color_map.make_grayscale();
374  _color_map.set_maxval(_heightfield.get_maxval());
375 
376  for (unsigned int x = 0; x < _xsize; ++x) {
377  for (unsigned int y = 0; y < _ysize; ++y) {
378  _color_map.set_xel(x, _ysize - y - 1, get_pixel_value(x, y));
379  }
380  }
381 
382  // We use the cheap old method of subtracting a blurred version of the
383  // heightmap from the heightmap, and using that as lightmap.
384  _color_map.gaussian_filter(radius);
385 
386  for (unsigned int x = 0; x < _xsize; ++x) {
387  for (unsigned int y = 0; y < _ysize; ++y) {
388  _color_map.set_xel(x, y, (get_pixel_value(x, _ysize - y - 1) - _color_map.get_gray(x, y)) * contrast + brightness);
389  }
390  }
391 
392  _has_color_map = true;
393 }
394 
395 /**
396  * (Re)generates the entire terrain, erasing the current. This call un-
397  * flattens the terrain, so make sure you have set auto-flatten if you want to
398  * keep your terrain flattened.
399  */
401 generate() {
402  if (_xsize < 3 || _ysize < 3) {
403  grutil_cat.error() << "No valid heightfield image has been set!\n";
404  return;
405  }
406  calc_levels();
407  _root.node()->remove_all_children();
408  _blocks.clear();
409  _old_levels.clear();
410  _old_levels.resize(int((_xsize - 1) / _block_size));
411  _root_flattened = false;
412  for (unsigned int mx = 0; mx < (_xsize - 1) / _block_size; mx++) {
413  _old_levels[mx].resize(int((_ysize - 1) / _block_size));
414  pvector<NodePath> tvector; // Create temporary row
415  for (unsigned int my = 0; my < (_ysize - 1) / _block_size; my++) {
416  if (_bruteforce) {
417  tvector.push_back(_root.attach_new_node(generate_block(mx, my, 0)));
418  } else {
419  tvector.push_back(_root.attach_new_node(generate_block(mx, my, _levels[mx][my])));
420  }
421  tvector[my].set_pos((mx + 0.5) * _block_size, (my + 0.5) * _block_size, 0);
422  }
423  _blocks.push_back(tvector); // Push the new row of NodePaths into the 2d vect
424  tvector.clear();
425  }
426  auto_flatten();
427  _is_dirty = false;
428 }
429 
430 /**
431  * Loops through all of the terrain blocks, and checks whether they need to be
432  * updated. If that is indeed the case, it regenerates the mipmap. Returns a
433  * true when the terrain has changed. Returns false when the terrain isn't
434  * updated at all. If there is no terrain yet, it generates the entire
435  * terrain. This call un-flattens the terrain, so make sure you have set
436  * auto-flatten if you want to keep your terrain flattened.
437  */
439 update() {
440  if (_xsize < 3 || _ysize < 3) {
441  grutil_cat.error() << "No valid heightfield image has been set!\n";
442  return false;
443  }
444  if (_is_dirty) {
445  generate();
446  return true;
447  } else if (!_bruteforce) {
448  calc_levels();
449  if (root_flattened()) {
450  _root.node()->remove_all_children();
451  unsigned int xsize = _blocks.size();
452  for (unsigned int tx = 0; tx < xsize; tx++) {
453  unsigned int ysize = _blocks[tx].size();
454  for (unsigned int ty = 0;ty < ysize; ty++) {
455  _blocks[tx][ty].reparent_to(_root);
456  }
457  }
458  _root_flattened = false;
459  }
460  bool returnVal = false;
461  for (unsigned int mx = 0; mx < (_xsize - 1) / _block_size; mx++) {
462  for (unsigned int my = 0; my < (_ysize - 1) / _block_size; my++) {
463  bool isUpd (update_block(mx, my));
464  if (isUpd && mx > 0 && _old_levels[mx - 1][my] == _levels[mx - 1][my]) {
465  if (update_block(mx - 1, my, -1, true)) {
466  returnVal = true;
467  }
468  }
469  if (isUpd && mx < (_ysize - 1)/_block_size - 1
470  && _old_levels[mx + 1][my] == _levels[mx + 1][my]) {
471  if (update_block(mx + 1, my, -1, true)) {
472  returnVal = true;
473  }
474  }
475  if (isUpd && my > 0 && _old_levels[mx][my - 1] == _levels[mx][my - 1]) {
476  if (update_block(mx, my - 1, -1, true)) {
477  returnVal = true;
478  }
479  }
480  if (isUpd && my < (_ysize - 1)/_block_size - 1
481  && _old_levels[mx][my + 1] == _levels[mx][my + 1]) {
482  if (update_block(mx, my + 1, -1, true)) {
483  returnVal = true;
484  }
485  }
486  if (isUpd) {
487  returnVal = true;
488  }
489  }
490  }
491  auto_flatten();
492  return returnVal;
493  }
494  return false;
495 }
496 
497 /**
498  * Normally, the root's children are the terrain blocks. However, if we call
499  * flatten_strong on the root, then the root will contain unpredictable stuff.
500  * This function returns true if the root has been flattened, and therefore,
501  * does not contain the terrain blocks.
502  */
503 bool GeoMipTerrain::
504 root_flattened() {
505  if (_root_flattened) {
506  return true;
507  }
508 
509  // The following code is error-checking code. It actually verifies that the
510  // terrain blocks are underneath the root, and that nothing else is
511  // underneath the root. It is not very efficient, and should eventually be
512  // removed once we're sure everything works.
513 
514  int total = 0;
515  unsigned int xsize = _blocks.size();
516  for (unsigned int tx = 0; tx < xsize; tx++) {
517  unsigned int ysize = _blocks[tx].size();
518  for (unsigned int ty = 0;ty < ysize; ty++) {
519  if (_blocks[tx][ty].get_node(1) != _root.node()) {
520  grutil_cat.error() << "GeoMipTerrain: root node unexpectedly mangled!\n";
521  return true;
522  }
523  total += 1;
524  }
525  }
526  if (total != _root.node()->get_num_children()) {
527  grutil_cat.error() << "GeoMipTerrain: root node unexpectedly mangled: " << total << " vs " << (_root.node()->get_num_children()) << "\n";
528  return true;
529  }
530 
531  // The default.
532  return false;
533 }
534 
535 /**
536  * Flattens the geometry under the root.
537  */
538 void GeoMipTerrain::
539 auto_flatten() {
540  if (_auto_flatten == AFM_off) {
541  return;
542  }
543 
544  // Creating a backup node causes the SceneGraphReducer to operate in a
545  // nondestructive manner. This protects the terrain blocks themselves from
546  // the flattener.
547 
548  NodePath np("Backup Node");
549  np.node()->copy_children(_root.node());
550 
551  // Check if the root's children have changed unexpectedly.
552  switch(_auto_flatten) {
553  case AFM_light: _root.flatten_light(); break;
554  case AFM_medium: _root.flatten_medium(); break;
555  case AFM_strong: _root.flatten_strong(); break;
556  }
557 
558  _root_flattened = true;
559 }
560 
561 /**
562  * Loops through all of the terrain blocks, and calculates on what level they
563  * should be generated.
564  */
565 void GeoMipTerrain::
566 calc_levels() {
567  nassertv(_xsize >= 3 && _ysize >= 3);
568  _levels.clear();
569  for (unsigned int mx = 0; mx < (_xsize - 1) / _block_size; mx++) {
570  pvector<unsigned short> tvector; //create temporary row
571  for (unsigned int my = 0; my < (_ysize - 1) / _block_size; my++) {
572  if(_bruteforce) {
573  tvector.push_back(0);
574  } else {
575  tvector.push_back(min(short(max(_min_level, lod_decide(mx, my))),
576  short(_max_level)));
577  }
578  }
579  _levels.push_back(tvector); //push the new row of levels into the 2d vector
580  tvector.clear();
581  }
582 }
583 
584 /**
585  * Checks whether the specified mipmap at (mx,my) needs to be updated, if so,
586  * it regenerates the mipmap. Returns a true when it has generated a mipmap.
587  * Returns false when the mipmap is already at the desired level, or when
588  * there is no terrain to update. Note: This does not affect neighboring
589  * blocks, so does NOT fix t-junctions. You will have to fix that by forced
590  * updating the neighboring chunks as well, with the same levels. NOTE: do
591  * NOT call this when the terrain is marked dirty. If the terrain is dirty,
592  * you will need to call update() or generate() first. You can check this by
593  * calling GeoMipTerrain::is_dirty().
594  */
595 bool GeoMipTerrain::
596 update_block(unsigned short mx, unsigned short my,
597  signed short level, bool forced) {
598  nassertr_always(!_is_dirty, false);
599  nassertr_always(mx < (_xsize - 1) / _block_size, false);
600  nassertr_always(my < (_ysize - 1) / _block_size, false);
601  if (level < 0) {
602  level = _levels[mx][my];
603  }
604  level = min(max(_min_level, (unsigned short) level), _max_level);
605  if (forced || _old_levels[mx][my] != level) { // If the level has changed...
606  // Replaces the chunk with a regenerated one.
607  generate_block(mx, my, level)->replace_node(_blocks[mx][my].node());
608  return true;
609  }
610  return false;
611 }
612 
613 /**
614  * Loads the specified heightmap image file into the heightfield. Returns
615  * true if succeeded, or false if an error has occured. If the heightmap is
616  * not a power of two plus one, it is scaled up using a gaussian filter.
617  */
619 set_heightfield(const Filename &filename, PNMFileType *ftype) {
620  // First, we need to load the header to determine the size and format.
621  PNMImageHeader imgheader;
622  if (imgheader.read_header(filename, ftype)) {
623  // Copy over the header to the heightfield image.
624  _heightfield.copy_header_from(imgheader);
625 
626  if (!is_power_of_two(imgheader.get_x_size() - 1) ||
627  !is_power_of_two(imgheader.get_y_size() - 1)) {
628  // Calculate the nearest power-of-two-plus-one size.
629  unsigned int reqx, reqy;
630  reqx = max(3, (int) pow(2.0, ceil(log((double) max(2, imgheader.get_x_size() - 1)) / log(2.0))) + 1);
631  reqy = max(3, (int) pow(2.0, ceil(log((double) max(2, imgheader.get_y_size() - 1)) / log(2.0))) + 1);
632 
633  // If it's not a valid size, tell PNMImage to resize it.
634  if (reqx != (unsigned int)imgheader.get_x_size() ||
635  reqy != (unsigned int)imgheader.get_y_size()) {
636  grutil_cat.warning()
637  << "Rescaling heightfield image " << filename
638  << " from " << imgheader.get_x_size() << "x" << imgheader.get_y_size()
639  << " to " << reqx << "x" << reqy << " pixels.\n";
640  _heightfield.set_read_size(reqx, reqy);
641  }
642  }
643 
644  if (imgheader.get_color_space() == CS_sRGB) {
645  // Probably a mistaken metadata setting on the file.
646  grutil_cat.warning()
647  << "Heightfield image is specified to have sRGB color space!\n"
648  "Panda applies gamma correction, which will probably cause "
649  "it to produce incorrect results.\n";
650  }
651 
652  // Read the real image now
653  if (!_heightfield.read(filename, ftype)) {
654  _heightfield.clear_read_size();
655  grutil_cat.error() << "Failed to read heightfield image " << filename << "!\n";
656  return false;
657  }
658 
659  _is_dirty = true;
660  _xsize = _heightfield.get_x_size();
661  _ysize = _heightfield.get_y_size();
662  return true;
663  } else {
664  grutil_cat.error() << "Failed to load heightfield image " << filename << "!\n";
665  }
666  return false;
667 }
668 
669 /**
670  * Helper function for generate().
671  */
672 unsigned short GeoMipTerrain::
673 get_neighbor_level(unsigned short mx, unsigned short my, short dmx, short dmy) {
674  // If we're across the terrain border, check if we want stitching. If not,
675  // return the same level as this one - it won't have to make junctions.
676  if ((int)mx + (int)dmx < 0 || (int)mx + (int)dmx >= ((int)_xsize - 1) / (int)_block_size ||
677  (int)my + (int)dmy < 0 || (int)my + (int)dmy >= ((int)_ysize - 1) / (int)_block_size) {
678  return (_stitching) ? _max_level : min(max(_min_level, _levels[mx][my]), _max_level);
679  }
680  // If we're rendering bruteforce, the level must be the same as this one.
681  if (_bruteforce) {
682  return min(max(_min_level, _levels[mx][my]), _max_level);
683  }
684  // Only if the level is higher than the current. Otherwise, the junctions
685  // will be made for the other chunk.
686  if (_levels[mx + dmx][my + dmy] > _levels[mx][my]) {
687  return min(max(_min_level, _levels[mx + dmx][my + dmy]), _max_level);
688  } else {
689  return min(max(_min_level, _levels[mx][my]), _max_level);
690  }
691 }
PandaNode::remove_all_children
void remove_all_children(Thread *current_thread=Thread::get_current_thread())
Removes all the children from the node at once, including stashed children.
Definition: pandaNode.cxx:830
Geom
A container for geometry primitives.
Definition: geom.h:54
geomVertexData.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
ConfigVariableBool
This is a convenience class to specialize ConfigVariable as a boolean type.
Definition: configVariableBool.h:23
PNMImage::copy_header_from
void copy_header_from(const PNMImageHeader &header)
Copies just the header information into this image.
Definition: pnmImage.cxx:200
GeoMipTerrain::generate
void generate()
(Re)generates the entire terrain, erasing the current.
Definition: geoMipTerrain.cxx:401
PNMImage::set_maxval
void set_maxval(xelval maxval)
Rescales the image to the indicated maxval.
Definition: pnmImage.cxx:794
geomVertexWriter.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
GeoMipTerrain::make_slope_image
PNMImage make_slope_image()
Returns a new grayscale image containing the slope angles.
Definition: geoMipTerrain.cxx:348
internalName.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
pvector< NodePath >
GeomVertexData
This defines the actual numeric vertex data stored in a Geom, in the structure defined by a particula...
Definition: geomVertexData.h:68
PNMImageHeader::read_header
bool read_header(const Filename &filename, PNMFileType *type=nullptr, bool report_unknown_type=true)
Opens up the image file and tries to read its header information to determine its size,...
Definition: pnmImageHeader.cxx:33
GeomVertexWriter::set_data4f
void set_data4f(float x, float y, float z, float w)
Sets the write row to a particular 4-component value, and advances the write row.
Definition: geomVertexWriter.I:399
PT
PT(GeomNode) GeoMipTerrain
Generates a chunk of terrain based on the level specified.
Definition: geoMipTerrain.cxx:51
sceneGraphReducer.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
GeoMipTerrain::set_heightfield
bool set_heightfield(const Filename &filename, PNMFileType *type=nullptr)
Loads the specified heightmap image file into the heightfield.
Definition: geoMipTerrain.cxx:619
PNMImageHeader::get_maxval
get_maxval
Returns the maximum channel value allowable for any pixel in this image; for instance,...
Definition: pnmImageHeader.h:70
geoMipTerrain.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
GeomVertexWriter
This object provides a high-level interface for quickly writing a sequence of numeric values from a v...
Definition: geomVertexWriter.h:55
PNMImage::get_xel_a
LColorf get_xel_a(int x, int y) const
Returns the RGBA color at the indicated pixel.
Definition: pnmImage.I:608
geomVertexArrayFormat.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
NodePath::flatten_light
int flatten_light()
Analyzes the geometry below this node and reports the number of vertices, triangles,...
Definition: nodePath.cxx:5510
PNMImage::read
bool read(const Filename &filename, PNMFileType *type=nullptr, bool report_unknown_type=true)
Reads the indicated image filename.
Definition: pnmImage.cxx:278
PNMImage
The name of this class derives from the fact that we originally implemented it as a layer on top of t...
Definition: pnmImage.h:58
GeoMipTerrain::calc_ambient_occlusion
void calc_ambient_occlusion(PN_stdfloat radius=32, PN_stdfloat contrast=2.0f, PN_stdfloat brightness=0.75f)
Calculates an approximate for the ambient occlusion and stores it in the color map,...
Definition: geoMipTerrain.cxx:371
PNMImageHeader::get_x_size
int get_x_size() const
Returns the number of pixels in the X direction.
Definition: pnmImageHeader.I:144
PNMImage::make_grayscale
void make_grayscale()
Converts the image from RGB to grayscale.
Definition: pnmImage.I:380
TypeHandle
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
GeomNode
A node that holds Geom objects, renderable pieces of geometry.
Definition: geomNode.h:34
PNMImageHeader
This is the base class of PNMImage, PNMReader, and PNMWriter.
Definition: pnmImageHeader.h:40
PNMImageHeader::get_y_size
int get_y_size() const
Returns the number of pixels in the Y direction.
Definition: pnmImageHeader.I:153
collideMask.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
geomTriangles.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
NodePath::flatten_strong
int flatten_strong()
The strongest possible flattening.
Definition: nodePath.cxx:5558
geom.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
GeomVertexFormat
This class defines the physical layout of the vertex data stored within a Geom.
Definition: geomVertexFormat.h:55
GeomTriangles
Defines a series of disconnected triangles.
Definition: geomTriangles.h:23
NodePath
NodePath is the fundamental system for disambiguating instances, and also provides a higher-level int...
Definition: nodePath.h:159
NodePath::get_name
get_name
Returns the name of the referenced node.
Definition: nodePath.h:945
PNMImage::clear_read_size
void clear_read_size()
Undoes the effect of a previous call to set_read_size().
Definition: pnmImage.I:298
GeoMipTerrain::update
bool update()
Loops through all of the terrain blocks, and checks whether they need to be updated.
Definition: geoMipTerrain.cxx:439
GeoMipTerrain::get_normal
LVector3 get_normal(int x, int y)
Fetches the terrain normal at (x, y), where the input coordinate is specified in pixels.
Definition: geoMipTerrain.cxx:317
PNMImage::get_gray
float get_gray(int x, int y) const
Returns the gray component color at the indicated pixel.
Definition: pnmImage.I:799
PNMImage::gaussian_filter
void gaussian_filter(float radius=1.0)
This flavor of gaussian_filter() will apply the filter over the entire image without resizing or copy...
Definition: pnmImage.I:918
PNMImage::set_xel
void set_xel(int x, int y, const LRGBColorf &value)
Changes the RGB color at the indicated pixel.
Definition: pnmImage.I:579
config_grutil.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
GeomVertexWriter::set_data2
void set_data2(PN_stdfloat x, PN_stdfloat y)
Sets the write row to a particular 2-component value, and advances the write row.
Definition: geomVertexWriter.I:610
NodePath::flatten_medium
int flatten_medium()
A more thorough flattening than flatten_light(), this first applies all the transforms,...
Definition: nodePath.cxx:5530
GeomVertexWriter::set_data3
void set_data3(PN_stdfloat x, PN_stdfloat y, PN_stdfloat z)
Sets the write row to a particular 3-component value, and advances the write row.
Definition: geomVertexWriter.I:640
geomTristrips.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
NodePath::attach_new_node
NodePath attach_new_node(PandaNode *node, int sort=0, Thread *current_thread=Thread::get_current_thread()) const
Attaches a new node, with or without existing parents, to the scene graph below the referenced node o...
Definition: nodePath.cxx:563
PNMImageHeader::get_color_space
get_color_space
Returns the color space that the image is encoded in, or CS_unspecified if unknown.
Definition: pnmImageHeader.h:71
geomNode.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
geomVertexFormat.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PandaNode::get_num_children
get_num_children
Returns the number of child nodes this node has.
Definition: pandaNode.h:124
NodePath::node
PandaNode * node() const
Returns the referenced node of the path.
Definition: nodePath.I:227
PNMFileType
This is the base class of a family of classes that represent particular image file types that PNMImag...
Definition: pnmFileType.h:32
PNMImage::set_read_size
void set_read_size(int x_size, int y_size)
Specifies the size to we'd like to scale the image upon reading it.
Definition: pnmImage.I:288
GeoMipTerrain::get_elevation
double get_elevation(double x, double y)
Fetches the elevation at (x, y), where the input coordinate is specified in pixels.
Definition: geoMipTerrain.cxx:282
GeomVertexArrayFormat
This describes the structure of a single array within a Geom data.
Definition: geomVertexArrayFormat.h:47
Filename
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
boundingBox.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.