Panda3D
shaderTerrainMesh.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 shaderTerrainMesh.cxx
10  * @author tobspr
11  * @date 2016-02-16
12  */
13 
14 
15 #include "shaderTerrainMesh.h"
16 #include "geom.h"
17 #include "geomVertexFormat.h"
18 #include "geomVertexData.h"
19 #include "geomVertexWriter.h"
20 #include "geomNode.h"
21 #include "geomTriangles.h"
22 #include "geomPatches.h"
23 #include "omniBoundingVolume.h"
24 #include "cullableObject.h"
25 #include "cullTraverser.h"
26 #include "cullHandler.h"
27 #include "cullTraverserData.h"
28 #include "clockObject.h"
29 #include "shaderAttrib.h"
30 #include "renderAttrib.h"
31 #include "shaderInput.h"
32 #include "boundingBox.h"
33 #include "samplerState.h"
34 #include "config_grutil.h"
35 #include "typeHandle.h"
36 
37 using std::endl;
38 using std::max;
39 using std::min;
40 
41 ConfigVariableBool stm_use_hexagonal_layout
42 ("stm-use-hexagonal-layout", false,
43  PRC_DESC("Set this to true to use a hexagonal vertex layout. This approximates "
44  "the heightfield in a better way, however the CLOD transitions might be "
45  "visible due to the vertices not matching exactly."));
46 
47 ConfigVariableInt stm_max_chunk_count
48 ("stm-max-chunk-count", 2048,
49  PRC_DESC("Controls the maximum amount of chunks the Terrain can display. If you use "
50  "a high LOD, you might have to increment this value. The lower this value is "
51  "the less data has to be transferred to the GPU."));
52 
53 ConfigVariableInt stm_max_views
54 ("stm-max-views", 8,
55  PRC_DESC("Controls the maximum amount of different views the Terrain can be rendered "
56  "with. Each camera rendering the terrain corresponds to a view. Lowering this "
57  "value will reduce the data that has to be transferred to the GPU."));
58 
59 ConfigVariableEnum<SamplerState::FilterType> stm_heightfield_minfilter
60 ("stm-heightfield-minfilter", SamplerState::FT_linear,
61  PRC_DESC("This specifies the minfilter that is applied for a heightfield texture. This "
62  "can be used to create heightfield that is visual correct with collision "
63  "geometry (for example bullet terrain mesh) by changing it to nearest"));
64 
65 ConfigVariableEnum<SamplerState::FilterType> stm_heightfield_magfilter
66 ("stm-heightfield-magfilter", SamplerState::FT_linear,
67  PRC_DESC("This specifies the magfilter that is applied for a heightfield texture. This "
68  "can be used to create heightfield that is visual correct with collision "
69  "geometry (for example bullet terrain mesh) by changing it to nearest"));
70 
71 PStatCollector ShaderTerrainMesh::_basic_collector("Cull:ShaderTerrainMesh:Setup");
72 PStatCollector ShaderTerrainMesh::_lod_collector("Cull:ShaderTerrainMesh:CollectLOD");
73 
74 NotifyCategoryDef(shader_terrain, "");
75 
76 TypeHandle ShaderTerrainMesh::_type_handle;
77 
78 /**
79  * @brief Helper function to check for a power of two
80  * @details This method checks for a power of two by using bitmasks
81  *
82  * @param x Number to check
83  * @return true if x is a power of two, false otherwise
84  */
85 int check_power_of_two(size_t x)
86 {
87  return ((x != 0) && ((x & (~x + 1)) == x));
88 }
89 
90 /**
91  * @brief Constructs a new Terrain Mesh
92  * @details This constructs a new terrain mesh. By default, no transform is set
93  * on the mesh, causing it to range over the unit box from (0, 0, 0) to
94  * (1, 1, 1). Usually you want to set a custom transform with NodePath::set_scale()
95  */
97  PandaNode("ShaderTerrainMesh"),
98  _size(0),
99  _chunk_size(32),
100  _generate_patches(false),
101  _data_texture(nullptr),
102  _chunk_geom(nullptr),
103  _current_view_index(0),
104  _last_frame_count(-1),
105  _target_triangle_width(10.0f),
106  _update_enabled(true),
107  _heightfield_tex(nullptr)
108 {
109  set_final(true);
111 }
112 
113 /**
114  * @brief Generates the terrain mesh
115  * @details This generates the terrain mesh, initializing all chunks of the
116  * internal used quadtree. At this point, a heightfield and a chunk size should
117  * have been set, otherwise an error is thrown.
118  *
119  * If anything goes wrong, like a missing heightfield, then an error is printed
120  * and false is returned.
121  *
122  * @return true if the terrain was initialized, false if an error occured
123  */
125  MutexHolder holder(_lock);
126  if (!do_check_heightfield())
127  return false;
128 
129  if (_chunk_size < 8 || !check_power_of_two(_chunk_size)) {
130  shader_terrain_cat.error() << "Invalid chunk size! Has to be >= 8 and a power of two!" << endl;
131  return false;
132  }
133 
134  if (_chunk_size > _size / 4) {
135  shader_terrain_cat.error() << "Chunk size too close or greater than the actual terrain size!" << endl;
136  return false;
137  }
138 
139  do_extract_heightfield();
140  do_create_chunks();
141  do_compute_bounds(&_base_chunk);
142  do_create_chunk_geom();
143  do_init_data_texture();
144 
145  // Clear image after using it, otherwise we have two copies of the heightfield
146  // in memory.
147  _heightfield.clear();
148 
149  return true;
150 }
151 
152 /**
153  * @brief Converts the internal used Texture to a PNMImage
154  * @details This converts the texture passed with set_heightfield to a PNMImage,
155  * so we can read the pixels in a fast way. This is only used while generating
156  * the chunks, and the PNMImage is destroyed afterwards.
157  */
158 void ShaderTerrainMesh::do_extract_heightfield() {
159  if (!_heightfield_tex->has_ram_image()) {
160  _heightfield_tex->reload();
161  }
162 
163  _heightfield_tex->store(_heightfield);
164 
165  if (_heightfield.get_maxval() != 65535) {
166  shader_terrain_cat.warning() << "Using non 16-bit heightfield!" << endl;
167  } else {
168  _heightfield_tex->set_format(Texture::F_r16);
169  }
170  _heightfield_tex->set_minfilter(stm_heightfield_minfilter);
171  _heightfield_tex->set_magfilter(stm_heightfield_magfilter);
172  _heightfield_tex->set_wrap_u(SamplerState::WM_clamp);
173  _heightfield_tex->set_wrap_v(SamplerState::WM_clamp);
174 }
175 
176 /**
177  * @brief Intermal method to check the heightfield
178  * @details This method cecks the heightfield generated from the heightfield texture,
179  * and performs some basic checks, including a check for a power of two,
180  * and same width and height.
181  *
182  * @return true if the heightfield meets the requirements
183  */
184 bool ShaderTerrainMesh::do_check_heightfield() {
185  if (_heightfield_tex->get_x_size() != _heightfield_tex->get_y_size()) {
186  shader_terrain_cat.error() << "Only square heightfields are supported!";
187  return false;
188  }
189 
190  _size = _heightfield_tex->get_x_size();
191  if (_size < 32 || !check_power_of_two(_size)) {
192  shader_terrain_cat.error() << "Invalid heightfield! Needs to be >= 32 and a power of two (was: "
193  << _size << ")!" << endl;
194  return false;
195  }
196 
197  return true;
198 }
199 
200 /**
201  * @brief Internal method to init the terrain data texture
202  * @details This method creates the data texture, used to store all chunk data.
203  * The data texture is set as a shader input later on, and stores the position
204  * and scale of each chunk. Every row in the data texture denotes a view on
205  * the terrain.
206  */
207 void ShaderTerrainMesh::do_init_data_texture() {
208  _data_texture = new Texture("TerrainDataTexture");
209  _data_texture->setup_2d_texture(stm_max_chunk_count, stm_max_views, Texture::T_float, Texture::F_rgba32);
210  _data_texture->set_compression(Texture::CM_off);
211  _data_texture->set_clear_color(LVector4(0));
212  _data_texture->clear_image();
213 }
214 
215 /**
216  * @brief Internal method to init the quadtree
217  * @details This method creates the base chunk and then inits all chunks recursively
218  * by using ShaderTerrainMesh::do_init_chunk().
219  */
220 void ShaderTerrainMesh::do_create_chunks() {
221 
222  // Release any previously stored children
223  _base_chunk.clear_children();
224 
225  // Create the base chunk
226  _base_chunk.depth = 0;
227  _base_chunk.x = 0;
228  _base_chunk.y = 0;
229  _base_chunk.size = _size;
230  _base_chunk.edges.set(0, 0, 0, 0);
231  _base_chunk.avg_height = 0.5;
232  _base_chunk.min_height = 0.0;
233  _base_chunk.max_height = 1.0;
234  _base_chunk.last_clod = 0.0;
235  do_init_chunk(&_base_chunk);
236 }
237 
238 /**
239  * @brief Internal method to recursively init the quadtree
240  * @details This method inits the quadtree. Starting from a given node, it
241  * first examines if that node should be subdivided.
242  *
243  * If the node should be subdivided, four children are created and this method
244  * is called on the children again. If the node is a leaf, all children are
245  * set to NULL and nothing else happens.
246  *
247  * The chunk parameter may not be zero or undefined behaviour occurs.
248  *
249  * @param chunk The parent chunk
250  */
251 void ShaderTerrainMesh::do_init_chunk(Chunk* chunk) {
252  if (chunk->size > _chunk_size) {
253 
254  // Compute children chunk size
255  size_t child_chunk_size = chunk->size / 2;
256 
257  // Subdivide chunk into 4 children
258  for (size_t y = 0; y < 2; ++y) {
259  for (size_t x = 0; x < 2; ++x) {
260  Chunk* child = new Chunk();
261  child->size = child_chunk_size;
262  child->depth = chunk->depth + 1;
263  child->x = chunk->x + x * child_chunk_size;
264  child->y = chunk->y + y * child_chunk_size;
265  do_init_chunk(child);
266  chunk->children[x + 2*y] = child;
267  }
268  }
269  } else {
270  // Final chunk, initialize all children to zero
271  for (size_t i = 0; i < 4; ++i) {
272  chunk->children[i] = nullptr;
273  }
274  }
275 }
276 
277 /**
278  * @brief Recursively computes the bounds for a given chunk
279  * @details This method takes a parent chunk, and computes the bounds recursively,
280  * depending on whether the chunk is a leaf or a node.
281  *
282  * If the chunk is a leaf, then the average, min and max values for that chunk
283  * are computed by iterating over the heightfield region of that chunk.
284  *
285  * If the chunk is a node, this method is called recursively on all children
286  * first, and after that, the average, min and max values for that chunk
287  * are computed by merging those values of the children.
288  *
289  * If chunk is NULL, undefined behaviour occurs.
290  *
291  * @param chunk The parent chunk
292  */
293 void ShaderTerrainMesh::do_compute_bounds(Chunk* chunk) {
294 
295  // Final chunk (Leaf)
296  if (chunk->size == _chunk_size) {
297 
298  // Get a pointer to the PNMImage data, this is faster than using get_xel()
299  // for all pixels, since get_xel() also includes bounds checks and so on.
300  xel* data = _heightfield.get_array();
301 
302  // Pixel getter function. Note that we have to flip the Y-component, since
303  // panda itself also flips it
304  // auto get_xel = [&](size_t x, size_t y){ return data[x + (_size - 1 - y) * _size].b / (PN_stdfloat)PGM_MAXMAXVAL; };
305  #define get_xel(x, y) (data[(x) + (_size - 1 - (y)) * _size].b / (PN_stdfloat)PGM_MAXMAXVAL)
306 
307  // Iterate over all pixels
308  PN_stdfloat avg_height = 0.0, min_height = 1.0, max_height = 0.0;
309  for (size_t x = 0; x < _chunk_size; ++x) {
310  for (size_t y = 0; y < _chunk_size; ++y) {
311 
312  // Access data directly, to improve performance
313  PN_stdfloat height = get_xel(chunk->x + x, chunk->y + y);
314  avg_height += height;
315  min_height = min(min_height, height);
316  max_height = max(max_height, height);
317  }
318  }
319 
320  // Normalize average height
321  avg_height /= _chunk_size * _chunk_size;
322 
323  // Store values
324  chunk->min_height = min_height;
325  chunk->max_height = max_height;
326  chunk->avg_height = avg_height;
327 
328  // Get edges in the order (0, 0) (1, 0) (0, 1) (1, 1)
329  for (size_t y = 0; y < 2; ++y) {
330  for (size_t x = 0; x < 2; ++x) {
331  chunk->edges.set_cell(x + 2 * y, get_xel(
332  chunk->x + x * (_chunk_size - 1),
333  chunk->y + y * (_chunk_size - 1)
334  ));
335  }
336  }
337 
338  #undef get_xel
339 
340  } else {
341 
342  // Reset heights
343  chunk->avg_height = 0.0;
344  chunk->min_height = 1.0;
345  chunk->max_height = 0.0;
346 
347  // Perform bounds computation for every children and merge the children values
348  for (size_t i = 0; i < 4; ++i) {
349  do_compute_bounds(chunk->children[i]);
350  chunk->avg_height += chunk->children[i]->avg_height / 4.0;
351  chunk->min_height = min(chunk->min_height, chunk->children[i]->min_height);
352  chunk->max_height = max(chunk->max_height, chunk->children[i]->max_height);
353  }
354 
355  // Also take the edge points from the children
356  chunk->edges.set_x(chunk->children[0]->edges.get_x());
357  chunk->edges.set_y(chunk->children[1]->edges.get_y());
358  chunk->edges.set_z(chunk->children[2]->edges.get_z());
359  chunk->edges.set_w(chunk->children[3]->edges.get_w());
360  }
361 }
362 
363 /**
364  * @brief Internal method to create the chunk geom
365  * @details This method generates the internal used base chunk. The base chunk geom
366  * is used to render the actual terrain, and will get instanced for every chunk.
367  *
368  * The chunk has a size of (size+3) * (size+3), since additional triangles are
369  * inserted at the borders to prevent holes between chunks of a different LOD.
370  *
371  * If the generate patches option is set, patches will be generated instead
372  * of triangles, which allows the terrain to use a tesselation shader.
373  */
374 void ShaderTerrainMesh::do_create_chunk_geom() {
375 
376  // Convert chunk size to an integer, because we operate on integers and get
377  // signed/unsigned mismatches otherwise
378  int size = (int)_chunk_size;
379 
380  // Create vertex data
381  PT(GeomVertexData) gvd = new GeomVertexData("vertices", GeomVertexFormat::get_v3(), Geom::UH_static);
382  gvd->reserve_num_rows( (size + 3) * (size + 3) );
383  GeomVertexWriter vertex_writer(gvd, "vertex");
384 
385  // Create primitive
386  PT(GeomPrimitive) triangles = nullptr;
387  if (_generate_patches) {
388  triangles = new GeomPatches(3, Geom::UH_static);
389  } else {
390  triangles = new GeomTriangles(Geom::UH_static);
391  }
392 
393  // Insert chunk vertices
394  for (int y = -1; y <= size + 1; ++y) {
395  for (int x = -1; x <= size + 1; ++x) {
396  LVector3 vtx_pos(x / (PN_stdfloat)size, y / (PN_stdfloat)size, 0.0f);
397  // Stitched vertices at the cornders
398  if (x == -1 || y == -1 || x == size + 1 || y == size + 1) {
399  vtx_pos.set_z(-1.0f / (PN_stdfloat)size);
400  vtx_pos.set_x(max((PN_stdfloat)0, min((PN_stdfloat)1, vtx_pos.get_x())));
401  vtx_pos.set_y(max((PN_stdfloat)0, min((PN_stdfloat)1, vtx_pos.get_y())));
402  }
403  vertex_writer.add_data3(vtx_pos);
404  }
405  }
406 
407  // Its important to use int and not size_t here, since we do store negative values
408  // auto get_point_index = [&size](int x, int y){ return (x + 1) + (size + 3) * (y + 1); };
409  #define get_point_index(x, y) (((x) + 1) + (size + 3) * ((y) + 1))
410 
411  // Create triangles
412  for (int y = -1; y <= size; ++y) {
413  for (int x = -1; x <= size; ++x) {
414  // Get point indices of the quad vertices
415  int tl = get_point_index(x, y);
416  int tr = get_point_index(x + 1, y);
417  int bl = get_point_index(x, y + 1);
418  int br = get_point_index(x + 1, y + 1);
419 
420  // Vary triangle scheme on each uneven quad
421  if (stm_use_hexagonal_layout && (x + y) % 2 == 0 ) {
422  triangles->add_vertices(tl, tr, br);
423  triangles->add_vertices(tl, br, bl);
424  } else {
425  triangles->add_vertices(tl, tr, bl);
426  triangles->add_vertices(bl, tr, br);
427  }
428  }
429  }
430 
431  #undef get_point_index
432 
433  // Construct geom
434  PT(Geom) geom = new Geom(gvd);
435  geom->add_primitive(triangles);
436 
437  // Do not set any bounds, we do culling ourself
438  geom->clear_bounds();
439  geom->set_bounds(new OmniBoundingVolume());
440  _chunk_geom = geom;
441 }
442 
443 /**
444  * @copydoc PandaNode::is_renderable()
445  */
447  return true;
448 }
449 
450 /**
451  * @copydoc PandaNode::is_renderable()
452  */
454  return false;
455 }
456 
457 /**
458  * @copydoc PandaNode::safe_to_combine()
459  */
461  return false;
462 }
463 
464 /**
465  * @copydoc PandaNode::add_for_draw()
466  */
468  MutexHolder holder(_lock);
469 
470  // Make sure the terrain was properly initialized, and the geom was created
471  // successfully
472  nassertv(_data_texture != nullptr);
473  nassertv(_chunk_geom != nullptr);
474 
475  _basic_collector.start();
476 
477  // Get current frame count
478  int frame_count = ClockObject::get_global_clock()->get_frame_count();
479 
480  if (_last_frame_count != frame_count) {
481  // Frame count changed, this means we are at the beginning of a new frame.
482  // In this case, update the frame count and reset the view index.
483  _last_frame_count = frame_count;
484  _current_view_index = 0;
485  }
486 
487  // Get transform and render state for this render pass
488  CPT(TransformState) modelview_transform = data.get_internal_transform(trav);
489  CPT(RenderState) state = data._state->compose(get_state());
490 
491  // Store a handle to the scene setup
492  const SceneSetup* scene = trav->get_scene();
493 
494  // Get the MVP matrix, this is required for the LOD
495  const Lens* current_lens = scene->get_lens();
496  const LMatrix4& projection_mat = current_lens->get_projection_mat();
497 
498  // Get the current lens bounds
499  PT(BoundingVolume) cam_bounds = scene->get_cull_bounds();
500 
501  // Transform the camera bounds with the main camera transform
502  DCAST(GeometricBoundingVolume, cam_bounds)->xform(scene->get_camera_transform()->get_mat());
503 
504  TraversalData traversal_data;
505  traversal_data.cam_bounds = cam_bounds;
506  traversal_data.model_mat = get_transform()->get_mat();
507  traversal_data.mvp_mat = modelview_transform->get_mat() * projection_mat;
508  traversal_data.emitted_chunks = 0;
509  traversal_data.storage_ptr = (ChunkDataEntry*)_data_texture->modify_ram_image().p();
510  traversal_data.screen_size.set(scene->get_viewport_width(), scene->get_viewport_height());
511 
512  // Move write pointer so it points to the beginning of the current view
513  traversal_data.storage_ptr += _data_texture->get_x_size() * _current_view_index;
514 
515  if (_update_enabled) {
516  // Traverse recursively
517  _lod_collector.start();
518  do_traverse(&_base_chunk, &traversal_data);
519  _lod_collector.stop();
520  } else {
521  // Do a rough guess of the emitted chunks, we don't know the actual count
522  // (we would have to store it). This is only for debugging anyways, so
523  // its not important we get an accurate count here.
524  traversal_data.emitted_chunks = _data_texture->get_x_size();
525  }
526 
527  // Set shader inputs
528  CPT(RenderAttrib) current_shader_attrib = state->get_attrib_def(ShaderAttrib::get_class_slot());
529 
530  // Make sure the user didn't forget to set a shader
531  if (!DCAST(ShaderAttrib, current_shader_attrib)->has_shader()) {
532  shader_terrain_cat.warning() << "No shader set on the terrain! You need to set the appropriate shader!" << endl;
533  }
534 
535  // Should never happen
536  nassertv(current_shader_attrib != nullptr);
537 
538  current_shader_attrib = DCAST(ShaderAttrib, current_shader_attrib)->set_shader_input(
539  ShaderInput("ShaderTerrainMesh.terrain_size", LVecBase2i(_size)));
540  current_shader_attrib = DCAST(ShaderAttrib, current_shader_attrib)->set_shader_input(
541  ShaderInput("ShaderTerrainMesh.chunk_size", LVecBase2i(_chunk_size)));
542  current_shader_attrib = DCAST(ShaderAttrib, current_shader_attrib)->set_shader_input(
543  ShaderInput("ShaderTerrainMesh.view_index", LVecBase2i(_current_view_index)));
544  current_shader_attrib = DCAST(ShaderAttrib, current_shader_attrib)->set_shader_input(
545  ShaderInput("ShaderTerrainMesh.data_texture", _data_texture));
546  current_shader_attrib = DCAST(ShaderAttrib, current_shader_attrib)->set_shader_input(
547  ShaderInput("ShaderTerrainMesh.heightfield", _heightfield_tex));
548  current_shader_attrib = DCAST(ShaderAttrib, current_shader_attrib)->set_instance_count(
549  traversal_data.emitted_chunks);
550 
551  state = state->set_attrib(current_shader_attrib, 10000);
552 
553  // Emit chunk
554  CullableObject *object = new CullableObject(_chunk_geom, std::move(state), std::move(modelview_transform));
555  trav->get_cull_handler()->record_object(object, trav);
556 
557  // After rendering, increment the view index
558  ++_current_view_index;
559 
560  if (_current_view_index > (size_t)stm_max_views) {
561  shader_terrain_cat.error() << "More views than supported! Increase the stm-max-views config variable!" << endl;
562  }
563 
564  _basic_collector.stop();
565 }
566 
567 /**
568  * @brief Traverses the quadtree
569  * @details This method traverses the given chunk, deciding whether it should
570  * be rendered or subdivided.
571  *
572  * In case the chunk is decided to be subdivided, this method is called on
573  * all children.
574  *
575  * In case the chunk is decided to be rendered, ShaderTerrainMesh::do_emit_chunk() is
576  * called. Otherwise nothing happens, and the chunk does not get rendered.
577  *
578  * @param chunk Chunk to traverse
579  * @param data Traversal data
580  */
581 void ShaderTerrainMesh::do_traverse(Chunk* chunk, TraversalData* data, bool fully_visible) {
582 
583  // Don't check bounds if we are fully visible
584  if (!fully_visible) {
585 
586  // Construct chunk bounding volume
587  PN_stdfloat scale = 1.0 / (PN_stdfloat)_size;
588  LPoint3 bb_min(chunk->x * scale, chunk->y * scale, chunk->min_height);
589  LPoint3 bb_max((chunk->x + chunk->size) * scale, (chunk->y + chunk->size) * scale, chunk->max_height);
590 
591  BoundingBox bbox = BoundingBox(bb_min, bb_max);
592  DCAST(GeometricBoundingVolume, &bbox)->xform(data->model_mat);
593  int intersection = data->cam_bounds->contains(&bbox);
594 
595  if (intersection == BoundingVolume::IF_no_intersection) {
596  // No intersection with frustum
597  return;
598  }
599 
600  // If the bounds are fully visible, there is no reason to perform culling
601  // on the children, so we set this flag to prevent any bounding computation
602  // on the child nodes.
603  fully_visible = (intersection & BoundingVolume::IF_all) != 0;
604  }
605 
606  // Check if the chunk should be subdivided. In case the chunk is a leaf node,
607  // the chunk will never get subdivided.
608  // NOTE: We still always perform the LOD check. This is for the reason that
609  // the lod check also computes the CLOD factor, which is useful.
610  if (do_check_lod_matches(chunk, data) || chunk->size == _chunk_size) {
611  do_emit_chunk(chunk, data);
612  } else {
613  // Traverse children
614  for (size_t i = 0; i < 4; ++i) {
615  do_traverse(chunk->children[i], data, fully_visible);
616  }
617  }
618 }
619 
620 /**
621  * @brief Checks whether a chunk should get subdivided
622  * @details This method checks whether a chunk fits on screen, or should be
623  * subdivided in order to provide bigger detail.
624  *
625  * In case this method returns true, the chunk lod is fine, and the chunk
626  * can be rendered. If the method returns false, the chunk should be subdivided.
627  *
628  * @param chunk Chunk to check
629  * @param data Traversal data
630  *
631  * @return true if the chunk is sufficient, false if the chunk should be subdivided
632  */
633 bool ShaderTerrainMesh::do_check_lod_matches(Chunk* chunk, TraversalData* data) {
634 
635  // Project all points to world space
636  LVector2 projected_points[4];
637  for (size_t y = 0; y < 2; ++y) {
638  for (size_t x = 0; x < 2; ++x) {
639 
640  // Compute point in model space (0,0,0 to 1,1,1)
641  LVector3 edge_pos = LVector3(
642  (PN_stdfloat)(chunk->x + x * (chunk->size - 1)) / (PN_stdfloat)_size,
643  (PN_stdfloat)(chunk->y + y * (chunk->size - 1)) / (PN_stdfloat)_size,
644  chunk->edges.get_cell(x + 2 * y)
645  );
646  LVector4 projected = data->mvp_mat.xform(LVector4(edge_pos, 1.0));
647  if (projected.get_w() == 0.0) {
648  projected.set(0.0, 0.0, -1.0, 1.0f);
649  }
650  projected *= 1.0 / projected.get_w();
651  projected_points[x + 2 * y].set(
652  projected.get_x() * data->screen_size.get_x(),
653  projected.get_y() * data->screen_size.get_y());
654  }
655  }
656 
657  // Compute the length of the edges in screen space
658  PN_stdfloat edge_top = (projected_points[1] - projected_points[3]).length_squared();
659  PN_stdfloat edge_right = (projected_points[0] - projected_points[2]).length_squared();
660  PN_stdfloat edge_bottom = (projected_points[2] - projected_points[3]).length_squared();
661  PN_stdfloat edge_left = (projected_points[0] - projected_points[1]).length_squared();
662 
663  // CLOD factor
664  PN_stdfloat max_edge = max(edge_top, max(edge_right, max(edge_bottom, edge_left)));
665 
666  // Micro-Optimization: We use length_squared() instead of length() to compute the
667  // maximum edge length. This reduces it to one csqrt instead of four.
668  max_edge = csqrt(max_edge);
669 
670  PN_stdfloat tesselation_factor = (max_edge / _target_triangle_width) / (PN_stdfloat)_chunk_size;
671  PN_stdfloat clod_factor = max(0.0, min(1.0, 2.0 - tesselation_factor));
672 
673  // Store the clod factor
674  chunk->last_clod = clod_factor;
675 
676  return tesselation_factor <= 2.0;
677 }
678 
679 /**
680  * @brief Internal method to spawn a chunk
681  * @details This method is used to spawn a chunk in case the traversal decided
682  * that the chunk gets rendered. It writes the chunks data to the texture, and
683  * increments the write pointer
684  *
685  * @param chunk Chunk to spawn
686  * @param data Traversal data
687  */
688 void ShaderTerrainMesh::do_emit_chunk(Chunk* chunk, TraversalData* data) {
689  if (data->emitted_chunks >= _data_texture->get_x_size()) {
690 
691  // Only print warning once
692  if (data->emitted_chunks == _data_texture->get_x_size()) {
693  shader_terrain_cat.error() << "Too many chunks in the terrain! Consider lowering the desired LOD, or increase the stm-max-chunk-count variable." << endl;
694  data->emitted_chunks++;
695  }
696  return;
697  }
698 
699  ChunkDataEntry& data_entry = *data->storage_ptr;
700  data_entry.x = chunk->x;
701  data_entry.y = chunk->y;
702  data_entry.size = chunk->size / _chunk_size;
703  data_entry.clod = chunk->last_clod;
704 
705  data->emitted_chunks++;
706  data->storage_ptr++;
707 }
708 
709 /**
710  * @brief Transforms a texture coordinate to world space
711  * @details This transforms a texture coordinatefrom uv-space (0 to 1) to world
712  * space. This takes the terrains transform into account, and also samples the
713  * heightmap. This method should be called after generate().
714  *
715  * @param coord Coordinate in uv-space from 0, 0 to 1, 1
716  * @return World-Space point
717  */
718 LPoint3 ShaderTerrainMesh::uv_to_world(const LTexCoord& coord) const {
719  MutexHolder holder(_lock);
720  nassertr(_heightfield_tex != nullptr, LPoint3(0)); // Heightfield not set yet
721  nassertr(_heightfield_tex->has_ram_image(), LPoint3(0)); // Heightfield not in memory
722 
723  PT(TexturePeeker) peeker = _heightfield_tex->peek();
724  nassertr(peeker != nullptr, LPoint3(0));
725 
726  LColor result;
727  if (!peeker->lookup_bilinear(result, coord.get_x(), coord.get_y())) {
728  shader_terrain_cat.error() << "UV out of range, cant transform to world!" << endl;
729  return LPoint3(0);
730  }
731  LPoint3 unit_point(coord.get_x(), coord.get_y(), result.get_x());
732  return get_transform()->get_mat().xform_point_general(unit_point);
733 }
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
An axis-aligned bounding box; that is, a minimum and maximum coordinate triple.
Definition: boundingBox.h:29
This is an abstract class for any volume in any sense which can be said to define the locality of ref...
get_frame_count
Returns the number of times tick() has been called since the ClockObject was created,...
Definition: clockObject.h:94
static ClockObject * get_global_clock()
Returns a pointer to the global ClockObject.
Definition: clockObject.I:215
This is a convenience class to specialize ConfigVariable as a boolean type.
This class specializes ConfigVariable as an enumerated type.
This is a convenience class to specialize ConfigVariable as an integer type.
virtual void record_object(CullableObject *object, const CullTraverser *traverser)
This callback function is intended to be overridden by a derived class.
Definition: cullHandler.cxx:43
This collects together the pieces of data that are accumulated for each node while walking the scene ...
This object performs a depth-first traversal of the scene graph, with optional view-frustum culling,...
Definition: cullTraverser.h:45
CullHandler * get_cull_handler() const
Returns the object that will receive the culled Geoms.
SceneSetup * get_scene() const
Returns the SceneSetup object.
Definition: cullTraverser.I:35
The smallest atom of cull.
Defines a series of "patches", fixed-size groupings of vertices that must be processed by a tessellat...
Definition: geomPatches.h:24
This is an abstract base class for a family of classes that represent the fundamental geometry primit...
Definition: geomPrimitive.h:56
Defines a series of disconnected triangles.
Definition: geomTriangles.h:23
This defines the actual numeric vertex data stored in a Geom, in the structure defined by a particula...
static const GeomVertexFormat * get_v3()
Returns a standard vertex format with just a 3-component vertex position.
This object provides a high-level interface for quickly writing a sequence of numeric values from a v...
A container for geometry primitives.
Definition: geom.h:54
This is another abstract class, for a general class of bounding volumes that actually enclose points ...
A base class for any number of different kinds of lenses, linear and otherwise.
Definition: lens.h:41
const LMatrix4 & get_projection_mat(StereoChannel channel=SC_mono) const
Returns the complete transformation matrix from a 3-d point in space to a point on the film,...
Definition: lens.I:563
A lightweight C++ object whose constructor calls acquire() and whose destructor calls release() on a ...
Definition: mutexHolder.h:25
This is a special kind of GeometricBoundingVolume that fills all of space.
get_maxval
Returns the maximum channel value allowable for any pixel in this image; for instance,...
void clear()
Frees all memory allocated for the image, and clears all its parameters (size, color,...
Definition: pnmImage.cxx:48
xel * get_array()
Directly access the underlying PNMImage array.
Definition: pnmImage.I:1098
A lightweight class that represents a single element that may be timed and/or counted via stats.
A basic node of the scene graph or data graph.
Definition: pandaNode.h:65
void set_bounds(const BoundingVolume *volume)
Resets the bounding volume so that it is the indicated volume.
Definition: pandaNode.cxx:1907
This is the base class for a number of render attributes (other than transform) that may be set on sc...
Definition: renderAttrib.h:51
This represents a unique collection of RenderAttrib objects that correspond to a particular renderabl...
Definition: renderState.h:47
This object holds the camera position, etc., and other general setup information for rendering a part...
Definition: sceneSetup.h:32
const Lens * get_lens() const
Returns the particular Lens used for rendering.
Definition: sceneSetup.I:131
int get_viewport_width() const
Returns the width of the viewport (display region) in pixels.
Definition: sceneSetup.I:59
const TransformState * get_camera_transform() const
Returns the position of the camera relative to the starting node.
Definition: sceneSetup.I:214
int get_viewport_height() const
Returns the height of the viewport (display region) in pixels.
Definition: sceneSetup.I:67
This is a small container class that can hold any one of the value types that can be passed as input ...
Definition: shaderInput.h:40
LPoint3 uv_to_world(const LTexCoord &coord) const
Transforms a texture coordinate to world space.
virtual void add_for_draw(CullTraverser *trav, CullTraverserData &data)
Adds the node's contents to the CullResult we are building up during the cull traversal,...
virtual bool is_renderable() const
Returns true if there is some value to visiting this particular node during the cull traversal for an...
virtual bool safe_to_combine() const
Returns true if it is generally safe to combine this particular kind of PandaNode with other kinds of...
ShaderTerrainMesh()
Constructs a new Terrain Mesh.
bool generate()
Generates the terrain mesh.
virtual bool safe_to_flatten() const
Returns true if there is some value to visiting this particular node during the cull traversal for an...
An instance of this object is returned by Texture::peek().
Definition: texturePeeker.h:27
Represents a texture object, which is typically a single 2-d image but may also represent a 1-d or 3-...
Definition: texture.h:71
Indicates a coordinate-system transform on vertices.
get_mat
Returns the matrix that describes the transform.
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
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.
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.
int check_power_of_two(size_t x)
Helper function to check for a power of two.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.