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  */
281 double GeoMipTerrain::
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  */
316 LVector3 GeoMipTerrain::
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  */
370 void GeoMipTerrain::
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  */
400 void GeoMipTerrain::
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  */
438 bool GeoMipTerrain::
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  */
618 bool GeoMipTerrain::
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 }
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,...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This object provides a high-level interface for quickly writing a sequence of numeric values from a v...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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
int flatten_light()
Analyzes the geometry below this node and reports the number of vertices, triangles,...
Definition: nodePath.cxx:5495
bool unclean_set_num_rows(int n)
This method behaves like set_num_rows(), except the new data is not initialized.
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.
This is a convenience class to specialize ConfigVariable as a boolean type.
float get_gray(int x, int y) const
Returns the gray component color at the indicated pixel.
Definition: pnmImage.I:785
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This is the base class of a family of classes that represent particular image file types that PNMImag...
Definition: pnmFileType.h:32
bool read(const Filename &filename, PNMFileType *type=nullptr, bool report_unknown_type=true)
Reads the indicated image filename.
Definition: pnmImage.cxx:278
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void generate()
(Re)generates the entire terrain, erasing the current.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
int get_y_size() const
Returns the number of pixels in the Y direction.
int get_x_size() const
Returns the number of pixels in the X direction.
void clear_read_size()
Undoes the effect of a previous call to set_read_size().
Definition: pnmImage.I:209
void set_data2(PN_stdfloat x, PN_stdfloat y)
Sets the write row to a particular 2-component value, and advances the write row.
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,...
get_num_children
Returns the number of child nodes this node has.
Definition: pandaNode.h:124
get_maxval
Returns the maximum channel value allowable for any pixel in this image; for instance,...
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:904
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
double get_elevation(double x, double y)
Fetches the elevation at (x, y), where the input coordinate is specified in pixels.
LColorf get_xel_a(int x, int y) const
Returns the RGBA color at the indicated pixel.
Definition: pnmImage.I:594
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
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
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.
get_name
Returns the name of the referenced node.
Definition: nodePath.h:946
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:836
void copy_header_from(const PNMImageHeader &header)
Copies just the header information into this image.
Definition: pnmImage.cxx:200
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This defines the actual numeric vertex data stored in a Geom, in the structure defined by a particula...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A container for geometry primitives.
Definition: geom.h:54
LVector3 get_normal(int x, int y)
Fetches the terrain normal at (x, y), where the input coordinate is specified in pixels.
This class defines the physical layout of the vertex data stored within a Geom.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
int flatten_strong()
The strongest possible flattening.
Definition: nodePath.cxx:5543
PandaNode * node() const
Returns the referenced node of the path.
Definition: nodePath.I:227
This describes the structure of a single array within a Geom data.
get_color_space
Returns the color space that the image is encoded in, or CS_unspecified if unknown.
Defines a series of disconnected triangles.
Definition: geomTriangles.h:23
This is the base class of PNMImage, PNMReader, and PNMWriter.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void set_maxval(xelval maxval)
Rescales the image to the indicated maxval.
Definition: pnmImage.cxx:794
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
bool set_heightfield(const Filename &filename, PNMFileType *type=nullptr)
Loads the specified heightmap image file into the heightfield.
void make_grayscale()
Converts the image from RGB to grayscale.
Definition: pnmImage.I:291
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:199
PT(GeomNode) GeoMipTerrain
Generates a chunk of terrain based on the level specified.
int flatten_medium()
A more thorough flattening than flatten_light(), this first applies all the transforms,...
Definition: nodePath.cxx:5515
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:522
NodePath is the fundamental system for disambiguating instances, and also provides a higher-level int...
Definition: nodePath.h:161
A node that holds Geom objects, renderable pieces of geometry.
Definition: geomNode.h:34
void add_geom(Geom *geom, const RenderState *state=RenderState::make_empty())
Adds a new Geom to the node.
Definition: geomNode.cxx:584
PNMImage make_slope_image()
Returns a new grayscale image containing the slope angles.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.