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_clear_color(LVector4(0));
211  _data_texture->clear_image();
212 }
213 
214 /**
215  * @brief Internal method to init the quadtree
216  * @details This method creates the base chunk and then inits all chunks recursively
217  * by using ShaderTerrainMesh::do_init_chunk().
218  */
219 void ShaderTerrainMesh::do_create_chunks() {
220 
221  // Release any previously stored children
222  _base_chunk.clear_children();
223 
224  // Create the base chunk
225  _base_chunk.depth = 0;
226  _base_chunk.x = 0;
227  _base_chunk.y = 0;
228  _base_chunk.size = _size;
229  _base_chunk.edges.set(0, 0, 0, 0);
230  _base_chunk.avg_height = 0.5;
231  _base_chunk.min_height = 0.0;
232  _base_chunk.max_height = 1.0;
233  _base_chunk.last_clod = 0.0;
234  do_init_chunk(&_base_chunk);
235 }
236 
237 /**
238  * @brief Internal method to recursively init the quadtree
239  * @details This method inits the quadtree. Starting from a given node, it
240  * first examines if that node should be subdivided.
241  *
242  * If the node should be subdivided, four children are created and this method
243  * is called on the children again. If the node is a leaf, all children are
244  * set to NULL and nothing else happens.
245  *
246  * The chunk parameter may not be zero or undefined behaviour occurs.
247  *
248  * @param chunk The parent chunk
249  */
250 void ShaderTerrainMesh::do_init_chunk(Chunk* chunk) {
251  if (chunk->size > _chunk_size) {
252 
253  // Compute children chunk size
254  size_t child_chunk_size = chunk->size / 2;
255 
256  // Subdivide chunk into 4 children
257  for (size_t y = 0; y < 2; ++y) {
258  for (size_t x = 0; x < 2; ++x) {
259  Chunk* child = new Chunk();
260  child->size = child_chunk_size;
261  child->depth = chunk->depth + 1;
262  child->x = chunk->x + x * child_chunk_size;
263  child->y = chunk->y + y * child_chunk_size;
264  do_init_chunk(child);
265  chunk->children[x + 2*y] = child;
266  }
267  }
268  } else {
269  // Final chunk, initialize all children to zero
270  for (size_t i = 0; i < 4; ++i) {
271  chunk->children[i] = nullptr;
272  }
273  }
274 }
275 
276 /**
277  * @brief Recursively computes the bounds for a given chunk
278  * @details This method takes a parent chunk, and computes the bounds recursively,
279  * depending on whether the chunk is a leaf or a node.
280  *
281  * If the chunk is a leaf, then the average, min and max values for that chunk
282  * are computed by iterating over the heightfield region of that chunk.
283  *
284  * If the chunk is a node, this method is called recursively on all children
285  * first, and after that, the average, min and max values for that chunk
286  * are computed by merging those values of the children.
287  *
288  * If chunk is NULL, undefined behaviour occurs.
289  *
290  * @param chunk The parent chunk
291  */
292 void ShaderTerrainMesh::do_compute_bounds(Chunk* chunk) {
293 
294  // Final chunk (Leaf)
295  if (chunk->size == _chunk_size) {
296 
297  // Get a pointer to the PNMImage data, this is faster than using get_xel()
298  // for all pixels, since get_xel() also includes bounds checks and so on.
299  xel* data = _heightfield.get_array();
300 
301  // Pixel getter function. Note that we have to flip the Y-component, since
302  // panda itself also flips it
303  // auto get_xel = [&](size_t x, size_t y){ return data[x + (_size - 1 - y) * _size].b / (PN_stdfloat)PGM_MAXMAXVAL; };
304  #define get_xel(x, y) (data[(x) + (_size - 1 - (y)) * _size].b / (PN_stdfloat)PGM_MAXMAXVAL)
305 
306  // Iterate over all pixels
307  PN_stdfloat avg_height = 0.0, min_height = 1.0, max_height = 0.0;
308  for (size_t x = 0; x < _chunk_size; ++x) {
309  for (size_t y = 0; y < _chunk_size; ++y) {
310 
311  // Access data directly, to improve performance
312  PN_stdfloat height = get_xel(chunk->x + x, chunk->y + y);
313  avg_height += height;
314  min_height = min(min_height, height);
315  max_height = max(max_height, height);
316  }
317  }
318 
319  // Normalize average height
320  avg_height /= _chunk_size * _chunk_size;
321 
322  // Store values
323  chunk->min_height = min_height;
324  chunk->max_height = max_height;
325  chunk->avg_height = avg_height;
326 
327  // Get edges in the order (0, 0) (1, 0) (0, 1) (1, 1)
328  for (size_t y = 0; y < 2; ++y) {
329  for (size_t x = 0; x < 2; ++x) {
330  chunk->edges.set_cell(x + 2 * y, get_xel(
331  chunk->x + x * (_chunk_size - 1),
332  chunk->y + y * (_chunk_size - 1)
333  ));
334  }
335  }
336 
337  #undef get_xel
338 
339  } else {
340 
341  // Reset heights
342  chunk->avg_height = 0.0;
343  chunk->min_height = 1.0;
344  chunk->max_height = 0.0;
345 
346  // Perform bounds computation for every children and merge the children values
347  for (size_t i = 0; i < 4; ++i) {
348  do_compute_bounds(chunk->children[i]);
349  chunk->avg_height += chunk->children[i]->avg_height / 4.0;
350  chunk->min_height = min(chunk->min_height, chunk->children[i]->min_height);
351  chunk->max_height = max(chunk->max_height, chunk->children[i]->max_height);
352  }
353 
354  // Also take the edge points from the children
355  chunk->edges.set_x(chunk->children[0]->edges.get_x());
356  chunk->edges.set_y(chunk->children[1]->edges.get_y());
357  chunk->edges.set_z(chunk->children[2]->edges.get_z());
358  chunk->edges.set_w(chunk->children[3]->edges.get_w());
359  }
360 }
361 
362 /**
363  * @brief Internal method to create the chunk geom
364  * @details This method generates the internal used base chunk. The base chunk geom
365  * is used to render the actual terrain, and will get instanced for every chunk.
366  *
367  * The chunk has a size of (size+3) * (size+3), since additional triangles are
368  * inserted at the borders to prevent holes between chunks of a different LOD.
369  *
370  * If the generate patches option is set, patches will be generated instead
371  * of triangles, which allows the terrain to use a tesselation shader.
372  */
373 void ShaderTerrainMesh::do_create_chunk_geom() {
374 
375  // Convert chunk size to an integer, because we operate on integers and get
376  // signed/unsigned mismatches otherwise
377  int size = (int)_chunk_size;
378 
379  // Create vertex data
380  PT(GeomVertexData) gvd = new GeomVertexData("vertices", GeomVertexFormat::get_v3(), Geom::UH_static);
381  gvd->reserve_num_rows( (size + 3) * (size + 3) );
382  GeomVertexWriter vertex_writer(gvd, "vertex");
383 
384  // Create primitive
385  PT(GeomPrimitive) triangles = nullptr;
386  if (_generate_patches) {
387  triangles = new GeomPatches(3, Geom::UH_static);
388  } else {
389  triangles = new GeomTriangles(Geom::UH_static);
390  }
391 
392  // Insert chunk vertices
393  for (int y = -1; y <= size + 1; ++y) {
394  for (int x = -1; x <= size + 1; ++x) {
395  LVector3 vtx_pos(x / (PN_stdfloat)size, y / (PN_stdfloat)size, 0.0f);
396  // Stitched vertices at the cornders
397  if (x == -1 || y == -1 || x == size + 1 || y == size + 1) {
398  vtx_pos.set_z(-1.0f / (PN_stdfloat)size);
399  vtx_pos.set_x(max((PN_stdfloat)0, min((PN_stdfloat)1, vtx_pos.get_x())));
400  vtx_pos.set_y(max((PN_stdfloat)0, min((PN_stdfloat)1, vtx_pos.get_y())));
401  }
402  vertex_writer.add_data3(vtx_pos);
403  }
404  }
405 
406  // Its important to use int and not size_t here, since we do store negative values
407  // auto get_point_index = [&size](int x, int y){ return (x + 1) + (size + 3) * (y + 1); };
408  #define get_point_index(x, y) (((x) + 1) + (size + 3) * ((y) + 1))
409 
410  // Create triangles
411  for (int y = -1; y <= size; ++y) {
412  for (int x = -1; x <= size; ++x) {
413  // Get point indices of the quad vertices
414  int tl = get_point_index(x, y);
415  int tr = get_point_index(x + 1, y);
416  int bl = get_point_index(x, y + 1);
417  int br = get_point_index(x + 1, y + 1);
418 
419  // Vary triangle scheme on each uneven quad
420  if (stm_use_hexagonal_layout && (x + y) % 2 == 0 ) {
421  triangles->add_vertices(tl, tr, br);
422  triangles->add_vertices(tl, br, bl);
423  } else {
424  triangles->add_vertices(tl, tr, bl);
425  triangles->add_vertices(bl, tr, br);
426  }
427  }
428  }
429 
430  #undef get_point_index
431 
432  // Construct geom
433  PT(Geom) geom = new Geom(gvd);
434  geom->add_primitive(triangles);
435 
436  // Do not set any bounds, we do culling ourself
437  geom->clear_bounds();
438  geom->set_bounds(new OmniBoundingVolume());
439  _chunk_geom = geom;
440 }
441 
442 /**
443  * @copydoc PandaNode::is_renderable()
444  */
446  return true;
447 }
448 
449 /**
450  * @copydoc PandaNode::is_renderable()
451  */
453  return false;
454 }
455 
456 /**
457  * @copydoc PandaNode::safe_to_combine()
458  */
460  return false;
461 }
462 
463 /**
464  * @copydoc PandaNode::add_for_draw()
465  */
467  MutexHolder holder(_lock);
468 
469  // Make sure the terrain was properly initialized, and the geom was created
470  // successfully
471  nassertv(_data_texture != nullptr);
472  nassertv(_chunk_geom != nullptr);
473 
474  _basic_collector.start();
475 
476  // Get current frame count
477  int frame_count = ClockObject::get_global_clock()->get_frame_count();
478 
479  if (_last_frame_count != frame_count) {
480  // Frame count changed, this means we are at the beginning of a new frame.
481  // In this case, update the frame count and reset the view index.
482  _last_frame_count = frame_count;
483  _current_view_index = 0;
484  }
485 
486  // Get transform and render state for this render pass
487  CPT(TransformState) modelview_transform = data.get_internal_transform(trav);
488  CPT(RenderState) state = data._state->compose(get_state());
489 
490  // Store a handle to the scene setup
491  const SceneSetup* scene = trav->get_scene();
492 
493  // Get the MVP matrix, this is required for the LOD
494  const Lens* current_lens = scene->get_lens();
495  const LMatrix4& projection_mat = current_lens->get_projection_mat();
496 
497  // Get the current lens bounds
498  PT(BoundingVolume) cam_bounds = scene->get_cull_bounds();
499 
500  // Transform the camera bounds with the main camera transform
501  DCAST(GeometricBoundingVolume, cam_bounds)->xform(scene->get_camera_transform()->get_mat());
502 
503  TraversalData traversal_data;
504  traversal_data.cam_bounds = cam_bounds;
505  traversal_data.model_mat = get_transform()->get_mat();
506  traversal_data.mvp_mat = modelview_transform->get_mat() * projection_mat;
507  traversal_data.emitted_chunks = 0;
508  traversal_data.storage_ptr = (ChunkDataEntry*)_data_texture->modify_ram_image().p();
509  traversal_data.screen_size.set(scene->get_viewport_width(), scene->get_viewport_height());
510 
511  // Move write pointer so it points to the beginning of the current view
512  traversal_data.storage_ptr += _data_texture->get_x_size() * _current_view_index;
513 
514  if (_update_enabled) {
515  // Traverse recursively
516  _lod_collector.start();
517  do_traverse(&_base_chunk, &traversal_data);
518  _lod_collector.stop();
519  } else {
520  // Do a rough guess of the emitted chunks, we don't know the actual count
521  // (we would have to store it). This is only for debugging anyways, so
522  // its not important we get an accurate count here.
523  traversal_data.emitted_chunks = _data_texture->get_x_size();
524  }
525 
526  // Set shader inputs
527  CPT(RenderAttrib) current_shader_attrib = state->get_attrib_def(ShaderAttrib::get_class_slot());
528 
529  // Make sure the user didn't forget to set a shader
530  if (!DCAST(ShaderAttrib, current_shader_attrib)->has_shader()) {
531  shader_terrain_cat.warning() << "No shader set on the terrain! You need to set the appropriate shader!" << endl;
532  }
533 
534  // Should never happen
535  nassertv(current_shader_attrib != nullptr);
536 
537  current_shader_attrib = DCAST(ShaderAttrib, current_shader_attrib)->set_shader_input(
538  ShaderInput("ShaderTerrainMesh.terrain_size", LVecBase2i(_size)));
539  current_shader_attrib = DCAST(ShaderAttrib, current_shader_attrib)->set_shader_input(
540  ShaderInput("ShaderTerrainMesh.chunk_size", LVecBase2i(_chunk_size)));
541  current_shader_attrib = DCAST(ShaderAttrib, current_shader_attrib)->set_shader_input(
542  ShaderInput("ShaderTerrainMesh.view_index", LVecBase2i(_current_view_index)));
543  current_shader_attrib = DCAST(ShaderAttrib, current_shader_attrib)->set_shader_input(
544  ShaderInput("ShaderTerrainMesh.data_texture", _data_texture));
545  current_shader_attrib = DCAST(ShaderAttrib, current_shader_attrib)->set_shader_input(
546  ShaderInput("ShaderTerrainMesh.heightfield", _heightfield_tex));
547  current_shader_attrib = DCAST(ShaderAttrib, current_shader_attrib)->set_instance_count(
548  traversal_data.emitted_chunks);
549 
550  state = state->set_attrib(current_shader_attrib, 10000);
551 
552  // Emit chunk
553  CullableObject *object = new CullableObject(_chunk_geom, std::move(state), std::move(modelview_transform));
554  trav->get_cull_handler()->record_object(object, trav);
555 
556  // After rendering, increment the view index
557  ++_current_view_index;
558 
559  if (_current_view_index > (size_t)stm_max_views) {
560  shader_terrain_cat.error() << "More views than supported! Increase the stm-max-views config variable!" << endl;
561  }
562 
563  _basic_collector.stop();
564 }
565 
566 /**
567  * @brief Traverses the quadtree
568  * @details This method traverses the given chunk, deciding whether it should
569  * be rendered or subdivided.
570  *
571  * In case the chunk is decided to be subdivided, this method is called on
572  * all children.
573  *
574  * In case the chunk is decided to be rendered, ShaderTerrainMesh::do_emit_chunk() is
575  * called. Otherwise nothing happens, and the chunk does not get rendered.
576  *
577  * @param chunk Chunk to traverse
578  * @param data Traversal data
579  */
580 void ShaderTerrainMesh::do_traverse(Chunk* chunk, TraversalData* data, bool fully_visible) {
581 
582  // Don't check bounds if we are fully visible
583  if (!fully_visible) {
584 
585  // Construct chunk bounding volume
586  PN_stdfloat scale = 1.0 / (PN_stdfloat)_size;
587  LPoint3 bb_min(chunk->x * scale, chunk->y * scale, chunk->min_height);
588  LPoint3 bb_max((chunk->x + chunk->size) * scale, (chunk->y + chunk->size) * scale, chunk->max_height);
589 
590  BoundingBox bbox = BoundingBox(bb_min, bb_max);
591  DCAST(GeometricBoundingVolume, &bbox)->xform(data->model_mat);
592  int intersection = data->cam_bounds->contains(&bbox);
593 
594  if (intersection == BoundingVolume::IF_no_intersection) {
595  // No intersection with frustum
596  return;
597  }
598 
599  // If the bounds are fully visible, there is no reason to perform culling
600  // on the children, so we set this flag to prevent any bounding computation
601  // on the child nodes.
602  fully_visible = (intersection & BoundingVolume::IF_all) != 0;
603  }
604 
605  // Check if the chunk should be subdivided. In case the chunk is a leaf node,
606  // the chunk will never get subdivided.
607  // NOTE: We still always perform the LOD check. This is for the reason that
608  // the lod check also computes the CLOD factor, which is useful.
609  if (do_check_lod_matches(chunk, data) || chunk->size == _chunk_size) {
610  do_emit_chunk(chunk, data);
611  } else {
612  // Traverse children
613  for (size_t i = 0; i < 4; ++i) {
614  do_traverse(chunk->children[i], data, fully_visible);
615  }
616  }
617 }
618 
619 /**
620  * @brief Checks whether a chunk should get subdivided
621  * @details This method checks whether a chunk fits on screen, or should be
622  * subdivided in order to provide bigger detail.
623  *
624  * In case this method returns true, the chunk lod is fine, and the chunk
625  * can be rendered. If the method returns false, the chunk should be subdivided.
626  *
627  * @param chunk Chunk to check
628  * @param data Traversal data
629  *
630  * @return true if the chunk is sufficient, false if the chunk should be subdivided
631  */
632 bool ShaderTerrainMesh::do_check_lod_matches(Chunk* chunk, TraversalData* data) {
633 
634  // Project all points to world space
635  LVector2 projected_points[4];
636  for (size_t y = 0; y < 2; ++y) {
637  for (size_t x = 0; x < 2; ++x) {
638 
639  // Compute point in model space (0,0,0 to 1,1,1)
640  LVector3 edge_pos = LVector3(
641  (PN_stdfloat)(chunk->x + x * (chunk->size - 1)) / (PN_stdfloat)_size,
642  (PN_stdfloat)(chunk->y + y * (chunk->size - 1)) / (PN_stdfloat)_size,
643  chunk->edges.get_cell(x + 2 * y)
644  );
645  LVector4 projected = data->mvp_mat.xform(LVector4(edge_pos, 1.0));
646  if (projected.get_w() == 0.0) {
647  projected.set(0.0, 0.0, -1.0, 1.0f);
648  }
649  projected *= 1.0 / projected.get_w();
650  projected_points[x + 2 * y].set(
651  projected.get_x() * data->screen_size.get_x(),
652  projected.get_y() * data->screen_size.get_y());
653  }
654  }
655 
656  // Compute the length of the edges in screen space
657  PN_stdfloat edge_top = (projected_points[1] - projected_points[3]).length_squared();
658  PN_stdfloat edge_right = (projected_points[0] - projected_points[2]).length_squared();
659  PN_stdfloat edge_bottom = (projected_points[2] - projected_points[3]).length_squared();
660  PN_stdfloat edge_left = (projected_points[0] - projected_points[1]).length_squared();
661 
662  // CLOD factor
663  PN_stdfloat max_edge = max(edge_top, max(edge_right, max(edge_bottom, edge_left)));
664 
665  // Micro-Optimization: We use length_squared() instead of length() to compute the
666  // maximum edge length. This reduces it to one csqrt instead of four.
667  max_edge = csqrt(max_edge);
668 
669  PN_stdfloat tesselation_factor = (max_edge / _target_triangle_width) / (PN_stdfloat)_chunk_size;
670  PN_stdfloat clod_factor = max(0.0, min(1.0, 2.0 - tesselation_factor));
671 
672  // Store the clod factor
673  chunk->last_clod = clod_factor;
674 
675  return tesselation_factor <= 2.0;
676 }
677 
678 /**
679  * @brief Internal method to spawn a chunk
680  * @details This method is used to spawn a chunk in case the traversal decided
681  * that the chunk gets rendered. It writes the chunks data to the texture, and
682  * increments the write pointer
683  *
684  * @param chunk Chunk to spawn
685  * @param data Traversal data
686  */
687 void ShaderTerrainMesh::do_emit_chunk(Chunk* chunk, TraversalData* data) {
688  if (data->emitted_chunks >= _data_texture->get_x_size()) {
689 
690  // Only print warning once
691  if (data->emitted_chunks == _data_texture->get_x_size()) {
692  shader_terrain_cat.error() << "Too many chunks in the terrain! Consider lowering the desired LOD, or increase the stm-max-chunk-count variable." << endl;
693  data->emitted_chunks++;
694  }
695  return;
696  }
697 
698  ChunkDataEntry& data_entry = *data->storage_ptr;
699  data_entry.x = chunk->x;
700  data_entry.y = chunk->y;
701  data_entry.size = chunk->size / _chunk_size;
702  data_entry.clod = chunk->last_clod;
703 
704  data->emitted_chunks++;
705  data->storage_ptr++;
706 }
707 
708 /**
709  * @brief Transforms a texture coordinate to world space
710  * @details This transforms a texture coordinatefrom uv-space (0 to 1) to world
711  * space. This takes the terrains transform into account, and also samples the
712  * heightmap. This method should be called after generate().
713  *
714  * @param coord Coordinate in uv-space from 0, 0 to 1, 1
715  * @return World-Space point
716  */
717 LPoint3 ShaderTerrainMesh::uv_to_world(const LTexCoord& coord) const {
718  MutexHolder holder(_lock);
719  nassertr(_heightfield_tex != nullptr, LPoint3(0)); // Heightfield not set yet
720  nassertr(_heightfield_tex->has_ram_image(), LPoint3(0)); // Heightfield not in memory
721 
722  PT(TexturePeeker) peeker = _heightfield_tex->peek();
723  nassertr(peeker != nullptr, LPoint3(0));
724 
725  LColor result;
726  if (!peeker->lookup_bilinear(result, coord.get_x(), coord.get_y())) {
727  shader_terrain_cat.error() << "UV out of range, cant transform to world!" << endl;
728  return LPoint3(0);
729  }
730  LPoint3 unit_point(coord.get_x(), coord.get_y(), result.get_x());
731  return get_transform()->get_mat().xform_point_general(unit_point);
732 }
static ClockObject * get_global_clock()
Returns a pointer to the global ClockObject.
Definition: clockObject.I:215
A basic node of the scene graph or data graph.
Definition: pandaNode.h:64
This object provides a high-level interface for quickly writing a sequence of numeric values from a v...
An axis-aligned bounding box; that is, a minimum and maximum coordinate triple.
Definition: boundingBox.h:29
CullHandler * get_cull_handler() const
Returns the object that will receive the culled Geoms.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Indicates a coordinate-system transform on vertices.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This is the base class for a number of render attributes (other than transform) that may be set on sc...
Definition: renderAttrib.h:51
A base class for any number of different kinds of lenses, linear and otherwise.
Definition: lens.h:41
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void set_bounds(const BoundingVolume *volume)
Resets the bounding volume so that it is the indicated volume.
Definition: pandaNode.cxx:1913
This is a convenience class to specialize ConfigVariable as a boolean type.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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
virtual bool safe_to_combine() const
Returns true if it is generally safe to combine this particular kind of PandaNode with other kinds of...
virtual bool is_renderable() const
Returns true if there is some value to visiting this particular node during the cull traversal for an...
This is an abstract base class for a family of classes that represent the fundamental geometry primit...
Definition: geomPrimitive.h:56
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This collects together the pieces of data that are accumulated for each node while walking the scene ...
const Lens * get_lens() const
Returns the particular Lens used for rendering.
Definition: sceneSetup.I:131
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
xel * get_array()
Directly access the underlying PNMImage array.
Definition: pnmImage.I:1084
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
A lightweight C++ object whose constructor calls acquire() and whose destructor calls release() on a ...
Definition: mutexHolder.h:25
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
const TransformState * get_camera_transform() const
Returns the position of the camera relative to the starting node.
Definition: sceneSetup.I:214
get_mat
Returns the matrix that describes the transform.
int get_viewport_height() const
Returns the height of the viewport (display region) in pixels.
Definition: sceneSetup.I:67
virtual bool safe_to_flatten() const
Returns true if there is some value to visiting this particular node during the cull traversal for an...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This is an abstract class for any volume in any sense which can be said to define the locality of ref...
A lightweight class that represents a single element that may be timed and/or counted via stats.
This is another abstract class, for a general class of bounding volumes that actually enclose points ...
LPoint3 uv_to_world(const LTexCoord &coord) const
Transforms a texture coordinate to world space.
get_maxval
Returns the maximum channel value allowable for any pixel in this image; for instance,...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Defines a series of "patches", fixed-size groupings of vertices that must be processed by a tessellat...
Definition: geomPatches.h:24
The smallest atom of cull.
get_frame_count
Returns the number of times tick() has been called since the ClockObject was created,...
Definition: clockObject.h:94
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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
int get_viewport_width() const
Returns the width of the viewport (display region) in pixels.
Definition: sceneSetup.I:59
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
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This class specializes ConfigVariable as an enumerated type.
bool generate()
Generates the terrain mesh.
This represents a unique collection of RenderAttrib objects that correspond to a particular renderabl...
Definition: renderState.h:47
An instance of this object is returned by Texture::peek().
Definition: texturePeeker.h:27
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,...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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
void clear()
Frees all memory allocated for the image, and clears all its parameters (size, color,...
Definition: pnmImage.cxx:48
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
SceneSetup * get_scene() const
Returns the SceneSetup object.
Definition: cullTraverser.I:35
This is a convenience class to specialize ConfigVariable as an integer type.
int check_power_of_two(size_t x)
Helper function to check for a power of two.
Defines a series of disconnected triangles.
Definition: geomTriangles.h:23
This is a special kind of GeometricBoundingVolume that fills all of space.
ShaderTerrainMesh()
Constructs a new Terrain Mesh.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
This object holds the camera position, etc., and other general setup information for rendering a part...
Definition: sceneSetup.h:32
static const GeomVertexFormat * get_v3()
Returns a standard vertex format with just a 3-component vertex position.
This object performs a depth-first traversal of the scene graph, with optional view-frustum culling,...
Definition: cullTraverser.h:45
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.