Panda3D
Loading...
Searching...
No Matches
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"
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
32using std::max;
33using std::min;
34
35static 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
43TypeHandle 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 */
51PT(GeomNode) GeoMipTerrain::
52generate_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 */
282get_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 */
317get_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 */
371calc_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 */
401generate() {
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 */
439update() {
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 */
503bool GeoMipTerrain::
504root_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 */
538void GeoMipTerrain::
539auto_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 */
565void GeoMipTerrain::
566calc_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 */
595bool GeoMipTerrain::
596update_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 */
619set_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 */
672unsigned short GeoMipTerrain::
673get_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}
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This is a convenience class to specialize ConfigVariable as a boolean type.
The name of a file, such as a texture file or an Egg file.
Definition filename.h:44
double get_elevation(double x, double y)
Fetches the elevation at (x, y), where the input coordinate is specified in pixels.
bool update()
Loops through all of the terrain blocks, and checks whether they need to be updated.
bool set_heightfield(const Filename &filename, PNMFileType *type=nullptr)
Loads the specified heightmap image file into the heightfield.
PNMImage make_slope_image()
Returns a new grayscale image containing the slope angles.
LVector3 get_normal(int x, int y)
Fetches the terrain normal at (x, y), where the input coordinate is specified in pixels.
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,...
void generate()
(Re)generates the entire terrain, erasing the current.
A node that holds Geom objects, renderable pieces of geometry.
Definition geomNode.h:34
Defines a series of disconnected triangles.
This describes the structure of a single array within a Geom data.
This defines the actual numeric vertex data stored in a Geom, in the structure defined by a particula...
This class defines the physical layout of the vertex data stored within a Geom.
This object provides a high-level interface for quickly writing a sequence of numeric values from a v...
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 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.
void set_data2(PN_stdfloat x, PN_stdfloat y)
Sets the write row to a particular 2-component value, and advances the write row.
A container for geometry primitives.
Definition geom.h:54
NodePath is the fundamental system for disambiguating instances, and also provides a higher-level int...
Definition nodePath.h:159
int flatten_light()
Analyzes the geometry below this node and reports the number of vertices, triangles,...
int flatten_medium()
A more thorough flattening than flatten_light(), this first applies all the transforms,...
PandaNode * node() const
Returns the referenced node of the path.
Definition nodePath.I:227
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:599
get_name
Returns the name of the referenced node.
Definition nodePath.h:951
int flatten_strong()
The strongest possible flattening.
This is the base class of a family of classes that represent particular image file types that PNMImag...
Definition pnmFileType.h:32
This is the base class of PNMImage, PNMReader, and PNMWriter.
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,...
get_maxval
Returns the maximum channel value allowable for any pixel in this image; for instance,...
int get_x_size() const
Returns the number of pixels in the X direction.
get_color_space
Returns the color space that the image is encoded in, or CS_unspecified if unknown.
int get_y_size() const
Returns the number of pixels in the Y direction.
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
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
void set_maxval(xelval maxval)
Rescales the image to the indicated maxval.
Definition pnmImage.cxx:794
void make_grayscale()
Converts the image from RGB to grayscale.
Definition pnmImage.I:380
float get_gray(int x, int y) const
Returns the gray component color at the indicated pixel.
Definition pnmImage.I:799
LColorf get_xel_a(int x, int y) const
Returns the RGBA color at the indicated pixel.
Definition pnmImage.I:608
void copy_header_from(const PNMImageHeader &header)
Copies just the header information into this image.
Definition pnmImage.cxx:200
bool read(const Filename &filename, PNMFileType *type=nullptr, bool report_unknown_type=true)
Reads the indicated image filename.
Definition pnmImage.cxx:278
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
void set_xel(int x, int y, const LRGBColorf &value)
Changes the RGB color at the indicated pixel.
Definition pnmImage.I:579
void clear_read_size()
Undoes the effect of a previous call to set_read_size().
Definition pnmImage.I:298
void remove_all_children(Thread *current_thread=Thread::get_current_thread())
Removes all the children from the node at once, including stashed children.
get_num_children
Returns the number of child nodes this node has.
Definition pandaNode.h:124
TypeHandle is the identifier used to differentiate C++ class types.
Definition typeHandle.h:81
This is our own Panda specialization on the default STL vector.
Definition pvector.h:42
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.