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