Panda3D
Loading...
Searching...
No Matches
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
37using std::endl;
38using std::max;
39using std::min;
40
41ConfigVariableBool 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
47ConfigVariableInt 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
53ConfigVariableInt 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
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
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
71PStatCollector ShaderTerrainMesh::_basic_collector("Cull:ShaderTerrainMesh:Setup");
72PStatCollector ShaderTerrainMesh::_lod_collector("Cull:ShaderTerrainMesh:CollectLOD");
73
74NotifyCategoryDef(shader_terrain, "");
75
76TypeHandle 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 */
85int 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 */
158void 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 */
184bool 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 */
207void 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 */
220void 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 */
251void 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 */
293void 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 */
374void 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 */
581void 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 */
633bool 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 */
688void 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 */
718LPoint3 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.
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.
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,...
CullHandler * get_cull_handler() const
Returns the object that will receive the culled Geoms.
SceneSetup * get_scene() const
Returns the SceneSetup object.
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...
Defines a series of disconnected triangles.
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.
This is the base class for a number of render attributes (other than transform) that may be set on sc...
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().
Represents a texture object, which is typically a single 2-d image but may also represent a 1-d or 3-...
Definition texture.h:72
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.