Unravel Engine C++ Reference
Loading...
Searching...
No Matches
mesh.cpp
Go to the documentation of this file.
1#include "mesh.h"
2#include "camera.h"
4#include "glm/gtc/epsilon.hpp"
5
8#include <logging/logging.h>
10#include <meshoptimizer/src/meshoptimizer.h>
11
12
13#include <engine/engine.h>
15#include <algorithm>
16#include <cmath>
17#include <cstring>
18
19namespace unravel
20{
21
22namespace
23{
24//-----------------------------------------------------------------------------
25// Local Module Level Namespaces.
26//-----------------------------------------------------------------------------
27
28auto hf_height_at(hpp::span<const float> heights, uint32_t vx, int32_t sx, int32_t sz, int32_t ix, int32_t iz) -> double
29{
30 ix = std::clamp(ix, 0, sx);
31 iz = std::clamp(iz, 0, sz);
32 const size_t idx = static_cast<size_t>(iz) * vx + static_cast<size_t>(ix);
33 return static_cast<double>(heights[idx]);
34}
35
36auto hf_dh_dt0(hpp::span<const float> heights, uint32_t vx, int32_t sx, int32_t sz, int32_t ix, int32_t iz) -> double
37{
38 if(sx < 1)
39 {
40 return 0.0;
41 }
42 if(ix <= 0)
43 {
44 return static_cast<double>(sx) * (hf_height_at(heights, vx, sx, sz, 1, iz) - hf_height_at(heights, vx, sx, sz, 0, iz));
45 }
46 if(ix >= sx)
47 {
48 return static_cast<double>(sx) *
49 (hf_height_at(heights, vx, sx, sz, sx, iz) - hf_height_at(heights, vx, sx, sz, sx - 1, iz));
50 }
51 return static_cast<double>(sx) * 0.5 *
52 (hf_height_at(heights, vx, sx, sz, ix + 1, iz) - hf_height_at(heights, vx, sx, sz, ix - 1, iz));
53}
54
55auto hf_dh_dt1(hpp::span<const float> heights, uint32_t vx, int32_t sx, int32_t sz, int32_t ix, int32_t iz) -> double
56{
57 if(sz < 1)
58 {
59 return 0.0;
60 }
61 if(iz <= 0)
62 {
63 return static_cast<double>(sz) * (hf_height_at(heights, vx, sx, sz, ix, 1) - hf_height_at(heights, vx, sx, sz, ix, 0));
64 }
65 if(iz >= sz)
66 {
67 return static_cast<double>(sz) *
68 (hf_height_at(heights, vx, sx, sz, ix, sz) - hf_height_at(heights, vx, sx, sz, ix, sz - 1));
69 }
70 return static_cast<double>(sz) * 0.5 *
71 (hf_height_at(heights, vx, sx, sz, ix, iz + 1) - hf_height_at(heights, vx, sx, sz, ix, iz - 1));
72}
73
74void create_mesh(const gfx::vertex_layout& format,
75 const generator::any_mesh& mesh,
76 mesh::preparation_data& data,
77 math::bbox& bbox)
78{
79 // Determine the correct offset to any relevant elements in the vertex
80 bool has_position = format.has(gfx::attribute::Position);
81 bool has_texcoord0 = format.has(gfx::attribute::TexCoord0);
82 bool has_normals = format.has(gfx::attribute::Normal);
83 bool has_tangents = format.has(gfx::attribute::Tangent);
84 bool has_bitangents = format.has(gfx::attribute::Bitangent);
85 uint16_t vertex_stride = format.getStride();
86
87 auto triangle_count = generator::count(mesh.triangles());
88 auto vertex_count = generator::count(mesh.vertices());
89 data.triangle_count = uint32_t(triangle_count);
90 data.vertex_count = uint32_t(vertex_count);
91
92 // Allocate enough space for the new vertex and triangle data
93 data.vertex_data.resize(data.vertex_count * vertex_stride);
94 data.vertex_flags.resize(data.vertex_count);
95 data.triangle_data.resize(data.triangle_count);
96 mesh::submesh submesh;
97 submesh.data_group_id = 0;
98 submesh.face_count = data.triangle_count;
99 submesh.face_start = 0;
100 submesh.vertex_count = data.vertex_count;
101 submesh.vertex_start = 0;
102
103 uint8_t* current_vertex_ptr = data.vertex_data.data();
104 size_t i = 0;
105 for(const auto& v : mesh.vertices())
106 {
107 math::vec3 position = v.position;
108 math::vec4 normal = math::vec4(v.normal, 0.0f);
109 math::vec2 texcoords0 = v.tex_coord;
110 // Store vertex components
111 if(has_position)
112 gfx::vertex_pack(math::value_ptr(position),
113 false,
114 gfx::attribute::Position,
115 format,
116 current_vertex_ptr,
117 uint32_t(i));
118 if(has_normals)
119 gfx::vertex_pack(math::value_ptr(normal),
120 true,
121 gfx::attribute::Normal,
122 format,
123 current_vertex_ptr,
124 uint32_t(i));
125 if(has_texcoord0)
126 gfx::vertex_pack(math::value_ptr(texcoords0),
127 true,
128 gfx::attribute::TexCoord0,
129 format,
130 current_vertex_ptr,
131 uint32_t(i));
132
133 bbox.add_point(position);
134 i++;
135 }
136
137 size_t tri_idx = 0;
138 for(const auto& triangle : mesh.triangles())
139 {
140 const auto& indices = triangle.vertices;
141 auto& tri = data.triangle_data[tri_idx];
142 tri.indices[0] = uint32_t(indices[0]);
143 tri.indices[1] = uint32_t(indices[1]);
144 tri.indices[2] = uint32_t(indices[2]);
145
146 tri_idx++;
147 }
148
149 // We need to generate binormals / tangents?
150 data.compute_binormals = has_bitangents;
151 data.compute_tangents = has_tangents;
152 submesh.bbox = bbox;
153 data.submeshes.emplace_back(submesh);
154
155}
156
157} // namespace
158
159mesh::mesh() : hardware_vb_(std::make_shared<gfx::vertex_buffer>()), hardware_ib_(std::make_shared<gfx::index_buffer>())
160{
161}
162
164{
165 dispose();
166}
167
169{
170 // Iterate through the different submeshes in the mesh and clean up
171 for(auto submesh : mesh_submeshes_)
172 {
173 // Just perform a standard 'disconnect' in the
174 // regular unload case.
176 }
177
178 mesh_submeshes_.clear();
179 // submesh_lookup_.clear();
180 data_groups_.clear();
181
182 // Release bone palettes and skin data (if any)
183 bone_palettes_.clear();
185
186 // Clean up preparation data.
188 {
190 }
198
199 // Release mesh data memory
202
203 // Clean up LOD data
204 for(auto& lod : lods_)
205 {
206 // Clean up submeshes
207 for(auto* submesh : lod.submeshes_)
208 {
210 }
211 lod.submeshes_.clear();
212 // Clean up index buffer
213 checked_array_delete(lod.system_ib_);
214 lod.hardware_ib_.reset();
215 }
216 lods_.clear();
217 lod_count_ = 1;
218
219 triangle_data_.clear();
220
221 // Release resources
222 hardware_vb_.reset();
223 hardware_ib_.reset();
224
225 // Clear variables
235 face_count_ = 0;
236 vertex_count_ = 0;
237 system_vb_ = nullptr;
238 vertex_format_ = {};
239 system_ib_ = nullptr;
243
244 // Reset structures
245 bbox_.reset();
246}
247
248auto mesh::get_info() const -> info
249{
250 info result{
251 .vertices = vertex_count_,
252 .triangles = face_count_,
253 .submeshes = uint32_t(mesh_submeshes_.size()),
254 .data_groups = uint32_t(data_groups_.size()),
255 .lods = {}
256 };
257
258 // Add simplified LODs
259 for(size_t i = 0; i < lods_.size(); ++i)
260 {
261 const auto& lod = lods_[i];
262 result.lods.push_back(info::lod_info{
263 .triangles = lod.face_count_,
264 .percent = (static_cast<float>(lod.face_count_) / static_cast<float>(face_count_)) * 100.0f,
265 });
266 }
267
268 return result;
269}
271{
272 // APPLOG_TRACE_PERF(std::chrono::milliseconds);
273
274 // If we are already in the process of preparing, this is a no-op.
275 if(prepare_status_ == mesh_status::preparing)
276 {
277 return false;
278 }
279
280 if((prepare_status_ != mesh_status::preparing))
281 {
282 // Clear out anything which is currently loaded in the mesh.
283 dispose();
284
285 } // End if not rolling back or no need to roll back
286
287 // We are in the process of preparing the mesh
288 prepare_status_ = mesh_status::preparing;
289 vertex_format_ = format;
290
291 return true;
292}
293
294// #define SET_VERTICES_WHEN_SETTING_PRIMITIVES 1
295
296auto mesh::set_vertex_source(byte_array_t&& source, uint32_t vertex_count, const gfx::vertex_layout& source_format)
297 -> bool
298{
299 // APPLOG_TRACE_PERF(std::chrono::milliseconds);
300
301 // We can only do this if we are in the process of preparing the mesh
302 if(prepare_status_ != mesh_status::preparing)
303 {
304 APPLOG_ERROR("Attempting to set a mesh vertex source without first calling "
305 "'prepareMesh' is not allowed.\n");
306 return false;
307
308 } // End if not preparing
309
310 // Clear any existing source information.
311 if(preparation_data_.owns_source)
312 {
313 checked_array_delete(preparation_data_.vertex_source);
314 }
315 preparation_data_.vertex_source = nullptr;
316 preparation_data_.source_format = {};
317 preparation_data_.owns_source = false;
318 preparation_data_.vertex_records.clear();
319
320 // Validate requirements
321 if(vertex_count == 0)
322 {
323 return false;
324 }
325
326 // If source format matches the format we're using to prepare
327 // then just store the pointer for this vertex source. Otherwise
328 // we need to allocate a temporary buffer and convert the data.
329 preparation_data_.source_format = source_format;
330 if(source_format.m_hash == vertex_format_.m_hash)
331 {
332 preparation_data_.vertex_source = reinterpret_cast<uint8_t*>(source.data());
333
334 } // End if matching
335 else
336 {
337 preparation_data_.vertex_source = new uint8_t[vertex_count * vertex_format_.getStride()];
338 preparation_data_.owns_source = true;
339 gfx::vertex_convert(vertex_format_,
340 preparation_data_.vertex_source,
341 source_format,
342 reinterpret_cast<uint8_t*>(source.data()),
343 vertex_count);
344 } // End if !matching
345
346 // Some data needs computing? These variables are essentially 'toggles'
347 // that are set largely so that we can early out if it was NEVER necessary
348 // to generate these components (i.e. not one single vertex needed it).
349 if(!source_format.has(gfx::attribute::Normal) && vertex_format_.has(gfx::attribute::Normal))
350 {
351 preparation_data_.compute_normals = true;
352 }
353 if(!source_format.has(gfx::attribute::Bitangent) && vertex_format_.has(gfx::attribute::Bitangent))
354 {
355 preparation_data_.compute_binormals = true;
356 }
357 if(!source_format.has(gfx::attribute::Tangent) && vertex_format_.has(gfx::attribute::Tangent))
358 {
359 preparation_data_.compute_tangents = true;
360 }
361
362 math::vec4 normal{};
363 math::vec4 tangent{};
364 math::vec4 bitangent{};
365 gfx::vertex_unpack(math::value_ptr(normal), gfx::attribute::Normal, vertex_format_, preparation_data_.vertex_source, 0);
366 gfx::vertex_unpack(math::value_ptr(tangent), gfx::attribute::Tangent, vertex_format_, preparation_data_.vertex_source, 0);
367 gfx::vertex_unpack(math::value_ptr(bitangent), gfx::attribute::Bitangent, vertex_format_, preparation_data_.vertex_source, 0);
368 if(math::epsilonEqual(math::length(normal), 0.0f, math::epsilon<float>()))
369 {
370 preparation_data_.compute_normals = true;
371 }
372 if(math::epsilonEqual(math::length(tangent), 0.0f, math::epsilon<float>()))
373 {
374 preparation_data_.compute_tangents = true;
375 }
376 if(math::epsilonEqual(math::length(bitangent), 0.0f, math::epsilon<float>()))
377 {
378 preparation_data_.compute_binormals = true;
379 }
380
381#ifdef SET_VERTICES_WHEN_SETTING_PRIMITIVES
382 // Allocate the vertex records for the new vertex buffer
383 preparation_data_.vertex_records.clear();
384 preparation_data_.vertex_records.resize(vertex_count);
385
386 // Fill with 0xFFFFFFFF initially to indicate that no vertex
387 // originally in this location has yet been inserted into the
388 // final vertex list.
389 memset(preparation_data_.vertex_records.data(), 0xFF, vertex_count * sizeof(uint32_t));
390#else
391 preparation_data_.vertex_data = std::move(source);
392 preparation_data_.vertex_count = vertex_count;
393#endif
394 // Success!
395 return true;
396}
397
399{
400 // APPLOG_TRACE_PERF(std::chrono::milliseconds);
401
402 bbox_ = box;
403 return true;
404}
405
406auto mesh::set_submeshes(const std::vector<submesh>& submeshes) -> bool
407{
408 // APPLOG_TRACE_PERF(std::chrono::milliseconds);
409
410 // We can only do this if we are in the process of preparing the mesh
411 if(prepare_status_ != mesh_status::preparing)
412 {
413 APPLOG_ERROR("Attempting to add primitives to a mesh without first calling "
414 "'prepareMesh' is not allowed.\n");
415 return false;
416
417 } // End if not preparing
418
419 preparation_data_.submeshes = submeshes;
420
421 return true;
422}
423
424auto mesh::set_primitives(triangle_array_t&& triangles) -> bool
425{
426 // APPLOG_TRACE_PERF(std::chrono::milliseconds);
427
428 // We can only do this if we are in the process of preparing the mesh
429 if(prepare_status_ != mesh_status::preparing)
430 {
431 APPLOG_ERROR("Attempting to add primitives to a mesh without first calling "
432 "'prepareMesh' is not allowed.\n");
433 return false;
434
435 } // End if not preparing
436
437#ifdef SET_VERTICES_WHEN_SETTING_PRIMITIVES
438
439 preparation_data_.triangle_count = 0;
440 preparation_data_.triangle_data.clear();
441
442 // Determine the correct offset to any relevant elements in the vertex
443 bool has_position = vertex_format_.has(gfx::attribute::Position);
444 bool has_normal = vertex_format_.has(gfx::attribute::Normal);
445 uint16_t vertex_stride = vertex_format_.getStride();
446
447 // During the construction process we test to see if any specified
448 // vertex normal contains invalid data. If the original source vertex
449 // data did not contain a normal, we can optimize and skip this step.
450 bool source_has_normals = preparation_data_.source_format.has(gfx::attribute::Normal);
451 bool source_has_binormal = preparation_data_.source_format.has(gfx::attribute::Bitangent);
452 bool source_has_tangent = preparation_data_.source_format.has(gfx::attribute::Tangent);
453
454 // In addition, we also record which of the required components each
455 // vertex actually contained based on the following information.
456 uint8_t vertex_flags = 0;
457 if(source_has_normals)
458 {
459 vertex_flags |= preparation_data::source_contains_normal;
460 }
461 if(source_has_binormal)
462 {
463 vertex_flags |= preparation_data::source_contains_binormal;
464 }
465 if(source_has_tangent)
466 {
467 vertex_flags |= preparation_data::source_contains_tangent;
468 }
469
470 // Loop through the specified faces and process them.
471 uint8_t* src_vertices_ptr = preparation_data_.vertex_source;
472
473 for(const auto& src_tri : triangles)
474 {
475 // Retrieve vertex positions (if there are any) so that we can perform
476 // degenerate testing.
477 if(preparation_data_.check_for_degenerates)
478 {
479 if(has_position)
480 {
481 math::vec3 v1;
482 float vf1[4];
483 gfx::vertex_unpack(vf1, gfx::attribute::Position, vertex_format_, src_vertices_ptr, src_tri.indices[0]);
484 math::vec3 v2;
485 float vf2[4];
486 gfx::vertex_unpack(vf2, gfx::attribute::Position, vertex_format_, src_vertices_ptr, src_tri.indices[1]);
487 math::vec3 v3;
488 float vf3[4];
489 gfx::vertex_unpack(vf3, gfx::attribute::Position, vertex_format_, src_vertices_ptr, src_tri.indices[2]);
490 std::memcpy(&v1[0], vf1, 3 * sizeof(float));
491 std::memcpy(&v2[0], vf2, 3 * sizeof(float));
492 std::memcpy(&v3[0], vf3, 3 * sizeof(float));
493
494 // Skip triangle if it is degenerate.
495 if(math::all(math::equal(v1, v2, math::epsilon<float>())) ||
496 math::all(math::equal(v1, v3, math::epsilon<float>())) ||
497 math::all(math::equal(v2, v3, math::epsilon<float>())))
498 {
499 continue;
500 }
501 } // End if has position.
502 }
503 // Prepare a triangle structure ready for population
504 preparation_data_.triangle_count++;
505 preparation_data_.triangle_data.resize(preparation_data_.triangle_count);
506 triangle& triangle_data = preparation_data_.triangle_data[preparation_data_.triangle_count - 1];
507
508 // Set triangle's submesh information.
509 triangle_data.data_group_id = src_tri.data_group_id;
510
511 // For each index in the face
512 for(uint32_t j = 0; j < 3; ++j)
513 {
514 // Extract the original index from the specified index buffer
515 uint32_t orig_index = src_tri.indices[j];
516
517 // Retrieve the vertex record for the original vertex
518 uint32_t index = preparation_data_.vertex_records[orig_index];
519
520 // Have we inserted this vertex into the vertex buffer previously?
521 if(index == 0xFFFFFFFF)
522 {
523 // Vertex does not yet exist in the vertex buffer we are preparing
524 // so copy the vertex in and record the index mapping for this vertex.
525 index = preparation_data_.vertex_count++;
526 preparation_data_.vertex_records[orig_index] = index;
527
528 // Resize the output vertex buffer ready to hold this new data.
529 size_t initial_size = preparation_data_.vertex_data.size();
530 preparation_data_.vertex_data.resize(initial_size + vertex_stride);
531
532 // Copy the data in.
533 uint8_t* src_ptr = src_vertices_ptr + (orig_index * vertex_stride);
534 uint8_t* dst_ptr = &preparation_data_.vertex_data[initial_size];
535 std::memcpy(dst_ptr, src_ptr, vertex_stride);
536
537 // Also record other pertenant details about this vertex.
538 preparation_data_.vertex_flags.push_back(vertex_flags);
539
540 // Clear any invalid normals (completely messes up HDR if ANY NaNs make
541 // it this far)
542 // if(has_normal && source_has_normals)
543 // {
544 // float fnorm[4];
545 // gfx::vertex_unpack(fnorm, gfx::attribute::Normal, vertex_format_, dst_ptr);
546 // if(std::isnan(fnorm[0]) || std::isnan(fnorm[1]) || std::isnan(fnorm[2]))
547 // {
548 // gfx::vertex_pack(fnorm, true, gfx::attribute::Normal, vertex_format_, dst_ptr);
549 // }
550 // } // End if have normal
551
552 // Grow the size of the bounding box
553 if(has_position)
554 {
555 float fpos[4];
556 gfx::vertex_unpack(fpos, gfx::attribute::Position, vertex_format_, dst_ptr);
557 bbox_.add_point(math::vec3(fpos[0], fpos[1], fpos[2]));
558 }
559
560 } // End if vertex not recorded in this buffer yet
561
562 // Copy the index in
563 triangle_data.indices[j] = index;
564
565 } // Next Index
566
567 } // Next Face
568
569#else
570 preparation_data_.triangle_count = triangles.size();
571 preparation_data_.triangle_data = std::move(triangles);
572
573#endif
574
575 // Success!
576 return true;
577}
578
579auto mesh::bind_skin(const skin_bind_data& bind_data) -> bool
580{
581 // APPLOG_TRACE_PERF(std::chrono::milliseconds);
582
583 if(!bind_data.has_bones())
584 {
585 return true;
586 }
587
588 if(prepare_status_ == mesh_status::prepared)
589 {
590 return false;
591 }
592
594 skin_bind_data_.clear();
595 skin_bind_data_ = bind_data;
596
597 // Build a list of all bone indices and associated weights for each vertex.
598 skin_bind_data_.build_vertex_table(preparation_data_.vertex_count, preparation_data_.vertex_records, vertex_table);
599 skin_bind_data_.clear_vertex_influences(); // Clear unneeded data to save space.
600
601 uint32_t palette_size = gfx::get_max_blend_transforms();
602
603 // Destroy any previous palette entries.
604 bone_palettes_.clear();
605
606 triangle_array_t& tri_data = preparation_data_.triangle_data;
607
608 bone_palettes_.reserve(preparation_data_.submeshes.size());
609 // Iterate over each submesh to generate palettes.
610 for(size_t palette_id = 0; palette_id < preparation_data_.submeshes.size(); ++palette_id)
611 {
612 auto& submesh = preparation_data_.submeshes[palette_id];
613 // face_influences used_bones;
614
615 std::vector<bool> used_bones(gfx::get_max_blend_transforms(), false);
616 std::vector<uint32_t> faces; // Collect faces in this submesh
617 faces.reserve(submesh.face_count);
618 // Collect all unique bone indices influencing this submesh and the faces.
619 for(uint32_t i = submesh.face_start; i < submesh.face_start + submesh.face_count; ++i)
620 {
621 faces.push_back(i);
622
623 for(uint32_t vertex_index : tri_data[i].indices)
624 {
625 const auto& data = vertex_table[vertex_index];
626 for(const auto& influence : data.influences)
627 {
628 // used_bones.bones[static_cast<uint32_t>(influence)] = 1;
629 used_bones[static_cast<uint32_t>(influence)] = true;
630 }
631 }
632 }
633
634 // Create a bone palette for this submesh.
635 bone_palette new_palette(palette_size);
637
638 // Assign bones and faces to the palette.
639 // new_palette.assign_bones(used_bones.bones, faces);
640 new_palette.assign_bones(used_bones, faces);
641 bone_palettes_.push_back(new_palette);
642
643 // Assign the palette ID to each vertex in this submesh.
644
645 auto face_start = submesh.face_start;
646 auto face_end = submesh.face_start + submesh.face_count;
647 for(uint32_t i = face_start; i < face_end; ++i)
648 {
649 for(uint32_t k = 0; k < 3; ++k)
650 {
651 uint32_t vertex_index = tri_data[i].indices[k];
652 auto& data = vertex_table[vertex_index];
653
654 // If the vertex is not already assigned to a palette, assign it.
655 if(data.palette == -1)
656 {
657 data.palette = static_cast<int32_t>(palette_id);
658
659 // Check if the vertex index falls within the submesh's vertex range
660 if(submesh.vertex_start == -1 || vertex_index < submesh.vertex_start)
661 {
662 submesh.vertex_start = vertex_index;
663 }
664 if(vertex_index >= submesh.vertex_start + submesh.vertex_count)
665 {
666 submesh.vertex_count = (vertex_index - submesh.vertex_start) + 1;
667 }
668 }
669 else if(data.palette != static_cast<int32_t>(palette_id))
670 {
671 // Vertex is shared between submeshes, need to duplicate it.
672 uint32_t new_index = static_cast<uint32_t>(vertex_table.size());
673
674 // Create a new vertex_data.
675 skin_bind_data::vertex_data new_vertex(data);
676 new_vertex.original_vertex = vertex_index;
677 new_vertex.palette = static_cast<int32_t>(palette_id);
678 vertex_table.push_back(new_vertex);
679
680 // Update submesh's vertex range
681 if(submesh.vertex_start == -1 || new_index < submesh.vertex_start)
682 {
683 submesh.vertex_start = new_index;
684 }
685 if(new_index >= submesh.vertex_start + submesh.vertex_count)
686 {
687 submesh.vertex_count = (new_index - submesh.vertex_start) + 1;
688 }
689
690 // Update triangle index to point to new vertex.
691 tri_data[i].indices[k] = new_index;
692 }
693 // Else, the vertex is already assigned to this palette; no action needed.
694 }
695 }
696 }
697
698 // Adjust vertex format to include blend weights and indices if necessary.
699 gfx::vertex_layout new_format(vertex_format_);
700 gfx::vertex_layout original_format = vertex_format_;
701 bool has_weights = new_format.has(gfx::attribute::Weight);
702 bool has_indices = new_format.has(gfx::attribute::Indices);
703 if(!has_weights || !has_indices)
704 {
705 new_format.m_hash = 0;
706 if(!has_weights)
707 {
708 new_format.add(gfx::attribute::Weight, 4, gfx::attribute_type::Float);
709 }
710 if(!has_indices)
711 {
712 new_format.add(gfx::attribute::Indices, 4, gfx::attribute_type::Float, false, true);
713 }
714
715 new_format.end();
716 // Update the vertex format.
717 vertex_format_ = new_format;
718 }
719
720 // Get access to final data offset information.
721 uint16_t vertex_stride = vertex_format_.getStride();
722
723 // Now we need to update the vertex data as required.
724 uint32_t original_vertex_count = preparation_data_.vertex_count;
725 if(vertex_format_.m_hash != original_format.m_hash)
726 {
727 // Format has changed, run conversion.
728 byte_array_t original_buffer(preparation_data_.vertex_data);
729 preparation_data_.vertex_data.clear();
730 preparation_data_.vertex_data.resize(vertex_table.size() * vertex_stride);
731 preparation_data_.vertex_flags.resize(vertex_table.size());
732
733 gfx::vertex_convert(vertex_format_,
734 preparation_data_.vertex_data.data(),
735 original_format,
736 original_buffer.data(),
737 original_vertex_count);
738 }
739 else
740 {
741 // No conversion required, just ensure buffer is large enough.
742 preparation_data_.vertex_data.resize(vertex_table.size() * vertex_stride);
743 preparation_data_.vertex_flags.resize(vertex_table.size());
744 }
745
746 // Update vertex data with new bone indices and weights.
747 uint8_t* src_vertices_ptr = preparation_data_.vertex_data.data();
748 for(size_t i = 0; i < vertex_table.size(); ++i)
749 {
750 auto& data = vertex_table[i];
751
752 // Determine which palette this vertex belongs to.
753 int32_t palette_id = data.palette;
754
755 // Skip if the vertex isn't assigned to any palette.
756 if(palette_id < 0)
757 {
758 continue;
759 }
760
761 const auto& palette = bone_palettes_[static_cast<size_t>(palette_id)];
762
763 // If this is a new vertex, duplicate data from original vertex.
764 if(i >= original_vertex_count)
765 {
766 std::memcpy(src_vertices_ptr + (i * vertex_stride),
767 src_vertices_ptr + (data.original_vertex * vertex_stride),
768 vertex_stride);
769
770 // Also duplicate additional vertex data.
771 preparation_data_.vertex_flags[i] = preparation_data_.vertex_flags[data.original_vertex];
772 }
773
774 uint32_t max_bones = std::min<uint32_t>(4, uint32_t(data.influences.size()));
775
776 if(max_bones > 0)
777 {
778 // Assign bone indices (the index to the relevant entry in the palette, not the main bone list index) and
779 // weights.
780 math::vec4 blend_weights(0.0f, 0.0f, 0.0f, 0.0f);
781 math::vec4 blend_indices(0.0f, 0.0f, 0.0f, 0.0f);
782
783 for(uint32_t j = 0; j < max_bones; ++j)
784 {
785 // Map global bone index to local palette index.
786 uint32_t palette_bone_index =
787 palette.translate_bone_to_palette(static_cast<uint32_t>(data.influences[j]));
788
789 blend_indices[static_cast<math::vec4::length_type>(j)] = static_cast<float>(palette_bone_index);
790 blend_weights[static_cast<math::vec4::length_type>(j)] = data.weights[j];
791 }
792
793 gfx::vertex_pack(math::value_ptr(blend_weights),
794 false,
795 gfx::attribute::Weight,
796 vertex_format_,
797 src_vertices_ptr,
798 uint32_t(i));
799
800 gfx::vertex_pack(math::value_ptr(blend_indices),
801 false,
802 gfx::attribute::Indices,
803 vertex_format_,
804 src_vertices_ptr,
805 uint32_t(i));
806 }
807 }
808
809 // Update vertex count to match final size.
810 preparation_data_.vertex_count = static_cast<uint32_t>(vertex_table.size());
811
812 // Skin is now bound.
813 return true;
814}
815
816auto mesh::bind_armature(std::unique_ptr<armature_node>& root) -> bool
817{
818 // APPLOG_TRACE_PERF(std::chrono::milliseconds);
819
820 root_ = std::move(root);
821 return true;
822}
823
824auto mesh::load_mesh(load_data&& data) -> bool
825{
826 // APPLOG_TRACE_PERF(std::chrono::milliseconds);
827
828 default_material_uids_ = std::move(data.default_material_uids);
829
830 const bool has_skin_data = data.skin_data.has_bones();
831 const bool skin_is_prepared = data.skin_is_prepared;
832 auto bone_palette_bones = std::move(data.bone_palette_bones);
833
834 bool result = true;
835 result &= prepare_mesh(data.vertex_format);
836 result &= set_bounding_box(data.bbox);
837 result &= set_vertex_source(std::move(data.vertex_data), data.vertex_count, data.vertex_format);
838 result &= set_primitives(std::move(data.triangle_data));
839 result &= set_submeshes(data.submeshes);
840 if(has_skin_data && skin_is_prepared)
841 {
842 skin_bind_data_.clear();
843 skin_bind_data_ = data.skin_data;
844 }
845 else
846 {
847 result &= bind_skin(data.skin_data);
848 }
849 result &= bind_armature(data.root_node);
850 result &= end_prepare();
851
852 // Restore bone palettes for pre-skinned meshes (compiled assets) without re-running bind_skin.
853 if(result && has_skin_data && skin_is_prepared)
854 {
855 bone_palettes_.clear();
856 const uint32_t palette_size = gfx::get_max_blend_transforms();
857 bone_palettes_.reserve(data.submeshes.size());
858 for(size_t palette_id = 0; palette_id < data.submeshes.size(); ++palette_id)
859 {
860 bone_palette palette(palette_size);
861 palette.set_data_group(data.submeshes[palette_id].data_group_id);
862 if(palette_id < bone_palette_bones.size())
863 {
864 palette.assign_bones(bone_palette_bones[palette_id]);
865 }
866 bone_palettes_.push_back(std::move(palette));
867 }
868 }
869
870 // Restore LODs from load_data if they exist
871 if(result)
872 {
873 result &= restore_lods_from_load_data(data);
874 }
875
876 return result;
877}
878
880 float width,
881 float height,
882 uint32_t width_segments,
883 uint32_t height_segments,
884 mesh_create_origin origin,
885 bool hardware_copy /* = true */) -> bool
886{
887 // We are in the process of preparing.
888 prepare_mesh(format);
889
890 using namespace generator;
891 plane_mesh_t plane({width * 0.5f, height * 0.5f}, {width_segments, height_segments});
892 math::quat rot1(math::vec3(math::radians(-90.0f), 0.f, 0.0f));
893 math::quat rot2(math::vec3(math::radians(90.0f), 0.f, 0.0f));
894
895 auto plane1 = rotate_mesh(plane, rot1);
896 auto plane2 = rotate_mesh(plane, rot2);
897 auto mesh = merge_mesh(plane1, plane2);
898
899 create_mesh(vertex_format_, mesh, preparation_data_, bbox_);
900 // Finish up
901 return end_prepare(hardware_copy);
902}
903
905 hpp::span<const float> heights,
906 uint32_t segments_x,
907 uint32_t segments_z,
908 float half_extent_x,
909 float half_extent_z,
910 float height_scale,
911 mesh_create_origin origin,
912 bool hardware_copy) -> bool
913{
914 (void)origin;
915 const uint32_t sx = segments_x;
916 const uint32_t sz = segments_z;
917 if(sx < 1u || sz < 1u)
918 {
919 return false;
920 }
921 const uint32_t vx = sx + 1u;
922 if(heights.size() < static_cast<size_t>(vx) * static_cast<size_t>(sz + 1u))
923 {
924 return false;
925 }
926
927 prepare_mesh(format);
928
929 const double hx = static_cast<double>(half_extent_x);
930 const double hz = static_cast<double>(half_extent_z);
931 const double hs = static_cast<double>(height_scale);
932 const int32_t isx = static_cast<int32_t>(sx);
933 const int32_t isz = static_cast<int32_t>(sz);
934
935 auto eval = [heights, vx, sx, sz, hx, hz, hs, isx, isz](const gml::dvec2& t) -> generator::mesh_vertex_t
936 {
937 const double fx = t[0] * static_cast<double>(sx);
938 const double fz = t[1] * static_cast<double>(sz);
939 int32_t ix = static_cast<int32_t>(std::lround(fx));
940 int32_t iz = static_cast<int32_t>(std::lround(fz));
941 ix = std::clamp(ix, 0, isx);
942 iz = std::clamp(iz, 0, isz);
943
945 v.position[0] = (t[0] - 0.5) * 2.0 * hx;
946 v.position[1] = hs * hf_height_at(heights, vx, isx, isz, ix, iz);
947 v.position[2] = (t[1] - 0.5) * 2.0 * hz;
948 v.tex_coord[0] = t[0];
949 v.tex_coord[1] = t[1];
950
951 const gml::dvec3 dr_dt0{2.0 * hx, hs * hf_dh_dt0(heights, vx, isx, isz, ix, iz), 0.0};
952 const gml::dvec3 dr_dt1{0.0, hs * hf_dh_dt1(heights, vx, isx, isz, ix, iz), 2.0 * hz};
953 v.normal = -gml::normalize(gml::cross(dr_dt1, dr_dt0));
954
955 return v;
956 };
957
958 generator::parametric_mesh_t hf_mesh(eval, gml::ivec2{static_cast<int>(sx), static_cast<int>(sz)});
959 math::quat rot(math::vec3(math::radians(-180.0f), 0.f, 0.0f));
960 auto mesh = rotate_mesh(hf_mesh, rot);
961 create_mesh(vertex_format_, mesh, preparation_data_, bbox_);
962
963 return end_prepare(hardware_copy);
964}
965
967 float width,
968 float height,
969 float depth,
970 uint32_t width_segments,
971 uint32_t height_segments,
972 uint32_t depth_segments,
973 mesh_create_origin origin,
974 bool hardware_copy /* = true */) -> bool
975{
976 // We are in the process of preparing.
977 prepare_mesh(format);
978
979 using namespace generator;
980 box_mesh_t box({width * 0.5f, height * 0.5f, depth * 0.5f}, {width_segments, height_segments, depth_segments});
981 math::quat rot(math::vec3(math::radians(-90.0f), 0.f, 0.0f));
982 auto mesh = rotate_mesh(box, rot);
983
984 create_mesh(vertex_format_, mesh, preparation_data_, bbox_);
985 // Finish up
986 return end_prepare(hardware_copy);
987}
988
989
991 float width,
992 float height,
993 float depth,
994 uint32_t width_segments,
995 uint32_t height_segments,
996 uint32_t depth_segments,
997 mesh_create_origin origin,
998 bool hardware_copy /* = true */) -> bool
999{
1000 // We are in the process of preparing.
1001 prepare_mesh(format);
1002
1003 using namespace generator;
1004 rounded_box_mesh_t rounded_box(0.05, {width * 0.5f, height * 0.5f, depth * 0.5f}, 4, {width_segments, height_segments, depth_segments});
1005 math::quat rot(math::vec3(math::radians(-90.0f), 0.f, 0.0f));
1006 auto mesh = rotate_mesh(rounded_box, rot);
1007
1008 create_mesh(vertex_format_, mesh, preparation_data_, bbox_);
1009 // Finish up
1010 return end_prepare(hardware_copy);
1011}
1012
1014 float radius,
1015 uint32_t stacks,
1016 uint32_t slices,
1017 mesh_create_origin origin,
1018 bool hardware_copy /* = true */) -> bool
1019{
1020 // We are in the process of preparing.
1021 prepare_mesh(format);
1022
1023 using namespace generator;
1024 sphere_mesh_t sphere(radius, static_cast<int>(slices), static_cast<int>(stacks));
1025 math::quat rot(math::vec3(math::radians(-90.0f), 0.f, 0.0f));
1026 auto mesh = rotate_mesh(sphere, rot);
1027
1028 create_mesh(vertex_format_, mesh, preparation_data_, bbox_);
1029 // Finish up
1030 return end_prepare(hardware_copy);
1031}
1032
1034 float radius,
1035 float height,
1036 uint32_t stacks,
1037 uint32_t slices,
1038 mesh_create_origin origin,
1039 bool hardware_copy /* = true */) -> bool
1040{
1041 // Clear out old data.
1042 dispose();
1043
1044 // We are in the process of preparing.
1045 prepare_status_ = mesh_status::preparing;
1046 vertex_format_ = format;
1047
1048 using namespace generator;
1049 capped_cylinder_mesh_t cylinder(radius, height * 0.5, static_cast<int>(slices), static_cast<int>(stacks));
1050 math::quat rot(math::vec3(math::radians(-90.0f), 0.f, 0.0f));
1051 auto mesh = rotate_mesh(cylinder, rot);
1052
1053 create_mesh(vertex_format_, mesh, preparation_data_, bbox_);
1054 // Finish up
1055 return end_prepare(hardware_copy);
1056}
1057
1059 float radius,
1060 float height,
1061 uint32_t stacks,
1062 uint32_t slices,
1063 mesh_create_origin origin,
1064 bool hardware_copy /* = true */) -> bool
1065{
1066 // We are in the process of preparing.
1067 prepare_mesh(format);
1068
1069 using namespace generator;
1070 capsule_mesh_t capsule(radius, height * 0.5, static_cast<int>(slices), static_cast<int>(stacks));
1071 math::quat rot(math::vec3(math::radians(-90.0f), 0.f, 0.0f));
1072 auto mesh = rotate_mesh(capsule, rot);
1073
1074 create_mesh(vertex_format_, mesh, preparation_data_, bbox_);
1075 // Finish up
1076 return end_prepare(hardware_copy);
1077}
1078
1080 float radius,
1081 float radius_tip,
1082 float height,
1083 uint32_t stacks,
1084 uint32_t slices,
1085 mesh_create_origin origin,
1086 bool hardware_copy /* = true */) -> bool
1087{
1088 // We are in the process of preparing.
1089 prepare_mesh(format);
1090
1091 using namespace generator;
1092 capped_cone_mesh_t cone(radius, 1.0, static_cast<int>(stacks), static_cast<int>(slices));
1093 math::quat rot(math::vec3(math::radians(-90.0f), 0.f, 0.0f));
1094 auto mesh = rotate_mesh(cone, rot);
1095
1096 create_mesh(vertex_format_, mesh, preparation_data_, bbox_);
1097 // Finish up
1098 return end_prepare(hardware_copy);
1099}
1100
1102 float outer_radius,
1103 float inner_radius,
1104 uint32_t bands,
1105 uint32_t sides,
1106 mesh_create_origin origin,
1107 bool hardware_copy /* = true */) -> bool
1108{
1109 // We are in the process of preparing.
1110 prepare_mesh(format);
1111
1112 using namespace generator;
1113 torus_mesh_t torus(inner_radius, outer_radius, static_cast<int>(sides), static_cast<int>(bands));
1114 math::quat rot(math::vec3(math::radians(-90.0f), 0.f, 0.0f));
1115 auto mesh = rotate_mesh(torus, rot);
1116
1117 create_mesh(vertex_format_, mesh, preparation_data_, bbox_);
1118 // Finish up
1119 return end_prepare(hardware_copy);
1120}
1121
1122auto mesh::create_teapot(const gfx::vertex_layout& format, bool hardware_copy /*= true*/) -> bool
1123{
1124 // We are in the process of preparing.
1125 prepare_mesh(format);
1126
1127 using namespace generator;
1128 teapot_mesh_t teapot;
1129 math::quat rot(math::vec3(math::radians(-90.0f), 0.f, 0.0f));
1130 auto mesh = rotate_mesh(teapot, rot);
1131
1132 create_mesh(vertex_format_, mesh, preparation_data_, bbox_);
1133 // Finish up
1134 return end_prepare(hardware_copy);
1135}
1136
1137auto mesh::create_icosahedron(const gfx::vertex_layout& format, bool hardware_copy /*= true*/) -> bool
1138{
1139 // We are in the process of preparing.
1140 prepare_mesh(format);
1141
1142 using namespace generator;
1143 icosahedron_mesh_t icosahedron;
1144 math::quat rot(math::vec3(math::radians(-90.0f), 0.f, 0.0f));
1145 auto mesh = rotate_mesh(icosahedron, rot);
1146
1147 create_mesh(vertex_format_, mesh, preparation_data_, bbox_);
1148 // Finish up
1149 return end_prepare(hardware_copy);
1150}
1151
1152auto mesh::create_dodecahedron(const gfx::vertex_layout& format, bool hardware_copy /*= true*/) -> bool
1153{
1154 // We are in the process of preparing.
1155 prepare_mesh(format);
1156
1157 using namespace generator;
1158 dodecahedron_mesh_t dodecahedron;
1159 math::quat rot(math::vec3(math::radians(-90.0f), 0.f, 0.0f));
1160 auto mesh = rotate_mesh(dodecahedron, rot);
1161
1162 create_mesh(vertex_format_, mesh, preparation_data_, bbox_);
1163 // Finish up
1164 return end_prepare(hardware_copy);
1165}
1166
1167auto mesh::create_icosphere(const gfx::vertex_layout& format, int tesselation_level, bool hardware_copy /*= true*/)
1168 -> bool
1169{
1170 // We are in the process of preparing.
1171 prepare_mesh(format);
1172
1173 using namespace generator;
1174 ico_sphere_mesh_t icosphere(1, tesselation_level + 1);
1175 math::quat rot(math::vec3(math::radians(-90.0f), 0.f, 0.0f));
1176 auto mesh = rotate_mesh(icosphere, rot);
1177
1178 create_mesh(vertex_format_, mesh, preparation_data_, bbox_);
1179 // Finish up
1180 return end_prepare(hardware_copy);
1181}
1182
1184{
1185 // Scan the preparation data for degenerate triangles.
1186 uint16_t position_offset = vertex_format_.getOffset(gfx::attribute::Position);
1187 // uint16_t vertex_stride = _vertex_format.getStride();
1188 uint8_t* src_vertices_ptr = preparation_data_.vertex_data.data() + position_offset;
1189
1191 {
1192 for(uint32_t i = 0; i < preparation_data_.triangle_count; ++i)
1193 {
1195 math::vec3 v1;
1196 float vf1[4];
1197 gfx::vertex_unpack(vf1, gfx::attribute::Position, vertex_format_, src_vertices_ptr, tri.indices[0]);
1198 math::vec3 v2;
1199 float vf2[4];
1200 gfx::vertex_unpack(vf2, gfx::attribute::Position, vertex_format_, src_vertices_ptr, tri.indices[1]);
1201 math::vec3 v3;
1202 float vf3[4];
1203 gfx::vertex_unpack(vf3, gfx::attribute::Position, vertex_format_, src_vertices_ptr, tri.indices[2]);
1204 std::memcpy(&v1[0], vf1, 3 * sizeof(float));
1205 std::memcpy(&v2[0], vf2, 3 * sizeof(float));
1206 std::memcpy(&v3[0], vf3, 3 * sizeof(float));
1207
1208 math::vec3 c = math::cross(v2 - v1, v3 - v1);
1209 if(math::length2(c) < (4.0f * 0.000001f * 0.000001f))
1210 {
1212 }
1213
1214 } // Next triangle
1215 }
1216}
1217
1218auto mesh::end_prepare(bool hardware_copy, bool build_buffers, bool weld, bool optimize) -> bool
1219{
1220 // APPLOG_TRACE_PERF(std::chrono::milliseconds);
1221
1222 // Were we previously preparing?
1223 if(prepare_status_ != mesh_status::preparing)
1224 {
1225 APPLOG_ERROR("Attempting to call 'end_prepare' on a mesh without first "
1226 "calling 'prepare_mesh' is not "
1227 "allowed.\n");
1228 return false;
1229
1230 } // End if previously preparing
1231
1232 // Check for degenerates
1233 check_for_degenerates();
1234
1235 // Process the vertex data in order to generate any additional components that
1236 // may be necessary
1237 // (i.e. Normal, Binormal and Tangent)
1238 if(!generate_vertex_components(weld))
1239 {
1240 return false;
1241 }
1242
1243 // Allocate the system memory vertex buffer ready for population.
1244 vertex_count_ = preparation_data_.vertex_count;
1245 system_vb_ = new uint8_t[vertex_count_ * vertex_format_.getStride()];
1246
1247 // Copy vertex data into the new buffer and dispose of the temporary data.
1248 std::memcpy(system_vb_, preparation_data_.vertex_data.data(), vertex_count_ * vertex_format_.getStride());
1249 preparation_data_.vertex_data.clear();
1250 preparation_data_.vertex_flags.clear();
1251 preparation_data_.vertex_count = 0;
1252
1253 // Index data has been updated and potentially needs to be serialized.
1254 if(build_buffers)
1255 {
1256 build_vb(hardware_copy);
1257 }
1258
1259 // Allocate the memory for our system memory index buffer
1260 face_count_ = preparation_data_.triangle_count;
1261 system_ib_ = new uint32_t[face_count_ * 3];
1262
1263 // Finally perform the final sort of the mesh data in order
1264 // to build the index buffer and submesh tables
1265 if(!sort_mesh_data())
1266 {
1267 return false;
1268 }
1269
1270 // Hardware versions of the final buffer were required?
1271 if(build_buffers)
1272 {
1273 build_ib(hardware_copy);
1274 }
1275
1276 if(preparation_data_.owns_source)
1277 {
1278 checked_array_delete(preparation_data_.vertex_source);
1279 }
1280 preparation_data_.vertex_source = nullptr;
1281
1282 // The mesh is now prepared
1283 prepare_status_ = mesh_status::prepared;
1284 hardware_mesh_ = hardware_copy;
1285 optimize_mesh_ = optimize;
1286
1287 // Success!
1288 return true;
1289}
1290
1291void mesh::build_vb(bool hardware_copy)
1292{
1293 // A video memory copy of the mesh was requested?
1294 if(hardware_copy)
1295 {
1296 // Calculate the required size of the vertex buffer
1297 auto buffer_size = vertex_count_ * vertex_format_.getStride();
1298
1299 // Compute-read flags so the vertex buffer can be bound as a raw read-only
1300 // Buffer<float> inside shaders (e.g. vertex pulling for wireframe overlay).
1301 const uint16_t vb_flags =
1302 BGFX_BUFFER_COMPUTE_READ | BGFX_BUFFER_COMPUTE_FORMAT_32X1 | BGFX_BUFFER_COMPUTE_TYPE_FLOAT;
1303
1304 const gfx::memory_view* mem = gfx::make_ref(system_vb_, buffer_size);
1305 hardware_vb_ = std::make_shared<gfx::vertex_buffer>(mem, vertex_format_, vb_flags);
1306
1307 } // End if video memory vertex buffer required
1308}
1309
1310void mesh::build_ib(bool hardware_copy)
1311{
1312 // Hardware versions of the final buffer were required?
1313 if(hardware_copy)
1314 {
1315 // Calculate the required size of the index buffer
1316 auto buffer_size = static_cast<uint32_t>(size_t(face_count_ * 3) * sizeof(uint32_t));
1317
1318 // Compute-read flags so the (32-bit) index buffer can be bound as a raw
1319 // read-only Buffer<uint> inside shaders (e.g. vertex pulling for wireframe overlay).
1320 const uint16_t ib_flags = BGFX_BUFFER_INDEX32
1321 | BGFX_BUFFER_COMPUTE_READ
1322 | BGFX_BUFFER_COMPUTE_FORMAT_32X1
1323 | BGFX_BUFFER_COMPUTE_TYPE_UINT;
1324
1325 // Allocate hardware buffer if required (i.e. it does not already exist).
1326 if(!hardware_ib_)
1327 {
1328 const gfx::memory_view* mem = gfx::make_ref(system_ib_, buffer_size);
1329 hardware_ib_ = std::make_shared<gfx::index_buffer>(mem, ib_flags);
1330 } // End if not allocated
1331 else
1332 {
1333 auto ib = std::static_pointer_cast<gfx::index_buffer>(hardware_ib_);
1334 if(!ib->is_valid())
1335 {
1336 const gfx::memory_view* mem = gfx::make_ref(system_ib_, buffer_size);
1337 hardware_ib_ = std::make_shared<gfx::index_buffer>(mem, ib_flags);
1338 }
1339 }
1340
1341 } // End if hardware buffer required
1342}
1343
1344auto mesh::generate_adjacency(std::vector<uint32_t>& adjacency) -> bool
1345{
1346 std::map<adjacent_edge_key, uint32_t> edge_tree;
1347 std::map<adjacent_edge_key, uint32_t>::iterator it_edge;
1348
1349 // What is the status of the mesh?
1350 if(prepare_status_ != mesh_status::prepared)
1351 {
1352 // Validate requirements
1353 if(preparation_data_.triangle_count == 0)
1354 {
1355 return false;
1356 }
1357
1358 // Retrieve useful data offset information.
1359 uint16_t position_offset = vertex_format_.getOffset(gfx::attribute::Position);
1360 uint16_t vertex_stride = vertex_format_.getStride();
1361
1362 // Insert all edges into the edge tree
1363 uint8_t* src_vertices_ptr = preparation_data_.vertex_data.data() + position_offset;
1364 for(uint32_t i = 0; i < preparation_data_.triangle_count; ++i)
1365 {
1366 adjacent_edge_key edge;
1367
1368 // Degenerate triangles cannot participate.
1369 const triangle& tri = preparation_data_.triangle_data[i];
1371 continue;
1372
1373 // Retrieve positions of each referenced vertex.
1374 const math::vec3* v1 =
1375 reinterpret_cast<const math::vec3*>(src_vertices_ptr + (tri.indices[0] * vertex_stride));
1376 const math::vec3* v2 =
1377 reinterpret_cast<const math::vec3*>(src_vertices_ptr + (tri.indices[1] * vertex_stride));
1378 const math::vec3* v3 =
1379 reinterpret_cast<const math::vec3*>(src_vertices_ptr + (tri.indices[2] * vertex_stride));
1380
1381 // edge 1
1382 edge.vertex1 = v1;
1383 edge.vertex2 = v2;
1384 edge_tree[edge] = i;
1385
1386 // edge 2
1387 edge.vertex1 = v2;
1388 edge.vertex2 = v3;
1389 edge_tree[edge] = i;
1390
1391 // edge 3
1392 edge.vertex1 = v3;
1393 edge.vertex2 = v1;
1394 edge_tree[edge] = i;
1395
1396 } // Next Face
1397
1398 // Size the output array.
1399 adjacency.resize(preparation_data_.triangle_count * 3, 0xFFFFFFFF);
1400
1401 // Now, find any adjacent edges for each triangle edge
1402 for(uint32_t i = 0; i < preparation_data_.triangle_count; ++i)
1403 {
1404 adjacent_edge_key edge;
1405
1406 // Degenerate triangles cannot participate.
1407 const triangle& tri = preparation_data_.triangle_data[i];
1409 {
1410 continue;
1411 }
1412
1413 // Retrieve positions of each referenced vertex.
1414 const math::vec3* v1 =
1415 reinterpret_cast<const math::vec3*>(src_vertices_ptr + (tri.indices[0] * vertex_stride));
1416 const math::vec3* v2 =
1417 reinterpret_cast<const math::vec3*>(src_vertices_ptr + (tri.indices[1] * vertex_stride));
1418 const math::vec3* v3 =
1419 reinterpret_cast<const math::vec3*>(src_vertices_ptr + (tri.indices[2] * vertex_stride));
1420
1421 // Note: Notice below that the order of the edge vertices
1422 // is swapped. This is because we want to find the
1423 // matching ADJACENT edge, rather than simply finding
1424 // the same edge that we're currently processing.
1425
1426 // edge 1
1427 edge.vertex2 = v1;
1428 edge.vertex1 = v2;
1429
1430 // Find the matching adjacent edge
1431 it_edge = edge_tree.find(edge);
1432 if(it_edge != edge_tree.end())
1433 {
1434 adjacency[(i * 3)] = it_edge->second;
1435 }
1436
1437 // edge 2
1438 edge.vertex2 = v2;
1439 edge.vertex1 = v3;
1440
1441 // Find the matching adjacent edge
1442 it_edge = edge_tree.find(edge);
1443 if(it_edge != edge_tree.end())
1444 {
1445 adjacency[(i * 3) + 1] = it_edge->second;
1446 }
1447
1448 // edge 3
1449 edge.vertex2 = v3;
1450 edge.vertex1 = v1;
1451
1452 // Find the matching adjacent edge
1453 it_edge = edge_tree.find(edge);
1454 if(it_edge != edge_tree.end())
1455 {
1456 adjacency[(i * 3) + 2] = it_edge->second;
1457 }
1458
1459 } // Next Face
1460
1461 } // End if not prepared
1462 else
1463 {
1464 // Validate requirements
1465 if(face_count_ == 0)
1466 {
1467 return false;
1468 }
1469
1470 // Retrieve useful data offset information.
1471 uint16_t position_offset = vertex_format_.getOffset(gfx::attribute::Position);
1472 uint16_t vertex_stride = vertex_format_.getStride();
1473
1474 // Insert all edges into the edge tree
1475 uint8_t* src_vertices_ptr = system_vb_ + position_offset;
1476 uint32_t* src_indices_ptr = system_ib_;
1477 for(uint32_t i = 0; i < face_count_; ++i, src_indices_ptr += 3)
1478 {
1479 adjacent_edge_key edge;
1480
1481 // Retrieve positions of each referenced vertex.
1482 const auto* v1 =
1483 reinterpret_cast<const math::vec3*>(src_vertices_ptr + (src_indices_ptr[0] * vertex_stride));
1484 const auto* v2 =
1485 reinterpret_cast<const math::vec3*>(src_vertices_ptr + (src_indices_ptr[1] * vertex_stride));
1486 const auto* v3 =
1487 reinterpret_cast<const math::vec3*>(src_vertices_ptr + (src_indices_ptr[2] * vertex_stride));
1488
1489 // edge 1
1490 edge.vertex1 = v1;
1491 edge.vertex2 = v2;
1492 edge_tree[edge] = i;
1493
1494 // edge 2
1495 edge.vertex1 = v2;
1496 edge.vertex2 = v3;
1497 edge_tree[edge] = i;
1498
1499 // edge 3
1500 edge.vertex1 = v3;
1501 edge.vertex2 = v1;
1502 edge_tree[edge] = i;
1503
1504 } // Next Face
1505
1506 // Size the output array.
1507 adjacency.resize(face_count_ * 3, 0xFFFFFFFF);
1508
1509 // Now, find any adjacent edges for each triangle edge
1510 src_indices_ptr = system_ib_;
1511 for(uint32_t i = 0; i < face_count_; ++i, src_indices_ptr += 3)
1512 {
1513 adjacent_edge_key edge;
1514
1515 // Retrieve positions of each referenced vertex.
1516 const math::vec3* v1 =
1517 reinterpret_cast<const math::vec3*>(src_vertices_ptr + (src_indices_ptr[0] * vertex_stride));
1518 const math::vec3* v2 =
1519 reinterpret_cast<const math::vec3*>(src_vertices_ptr + (src_indices_ptr[1] * vertex_stride));
1520 const math::vec3* v3 =
1521 reinterpret_cast<const math::vec3*>(src_vertices_ptr + (src_indices_ptr[2] * vertex_stride));
1522
1523 // Note: Notice below that the order of the edge vertices
1524 // is swapped. This is because we want to find the
1525 // matching ADJACENT edge, rather than simply finding
1526 // the same edge that we're currently processing.
1527
1528 // edge 1
1529 edge.vertex2 = v1;
1530 edge.vertex1 = v2;
1531
1532 // Find the matching adjacent edge
1533 it_edge = edge_tree.find(edge);
1534 if(it_edge != edge_tree.end())
1535 {
1536 adjacency[(i * 3)] = it_edge->second;
1537 }
1538
1539 // edge 2
1540 edge.vertex2 = v2;
1541 edge.vertex1 = v3;
1542
1543 // Find the matching adjacent edge
1544 it_edge = edge_tree.find(edge);
1545 if(it_edge != edge_tree.end())
1546 {
1547 adjacency[(i * 3) + 1] = it_edge->second;
1548 }
1549
1550 // edge 3
1551 edge.vertex2 = v3;
1552 edge.vertex1 = v1;
1553
1554 // Find the matching adjacent edge
1555 it_edge = edge_tree.find(edge);
1556 if(it_edge != edge_tree.end())
1557 {
1558 adjacency[(i * 3) + 2] = it_edge->second;
1559 }
1560
1561 } // Next Face
1562
1563 } // End if prepared
1564
1565 // Success!
1566 return true;
1567}
1568
1569auto mesh::get_face_count() const -> uint32_t
1570{
1572 {
1573 return face_count_;
1574 }
1576 {
1577 return static_cast<uint32_t>(preparation_data_.triangle_data.size());
1578 }
1579
1580 return 0;
1581}
1582
1583auto mesh::get_vertex_count() const -> uint32_t
1584{
1586 {
1587 return vertex_count_;
1588 }
1590 {
1592 }
1593
1594 return 0;
1595}
1596
1597auto mesh::get_system_vb() -> uint8_t*
1598{
1599 return system_vb_;
1600}
1601
1602auto mesh::get_system_ib() -> uint32_t*
1603{
1604 return system_ib_;
1605}
1606
1607auto mesh::get_vertex_format() const -> const gfx::vertex_layout&
1608{
1609 return vertex_format_;
1610}
1611
1612auto mesh::get_hardware_vb() const -> std::shared_ptr<gfx::vertex_buffer>
1613{
1614 return std::static_pointer_cast<gfx::vertex_buffer>(hardware_vb_);
1615}
1616
1617auto mesh::get_hardware_ib(uint32_t lod_index) const -> std::shared_ptr<gfx::index_buffer>
1618{
1619 if(lod_index == 0 || lods_.empty())
1620 {
1621 return std::static_pointer_cast<gfx::index_buffer>(hardware_ib_);
1622 }
1623
1624 if(lod_index <= lods_.size())
1625 {
1626 return std::static_pointer_cast<gfx::index_buffer>(lods_[lod_index - 1].hardware_ib_);
1627 }
1628
1629 return std::static_pointer_cast<gfx::index_buffer>(hardware_ib_);
1630}
1631
1633{
1634 return skin_bind_data_;
1635}
1636
1638{
1639 return bone_palettes_;
1640}
1641
1642auto mesh::get_armature() const -> const std::unique_ptr<mesh::armature_node>&
1643{
1644 return root_;
1645}
1646
1647namespace
1648{
1649void accumulate_submesh_node_transforms(const std::unique_ptr<mesh::armature_node>& node,
1650 const math::transform& parent_global,
1651 std::vector<math::transform>& out)
1652{
1653 if(!node)
1654 {
1655 return;
1656 }
1657 const math::transform global = parent_global * node->local_transform;
1658 for(auto submesh_index : node->submeshes)
1659 {
1660 if(submesh_index < out.size())
1661 {
1662 out[submesh_index] = global;
1663 }
1664 }
1665 for(const auto& child : node->children)
1666 {
1667 accumulate_submesh_node_transforms(child, global, out);
1668 }
1669}
1670} // namespace
1671
1672auto mesh::get_submesh_node_transforms(uint32_t lod_index) const -> std::vector<math::transform>
1673{
1674 const size_t submesh_count = get_submeshes_count(lod_index);
1675 std::vector<math::transform> transforms(submesh_count);
1676 accumulate_submesh_node_transforms(root_, math::transform{}, transforms);
1677 return transforms;
1678}
1679
1681{
1682 auto bounds = math::bbox::mul(get_bounds(), world);
1683 math::vec3 cen = bounds.get_center();
1684 math::vec3 ext = bounds.get_extents();
1685
1686 const auto view_proj = cam.get_view_projection();
1687 const auto& viewport_size = cam.get_viewport_size();
1688 const auto& viewport_pos = cam.get_viewport_pos();
1689 const float near_plane_epsilon = 0.001f;
1690
1691 std::array<math::vec3, 8> corners = {{
1692 math::vec3(cen.x - ext.x, cen.y - ext.y, cen.z - ext.z),
1693 math::vec3(cen.x + ext.x, cen.y - ext.y, cen.z - ext.z),
1694 math::vec3(cen.x - ext.x, cen.y - ext.y, cen.z + ext.z),
1695 math::vec3(cen.x + ext.x, cen.y - ext.y, cen.z + ext.z),
1696 math::vec3(cen.x - ext.x, cen.y + ext.y, cen.z - ext.z),
1697 math::vec3(cen.x + ext.x, cen.y + ext.y, cen.z - ext.z),
1698 math::vec3(cen.x - ext.x, cen.y + ext.y, cen.z + ext.z),
1699 math::vec3(cen.x + ext.x, cen.y + ext.y, cen.z + ext.z),
1700 }};
1701
1702 // Bounding box edges (pairs of corner indices)
1703 constexpr std::array<std::pair<int, int>, 12> edges = {{
1704 {0, 1}, {2, 3}, {4, 5}, {6, 7}, // X-aligned edges
1705 {0, 2}, {1, 3}, {4, 6}, {5, 7}, // Z-aligned edges
1706 {0, 4}, {1, 5}, {2, 6}, {3, 7} // Y-aligned edges
1707 }};
1708
1709 math::vec2 min = math::vec2(std::numeric_limits<float>::max());
1710 math::vec2 max = math::vec2(std::numeric_limits<float>::lowest());
1711 bool has_valid_point = false;
1712
1713 // Project corners and clip edges
1714 std::array<math::vec4, 8> clip_coords;
1715 std::array<bool, 8> is_visible;
1716
1717 for(int i = 0; i < 8; ++i)
1718 {
1719 clip_coords[i] = view_proj * math::vec4{corners[i].x, corners[i].y, corners[i].z, 1.0f};
1720 is_visible[i] = clip_coords[i].w > near_plane_epsilon;
1721
1722 if(is_visible[i])
1723 {
1724 const float recip_w = 1.0f / clip_coords[i].w;
1725 const float ndc_x = clip_coords[i].x * recip_w;
1726 const float ndc_y = clip_coords[i].y * recip_w;
1727
1728 math::vec2 screen_point;
1729 screen_point.x = ((ndc_x * 0.5f) + 0.5f) * float(viewport_size.width) + float(viewport_pos.x);
1730 screen_point.y = ((ndc_y * -0.5f) + 0.5f) * float(viewport_size.height) + float(viewport_pos.y);
1731
1732 min = math::min(min, screen_point);
1733 max = math::max(max, screen_point);
1734 has_valid_point = true;
1735 }
1736 }
1737
1738 // Clip edges that cross the near plane
1739 for(const auto& edge : edges)
1740 {
1741 const int idx0 = edge.first;
1742 const int idx1 = edge.second;
1743
1744 const bool v0_visible = is_visible[idx0];
1745 const bool v1_visible = is_visible[idx1];
1746
1747 if(v0_visible == v1_visible)
1748 {
1749 continue;
1750 }
1751
1752 const math::vec4& clip0 = clip_coords[idx0];
1753 const math::vec4& clip1 = clip_coords[idx1];
1754
1755 const float w0 = clip0.w;
1756 const float w1 = clip1.w;
1757
1758 const float t = (near_plane_epsilon - w0) / (w1 - w0);
1759
1760 if(t >= 0.0f && t <= 1.0f)
1761 {
1762 const math::vec4 clipped_clip = clip0 + t * (clip1 - clip0);
1763
1764 const float recip_w = 1.0f / clipped_clip.w;
1765 const float ndc_x = clipped_clip.x * recip_w;
1766 const float ndc_y = clipped_clip.y * recip_w;
1767
1768 math::vec2 screen_point;
1769 screen_point.x = ((ndc_x * 0.5f) + 0.5f) * float(viewport_size.width) + float(viewport_pos.x);
1770 screen_point.y = ((ndc_y * -0.5f) + 0.5f) * float(viewport_size.height) + float(viewport_pos.y);
1771
1772 min = math::min(min, screen_point);
1773 max = math::max(max, screen_point);
1774 has_valid_point = true;
1775 }
1776 }
1777
1778 if(!has_valid_point)
1779 {
1780 min = math::vec2(float(viewport_pos.x), float(viewport_pos.y));
1781 max = math::vec2(float(viewport_pos.x + viewport_size.width), float(viewport_pos.y + viewport_size.height));
1782 }
1783
1784 return irect32_t(irect32_t::value_type(min.x),
1785 irect32_t::value_type(min.y),
1786 irect32_t::value_type(max.x),
1787 irect32_t::value_type(max.y));
1788}
1789
1790auto mesh::calculate_screen_rect(const math::transform& world, const camera& cam) const -> irect32_t
1791{
1792 auto bounds = math::bbox::mul(get_bounds(), world);
1793 math::vec3 cen = bounds.get_center();
1794 math::vec3 ext = bounds.get_extents();
1795
1796 const auto view_proj = cam.get_view_projection();
1797 const auto& viewport_size = cam.get_viewport_size();
1798 const auto& viewport_pos = cam.get_viewport_pos();
1799
1800 std::array<math::vec3, 8> corners = {{
1801 math::vec3(cen.x - ext.x, cen.y - ext.y, cen.z - ext.z),
1802 math::vec3(cen.x + ext.x, cen.y - ext.y, cen.z - ext.z),
1803 math::vec3(cen.x - ext.x, cen.y - ext.y, cen.z + ext.z),
1804 math::vec3(cen.x + ext.x, cen.y - ext.y, cen.z + ext.z),
1805 math::vec3(cen.x - ext.x, cen.y + ext.y, cen.z - ext.z),
1806 math::vec3(cen.x + ext.x, cen.y + ext.y, cen.z - ext.z),
1807 math::vec3(cen.x - ext.x, cen.y + ext.y, cen.z + ext.z),
1808 math::vec3(cen.x + ext.x, cen.y + ext.y, cen.z + ext.z),
1809 }};
1810
1811 math::vec2 min = math::vec2(std::numeric_limits<float>::max());
1812 math::vec2 max = math::vec2(std::numeric_limits<float>::lowest());
1813 int valid_count = 0;
1814 int behind_count = 0;
1815
1816 for(const auto& corner : corners)
1817 {
1818 math::vec4 clip = view_proj * math::vec4{corner.x, corner.y, corner.z, 1.0f};
1819
1820 // Check if point is behind or very close to the camera (near plane)
1821 if(clip.w <= 0.001f)
1822 {
1823 behind_count++;
1824 continue;
1825 }
1826
1827 // Project to normalized device coordinates
1828 const float recip_w = 1.0f / clip.w;
1829 const float ndc_x = clip.x * recip_w;
1830 const float ndc_y = clip.y * recip_w;
1831
1832 // Transform to screen space
1833 math::vec2 screen_point;
1834 screen_point.x = ((ndc_x * 0.5f) + 0.5f) * float(viewport_size.width) + float(viewport_pos.x);
1835 screen_point.y = ((ndc_y * -0.5f) + 0.5f) * float(viewport_size.height) + float(viewport_pos.y);
1836
1837 min = math::min(min, screen_point);
1838 max = math::max(max, screen_point);
1839 valid_count++;
1840 }
1841
1842 // If some points are behind camera, the bounds cross the near plane
1843 // This means the object extends beyond the viewport edges - clamp to full screen
1844 if(behind_count > 0 && valid_count > 0)
1845 {
1846 min.x = float(viewport_pos.x);
1847 min.y = float(viewport_pos.y);
1848 max.x = float(viewport_pos.x + viewport_size.width);
1849 max.y = float(viewport_pos.y + viewport_size.height);
1850 }
1851 else if(valid_count == 0)
1852 {
1853 // All points behind camera - object encompasses the entire screen
1854 min = math::vec2(float(viewport_pos.x), float(viewport_pos.y));
1855 max = math::vec2(float(viewport_pos.x + viewport_size.width), float(viewport_pos.y + viewport_size.height));
1856 }
1857
1858 return irect32_t(irect32_t::value_type(min.x),
1859 irect32_t::value_type(min.y),
1860 irect32_t::value_type(max.x),
1861 irect32_t::value_type(max.y));
1862}
1863
1864auto mesh::get_submeshes(uint32_t lod_index) const -> const submesh_array_t&
1865{
1866 if(lod_index == 0)
1867 {
1868 return mesh_submeshes_;
1869 }
1870
1871 if(lod_index > 0 && lod_index <= lods_.size())
1872 {
1873 return lods_[lod_index - 1].submeshes_;
1874 }
1875
1876 // Invalid LOD index, return base
1877 return mesh_submeshes_;
1878}
1879
1880auto mesh::get_submeshes_count(uint32_t lod_index) const -> size_t
1881{
1882 if(lod_index == 0)
1883 {
1884 return mesh_submeshes_.size();
1885 }
1886
1887 if(lod_index > 0 && lod_index <= lods_.size())
1888 {
1889 return lods_[lod_index - 1].submeshes_.size();
1890 }
1891
1892 // Invalid LOD index, return base
1893 return mesh_submeshes_.size();
1894}
1895
1896auto mesh::get_submesh(uint32_t submesh_index, uint32_t lod_index) const -> const submesh*
1897{
1898 const auto& submeshes = get_submeshes(lod_index);
1899 if(submesh_index < submeshes.size())
1900 {
1901 return submeshes[submesh_index];
1902 }
1903 return nullptr;
1904}
1905
1906auto mesh::get_submesh_index(const submesh* s, uint32_t lod_index) const -> int
1907{
1908 const auto& submeshes = get_submeshes(lod_index);
1909 int index = -1;
1910 for(const auto& submesh : submeshes)
1911 {
1912 index++;
1913 if(submesh == s)
1914 {
1915 return index;
1916 }
1917 }
1918
1919 return -1;
1920}
1921
1922auto mesh::find_submesh_index_by_stable_id(uint32_t stable_id, uint32_t lod_index) const -> int
1923{
1924 if(stable_id == 0)
1925 {
1926 return -1;
1927 }
1928
1929 const auto& submeshes = get_submeshes(lod_index);
1930 for(size_t i = 0; i < submeshes.size(); ++i)
1931 {
1932 if(submeshes[i] != nullptr && submeshes[i]->stable_id == stable_id)
1933 {
1934 return static_cast<int>(i);
1935 }
1936 }
1937
1938 return -1;
1939}
1940
1941
1942auto mesh::get_lod_count() const -> uint32_t
1943{
1944 return lod_count_;
1945}
1946
1947auto mesh::get_lod_submeshes(uint32_t lod_index) const -> const submesh_array_t*
1948{
1949 if(lod_index == 0)
1950 {
1951 return &mesh_submeshes_;
1952 }
1953
1954 if(lod_index > 0 && lod_index <= lods_.size())
1955 {
1956 return &lods_[lod_index - 1].submeshes_;
1957 }
1958
1959 return nullptr;
1960}
1961
1962auto mesh::get_lod_face_count(uint32_t lod_index) const -> uint32_t
1963{
1964 if(lod_index == 0)
1965 {
1966 return face_count_;
1967 }
1968
1969 if(lod_index > 0 && lod_index <= lods_.size())
1970 {
1971 return lods_[lod_index - 1].face_count_;
1972 }
1973
1974 return 0;
1975}
1976
1977auto mesh::get_lod_index_data(uint32_t lod_index, std::vector<uint32_t>& out_indices, float& out_error) const -> bool
1978{
1979 if(lod_index == 0)
1980 {
1981 // Base LOD - return original index buffer
1982 if(!system_ib_ || face_count_ == 0)
1983 {
1984 return false;
1985 }
1986 out_indices.resize(face_count_ * 3);
1987 std::memcpy(out_indices.data(), system_ib_, face_count_ * 3 * sizeof(uint32_t));
1988 out_error = 0.0f;
1989 return true;
1990 }
1991
1992 if(lod_index > 0 && lod_index <= lods_.size())
1993 {
1994 const auto& lod = lods_[lod_index - 1];
1995 if(!lod.system_ib_ || lod.face_count_ == 0)
1996 {
1997 return false;
1998 }
1999 out_indices.resize(lod.face_count_ * 3);
2000 std::memcpy(out_indices.data(), lod.system_ib_, lod.face_count_ * 3 * sizeof(uint32_t));
2001 out_error = lod.simplification_error_;
2002 return true;
2003 }
2004
2005 return false;
2006}
2007
2008auto mesh::get_max_lod_count() -> uint32_t
2009{
2010 return 1 + get_max_generated_lod_count();
2011}
2012
2014{
2015 return 5;
2016}
2017
2018auto mesh::generate_default_lod_configs(const load_data& data, float target_error) -> std::vector<std::pair<size_t, float>>
2019{
2020 std::vector<std::pair<size_t, float>> lod_configs;
2021
2022 auto max_lod_count = get_max_generated_lod_count();
2023 for(uint32_t i = 1; i <= max_lod_count; ++i)
2024 {
2025 // Only generate LODs for meshes with enough triangles
2026 size_t base_tri_count = data.triangle_count;
2027 lod_configs.push_back({base_tri_count / (1 << i), target_error * i});
2028 }
2029
2030 return lod_configs;
2031}
2032
2034{
2035 if(!data.skin_data.has_bones())
2036 {
2037 return true; // No skinning needed
2038 }
2039
2040 // Build vertex table with bone influences
2042 data.skin_data.build_vertex_table(data.vertex_count, {}, vertex_table);
2043
2044 uint32_t palette_size = gfx::get_max_blend_transforms();
2045 std::vector<bone_palette> bone_palettes;
2046 bone_palettes.reserve(data.submeshes.size());
2047
2048 // Iterate over each submesh to generate palettes and duplicate vertices
2049 for(size_t palette_id = 0; palette_id < data.submeshes.size(); ++palette_id)
2050 {
2051 auto& submesh = data.submeshes[palette_id];
2052
2053 std::vector<bool> used_bones(gfx::get_max_blend_transforms(), false);
2054 std::vector<uint32_t> faces;
2055 faces.reserve(submesh.face_count);
2056
2057 // Collect all unique bone indices influencing this submesh
2058 for(uint32_t i = submesh.face_start; i < submesh.face_start + submesh.face_count; ++i)
2059 {
2060 faces.push_back(i);
2061 for(uint32_t vertex_index : data.triangle_data[i].indices)
2062 {
2063 const auto& vdata = vertex_table[vertex_index];
2064 for(const auto& influence : vdata.influences)
2065 {
2066 used_bones[static_cast<uint32_t>(influence)] = true;
2067 }
2068 }
2069 }
2070
2071 // Create bone palette for this submesh
2072 bone_palette new_palette(palette_size);
2074 new_palette.assign_bones(used_bones, faces);
2075 bone_palettes.push_back(new_palette);
2076
2077 // Assign palette ID to each vertex and duplicate shared vertices
2078 auto face_start = submesh.face_start;
2079 auto face_end = submesh.face_start + submesh.face_count;
2080 for(uint32_t i = face_start; i < face_end; ++i)
2081 {
2082 for(uint32_t k = 0; k < 3; ++k)
2083 {
2084 uint32_t vertex_index = data.triangle_data[i].indices[k];
2085 auto& vdata = vertex_table[vertex_index];
2086
2087 if(vdata.palette == -1)
2088 {
2089 vdata.palette = static_cast<int32_t>(palette_id);
2090 if(submesh.vertex_start == -1 || vertex_index < submesh.vertex_start)
2091 {
2092 submesh.vertex_start = vertex_index;
2093 }
2094 if(vertex_index >= submesh.vertex_start + submesh.vertex_count)
2095 {
2096 submesh.vertex_count = (vertex_index - submesh.vertex_start) + 1;
2097 }
2098 }
2099 else if(vdata.palette != static_cast<int32_t>(palette_id))
2100 {
2101 // Vertex is shared between submeshes, need to duplicate it
2102 uint32_t new_index = static_cast<uint32_t>(vertex_table.size());
2103
2104 skin_bind_data::vertex_data new_vertex(vdata);
2105 new_vertex.original_vertex = vertex_index;
2106 new_vertex.palette = static_cast<int32_t>(palette_id);
2107 vertex_table.push_back(new_vertex);
2108
2109 if(submesh.vertex_start == -1 || new_index < submesh.vertex_start)
2110 {
2111 submesh.vertex_start = new_index;
2112 }
2113 if(new_index >= submesh.vertex_start + submesh.vertex_count)
2114 {
2115 submesh.vertex_count = (new_index - submesh.vertex_start) + 1;
2116 }
2117
2118 // Update triangle index to point to new vertex
2119 data.triangle_data[i].indices[k] = new_index;
2120 }
2121 }
2122 }
2123 }
2124
2125 // Adjust vertex format to include blend weights and indices
2126 gfx::vertex_layout new_format(data.vertex_format);
2127 gfx::vertex_layout original_format = data.vertex_format;
2128 bool has_weights = new_format.has(gfx::attribute::Weight);
2129 bool has_indices = new_format.has(gfx::attribute::Indices);
2130
2131 if(!has_weights || !has_indices)
2132 {
2133 new_format.m_hash = 0;
2134 if(!has_weights)
2135 {
2136 new_format.add(gfx::attribute::Weight, 4, gfx::attribute_type::Float);
2137 }
2138 if(!has_indices)
2139 {
2140 new_format.add(gfx::attribute::Indices, 4, gfx::attribute_type::Float, false, true);
2141 }
2142 new_format.end();
2143 data.vertex_format = new_format;
2144 }
2145
2146 uint16_t vertex_stride = data.vertex_format.getStride();
2147 uint32_t original_vertex_count = data.vertex_count;
2148
2149 // Resize vertex buffer to accommodate duplicated vertices
2150 if(data.vertex_format.m_hash != original_format.m_hash)
2151 {
2152 // Format changed, need to convert
2153 byte_array_t original_buffer(data.vertex_data);
2154 data.vertex_data.clear();
2155 data.vertex_data.resize(vertex_table.size() * vertex_stride);
2156
2157 gfx::vertex_convert(data.vertex_format,
2158 data.vertex_data.data(),
2159 original_format,
2160 original_buffer.data(),
2161 original_vertex_count);
2162 }
2163 else
2164 {
2165 // No conversion, just resize
2166 data.vertex_data.resize(vertex_table.size() * vertex_stride);
2167 }
2168
2169 // Update vertex data with bone indices and weights, and duplicate vertices
2170 uint8_t* src_vertices_ptr = data.vertex_data.data();
2171 for(size_t i = 0; i < vertex_table.size(); ++i)
2172 {
2173 auto& vdata = vertex_table[i];
2174 int32_t palette_id = vdata.palette;
2175
2176 if(palette_id < 0)
2177 {
2178 continue;
2179 }
2180
2181 const auto& palette = bone_palettes[static_cast<size_t>(palette_id)];
2182
2183 // If this is a duplicated vertex, copy data from original
2184 if(i >= original_vertex_count)
2185 {
2186 std::memcpy(src_vertices_ptr + (i * vertex_stride),
2187 src_vertices_ptr + (vdata.original_vertex * vertex_stride),
2188 vertex_stride);
2189 }
2190
2191 uint32_t max_bones = std::min<uint32_t>(4, uint32_t(vdata.influences.size()));
2192 if(max_bones > 0)
2193 {
2194 math::vec4 blend_weights(0.0f, 0.0f, 0.0f, 0.0f);
2195 math::vec4 blend_indices(0.0f, 0.0f, 0.0f, 0.0f);
2196
2197 for(uint32_t j = 0; j < max_bones; ++j)
2198 {
2199 uint32_t palette_bone_index =
2200 palette.translate_bone_to_palette(static_cast<uint32_t>(vdata.influences[j]));
2201
2202 blend_indices[static_cast<math::vec4::length_type>(j)] = static_cast<float>(palette_bone_index);
2203 blend_weights[static_cast<math::vec4::length_type>(j)] = vdata.weights[j];
2204 }
2205
2206 gfx::vertex_pack(math::value_ptr(blend_weights),
2207 false,
2208 gfx::attribute::Weight,
2209 data.vertex_format,
2210 src_vertices_ptr,
2211 uint32_t(i));
2212
2213 gfx::vertex_pack(math::value_ptr(blend_indices),
2214 false,
2215 gfx::attribute::Indices,
2216 data.vertex_format,
2217 src_vertices_ptr,
2218 uint32_t(i));
2219 }
2220 }
2221
2222 // Update vertex count
2223 data.vertex_count = static_cast<uint32_t>(vertex_table.size());
2224
2225 // Mark topology as baked and store palette bones so runtime can restore palettes without running bind_skin.
2226 data.skin_is_prepared = true;
2227 data.bone_palette_bones.clear();
2228 data.bone_palette_bones.reserve(bone_palettes.size());
2229 for(const auto& palette : bone_palettes)
2230 {
2231 data.bone_palette_bones.push_back(palette.get_bones());
2232 }
2233 return true;
2234}
2235
2236auto mesh::generate_lods_for_load_data(load_data& data, const std::vector<std::pair<size_t, float>>& lod_configs) -> bool
2237{
2238 if(data.vertex_data.empty() || data.triangle_count == 0 || data.vertex_count == 0)
2239 {
2240 APPLOG_ERROR("Cannot generate LODs for empty mesh data\n");
2241 return false;
2242 }
2243
2244 if(!data.vertex_format.has(gfx::attribute::Position))
2245 {
2246 APPLOG_ERROR("Mesh must have position data to generate LODs\n");
2247 return false;
2248 }
2249
2250 if(data.submeshes.empty())
2251 {
2252 APPLOG_ERROR("Cannot generate LODs for mesh with no submeshes\n");
2253 return false;
2254 }
2255
2256 // Get position offset and stride for meshoptimizer
2257 uint16_t position_offset = data.vertex_format.getOffset(gfx::attribute::Position);
2258 uint16_t vertex_stride = data.vertex_format.getStride();
2259 const uint8_t* vertex_data_ptr = data.vertex_data.data();
2260
2261 // Point directly to position data in vertex buffer (cast to float*)
2262 const float* vertex_positions = reinterpret_cast<const float*>(vertex_data_ptr + position_offset);
2263
2264 // Build index buffer from triangle data
2265 std::vector<uint32_t> base_indices(data.triangle_count * 3);
2266 for(uint32_t i = 0; i < data.triangle_count; ++i)
2267 {
2268 base_indices[i * 3 + 0] = data.triangle_data[i].indices[0];
2269 base_indices[i * 3 + 1] = data.triangle_data[i].indices[1];
2270 base_indices[i * 3 + 2] = data.triangle_data[i].indices[2];
2271 }
2272
2273 // Clear existing LODs
2274 data.lods.clear();
2275
2276 // Track previous LOD face count to detect when simplification stops making progress
2277 uint32_t previous_face_count = data.triangle_count;
2278 constexpr float MIN_FACE_COUNT_DIFFERENCE_RATIO = 0.02f; //minimum difference
2279
2280 // Prepare attribute packing data (shared across all submeshes and LODs)
2281 bool use_attribute_simplify = false;
2282 std::vector<float> packed_attributes;
2283 const float* attribute_ptr = nullptr;
2284 size_t attribute_stride = 0;
2285 std::vector<float> attribute_weights;
2286 uint32_t attribute_component_count = 0;
2287 uint32_t total_components = 0;
2288
2289 // Check for available attributes and pack them into a single interleaved buffer
2290 bool has_normal = data.vertex_format.has(gfx::attribute::Normal);
2291 bool has_texcoord = data.vertex_format.has(gfx::attribute::TexCoord0);
2292 bool has_tangent = data.vertex_format.has(gfx::attribute::Tangent);
2293
2294 if(has_normal || has_texcoord || has_tangent)
2295 {
2296 // Calculate total components needed: normal(3) + uv(2) + tangent(3) + bitangent(3) + weight(4) + indices(4) = 19 max
2297 if(has_normal) total_components += 3;
2298 if(has_texcoord) total_components += 2;
2299 if(has_tangent) total_components += 3;
2300
2301 packed_attributes.resize(data.vertex_count * total_components);
2302 attribute_weights.resize(total_components);
2303
2304 // Setup attribute weights
2305 uint32_t weight_offset = 0;
2306 if(has_normal)
2307 {
2308 attribute_weights[weight_offset + 0] = 1.5f;
2309 attribute_weights[weight_offset + 1] = 1.5f;
2310 attribute_weights[weight_offset + 2] = 1.5f;
2311 weight_offset += 3;
2312 }
2313 if(has_texcoord)
2314 {
2315 attribute_weights[weight_offset + 0] = 1.0f;
2316 attribute_weights[weight_offset + 1] = 1.0f;
2317 weight_offset += 2;
2318 }
2319 if(has_tangent)
2320 {
2321 attribute_weights[weight_offset + 0] = 0.75f;
2322 attribute_weights[weight_offset + 1] = 0.75f;
2323 attribute_weights[weight_offset + 2] = 0.75f;
2324 weight_offset += 3;
2325 }
2326
2327
2328 // Extract and pack all attributes in a single pass over vertices
2329 for(uint32_t i = 0; i < data.vertex_count; ++i)
2330 {
2331 float* dst = &packed_attributes[i * total_components];
2332 uint32_t component_offset = 0;
2333
2334 if(has_normal)
2335 {
2336 float attr[4];
2337 gfx::vertex_unpack(attr, gfx::attribute::Normal, data.vertex_format, vertex_data_ptr, i);
2338 dst[component_offset + 0] = attr[0];
2339 dst[component_offset + 1] = attr[1];
2340 dst[component_offset + 2] = attr[2];
2341 component_offset += 3;
2342 }
2343
2344 if(has_texcoord)
2345 {
2346 float attr[4];
2347 gfx::vertex_unpack(attr, gfx::attribute::TexCoord0, data.vertex_format, vertex_data_ptr, i);
2348 dst[component_offset + 0] = attr[0];
2349 dst[component_offset + 1] = attr[1];
2350 component_offset += 2;
2351 }
2352
2353 if(has_tangent)
2354 {
2355 float attr[4];
2356 gfx::vertex_unpack(attr, gfx::attribute::Tangent, data.vertex_format, vertex_data_ptr, i);
2357 dst[component_offset + 0] = attr[0];
2358 dst[component_offset + 1] = attr[1];
2359 dst[component_offset + 2] = attr[2];
2360 component_offset += 3;
2361 }
2362
2363
2364 }
2365
2366 if(total_components > 0)
2367 {
2368 attribute_ptr = packed_attributes.data();
2369 attribute_stride = total_components * sizeof(float);
2370 attribute_component_count = total_components;
2371 use_attribute_simplify = true;
2372 }
2373 }
2374
2375
2376 // Computes a tight bounding box for a simplified index set so per-LOD submeshes carry
2377 // accurate bounds instead of inheriting the (potentially much larger) base LOD box.
2378 auto compute_bbox_from_indices = [&](const uint32_t* indices, size_t index_count) -> math::bbox
2379 {
2380 math::bbox box{};
2381 for(size_t i = 0; i < index_count; ++i)
2382 {
2383 const auto* pos =
2384 reinterpret_cast<const float*>(vertex_data_ptr + indices[i] * vertex_stride + position_offset);
2385 box.add_point(math::vec3(pos[0], pos[1], pos[2]));
2386 }
2387 return box;
2388 };
2389
2390 // Generate each LOD level
2391 for(const auto& config : lod_configs)
2392 {
2393 size_t target_tri_count = config.first;
2394 float target_error = config.second;
2395
2396 // Clamp target triangle count
2397 target_tri_count = std::min(target_tri_count, static_cast<size_t>(data.triangle_count));
2398 if(target_tri_count < 1)
2399 {
2400 continue;
2401 }
2402
2403 // Create LOD load data for this level
2405 lod_data.face_count = 0;
2406 lod_data.simplification_error = 0.0f;
2407
2408 uint32_t current_face_start = 0;
2409
2410
2411 unsigned int options = meshopt_SimplifyLockBorder;
2412
2413 if(data.submeshes.size() > 1)
2414 {
2415 options |= meshopt_SimplifySparse;
2416 }
2417
2418
2419 // Process each submesh independently
2420 for(const auto& base_submesh : data.submeshes)
2421 {
2422 // Skip empty submeshes
2423 if(base_submesh.face_count == 0)
2424 {
2425 submesh lod_submesh = base_submesh;
2426 lod_submesh.face_start = static_cast<int32_t>(current_face_start);
2427 lod_data.submeshes.push_back(lod_submesh);
2428 continue;
2429 }
2430
2431 // Calculate target triangle count for this submesh based on ratio
2432 float submesh_ratio = static_cast<float>(base_submesh.face_count) / static_cast<float>(data.triangle_count);
2433 size_t submesh_target_tri_count = static_cast<size_t>(std::max(1.0f, static_cast<float>(target_tri_count) * submesh_ratio));
2434 submesh_target_tri_count = std::min(submesh_target_tri_count, static_cast<size_t>(base_submesh.face_count));
2435
2436 // Extract indices for this submesh
2437 std::vector<uint32_t> submesh_indices(base_submesh.face_count * 3);
2438 for(uint32_t i = 0; i < base_submesh.face_count; ++i)
2439 {
2440 uint32_t tri_idx = base_submesh.face_start + i;
2441 submesh_indices[i * 3 + 0] = base_indices[tri_idx * 3 + 0];
2442 submesh_indices[i * 3 + 1] = base_indices[tri_idx * 3 + 1];
2443 submesh_indices[i * 3 + 2] = base_indices[tri_idx * 3 + 2];
2444 }
2445
2446 // Convert triangle count to index count
2447 size_t submesh_target_index_count = submesh_target_tri_count * 3;
2448
2449 // Allocate destination index buffer
2450 std::vector<uint32_t> submesh_lod_indices(base_submesh.face_count * 3);
2451
2452 // Simplify this submesh
2453 float submesh_result_error = 0.0f;
2454 size_t submesh_lod_index_count = 0;
2455
2456
2457 // Use attribute-preserving simplification if available, otherwise fall back to position-only
2458 if(use_attribute_simplify)
2459 {
2460 submesh_lod_index_count = meshopt_simplifyWithAttributes(
2461 submesh_lod_indices.data(),
2462 submesh_indices.data(),
2463 base_submesh.face_count * 3,
2464 vertex_positions,
2465 data.vertex_count,
2466 vertex_stride,
2467 attribute_ptr,
2468 attribute_stride,
2469 attribute_weights.data(),
2470 attribute_component_count,
2471 nullptr,
2472 submesh_target_index_count,
2473 target_error,
2474 options,
2475 &submesh_result_error);
2476 }
2477 else
2478 {
2479 submesh_lod_index_count = meshopt_simplify(
2480 submesh_lod_indices.data(),
2481 submesh_indices.data(),
2482 base_submesh.face_count * 3,
2483 vertex_positions,
2484 data.vertex_count,
2485 vertex_stride,
2486 submesh_target_index_count,
2487 target_error,
2488 options,
2489 &submesh_result_error);
2490 }
2491
2492 // Convert index count back to triangle count
2493 size_t submesh_lod_tri_count = submesh_lod_index_count / 3;
2494 if(submesh_lod_tri_count == 0)
2495 {
2496 APPLOG_WARNING("Failed to generate LOD for submesh with target {} triangles (error: {})\n", submesh_target_tri_count, target_error);
2497 // Use original submesh as fallback
2498 submesh lod_submesh = base_submesh;
2499 lod_submesh.face_start = static_cast<int32_t>(current_face_start);
2500 lod_data.submeshes.push_back(lod_submesh);
2501
2502 // Append original indices
2503 for(uint32_t i = 0; i < base_submesh.face_count * 3; ++i)
2504 {
2505 lod_data.index_data.push_back(submesh_indices[i]);
2506 }
2507 current_face_start += base_submesh.face_count;
2508 lod_data.face_count += base_submesh.face_count;
2509 continue;
2510 }
2511
2512 // Create submesh descriptor for this LOD
2513 submesh lod_submesh = base_submesh;
2514 lod_submesh.face_count = static_cast<uint32_t>(submesh_lod_tri_count);
2515 lod_submesh.face_start = static_cast<int32_t>(current_face_start);
2516 const math::bbox lod_bbox = compute_bbox_from_indices(submesh_lod_indices.data(), submesh_lod_index_count);
2517 if(lod_bbox.is_populated())
2518 {
2519 lod_submesh.bbox = lod_bbox;
2520 }
2521 lod_data.submeshes.push_back(lod_submesh);
2522
2523 // Append simplified indices to LOD index buffer
2524 for(size_t i = 0; i < submesh_lod_index_count; ++i)
2525 {
2526 lod_data.index_data.push_back(submesh_lod_indices[i]);
2527 }
2528
2529 // Update counters
2530 current_face_start += static_cast<uint32_t>(submesh_lod_tri_count);
2531 lod_data.face_count += static_cast<uint32_t>(submesh_lod_tri_count);
2532 lod_data.simplification_error = std::max(lod_data.simplification_error, submesh_result_error);
2533 }
2534
2535 // Check if LOD generation was successful
2536 if(lod_data.face_count == 0)
2537 {
2538 APPLOG_WARNING("Failed to generate LOD with target {} triangles (error: {})\n", target_tri_count, target_error);
2539 break;
2540 }
2541
2542 if(lod_data.face_count >= data.triangle_count)
2543 {
2544 APPLOG_WARNING("Could not simplify mesh. Not enough triangles to simplify.\n");
2545 break;
2546 }
2547
2548 // Check if this LOD has significant difference from previous LOD
2549 if(previous_face_count > 0)
2550 {
2551 float face_count_difference = static_cast<float>(previous_face_count - lod_data.face_count);
2552 float difference_ratio = face_count_difference / static_cast<float>(previous_face_count);
2553
2554 if(difference_ratio < MIN_FACE_COUNT_DIFFERENCE_RATIO)
2555 {
2556 APPLOG_INFO("Stopping LOD generation: new LOD face count ({}) is not significantly different from previous ({}, {:.2f}% difference)\n",
2557 lod_data.face_count, previous_face_count, difference_ratio * 100.0f);
2558 break;
2559 }
2560 }
2561
2562 data.lods.push_back(std::move(lod_data));
2563 previous_face_count = lod_data.face_count;
2564 }
2565
2566 APPLOG_INFO("Generated {} LOD levels\n", data.lods.size());
2567 return true;
2568}
2569
2571{
2572 if(data.lods.empty())
2573 {
2574 return true; // No LODs to restore, not an error
2575 }
2576
2577 // Clear any existing LODs
2578 for(auto& lod : lods_)
2579 {
2580 for(auto* submesh : lod.submeshes_)
2581 {
2583 }
2584 lod.submeshes_.clear();
2585 checked_array_delete(lod.system_ib_);
2586 lod.hardware_ib_.reset();
2587 }
2588 lods_.clear();
2589 lod_count_ = 1;
2590
2591 // Restore each LOD level
2592 for(const auto& lod_data : data.lods)
2593 {
2594 mesh::lod_level lod;
2595 lod.face_count_ = lod_data.face_count;
2596 lod.simplification_error_ = lod_data.simplification_error;
2597
2598 // Copy index buffer
2599 if(!lod_data.index_data.empty())
2600 {
2601 lod.system_ib_ = new uint32_t[lod_data.index_data.size()];
2602 std::memcpy(lod.system_ib_, lod_data.index_data.data(), lod_data.index_data.size() * sizeof(uint32_t));
2603 }
2604
2605 // Copy submeshes and build cached indices maps
2606 for(size_t i = 0; i < lod_data.submeshes.size(); ++i)
2607 {
2608 const auto& submesh_data = lod_data.submeshes[i];
2609 auto* lod_submesh = new submesh(submesh_data);
2610 lod.submeshes_.push_back(lod_submesh);
2611
2612 // Cache submesh indices by data group
2613 if(lod_submesh->skinned)
2614 {
2615 lod.skinned_submesh_indices_[lod_submesh->data_group_id].emplace_back(i);
2616 }
2617 else
2618 {
2619 lod.non_skinned_submesh_indices_[lod_submesh->data_group_id].emplace_back(i);
2620 }
2621 }
2622
2623 // Build hardware buffer for this LOD if needed
2624 if(hardware_mesh_ && lod.system_ib_ && lod.face_count_ > 0)
2625 {
2626 auto buffer_size = static_cast<uint32_t>(lod.face_count_ * 3 * sizeof(uint32_t));
2627 const gfx::memory_view* mem = gfx::make_ref(lod.system_ib_, buffer_size);
2628 // Same compute-read flags as base LOD so any LOD can be used as a
2629 // read-only buffer inside shaders (e.g. vertex pulling for wireframe overlay).
2630 const uint16_t ib_flags = BGFX_BUFFER_INDEX32
2631 | BGFX_BUFFER_COMPUTE_READ
2632 | BGFX_BUFFER_COMPUTE_FORMAT_32X1
2633 | BGFX_BUFFER_COMPUTE_TYPE_UINT;
2634 lod.hardware_ib_ = std::make_shared<gfx::index_buffer>(mem, ib_flags);
2635 }
2636
2637 lods_.push_back(std::move(lod));
2638 lod_count_++;
2639 }
2640
2641 return true;
2642}
2643
2644auto mesh::get_skinned_submeshes_count(uint32_t lod_index) const -> size_t
2645{
2646 const auto& submeshes = get_submeshes(lod_index);
2647 size_t count = 0;
2648 for(const auto* submesh : submeshes)
2649 {
2650 if(submesh && submesh->skinned)
2651 {
2652 count++;
2653 }
2654 }
2655 return count;
2656}
2657
2658auto mesh::get_skinned_submeshes_indices(uint32_t data_group_id, uint32_t lod_index) const -> const submesh_array_indices_t&
2659{
2660 // For base LOD (lod_index == 0), use cached map for performance
2661 if(lod_index == 0 || lods_.empty())
2662 {
2663 auto it = skinned_submesh_indices_.find(data_group_id);
2664 if(it != skinned_submesh_indices_.end())
2665 {
2666 return it->second;
2667 }
2668 static const submesh_array_indices_t empty;
2669 return empty;
2670 }
2671
2672 // For LOD levels, use cached map
2673 if(lod_index > 0 && lod_index <= lods_.size())
2674 {
2675 const auto& lod = lods_[lod_index - 1];
2676 auto it = lod.skinned_submesh_indices_.find(data_group_id);
2677 if(it != lod.skinned_submesh_indices_.end())
2678 {
2679 return it->second;
2680 }
2681 }
2682
2683 static const submesh_array_indices_t empty;
2684 return empty;
2685}
2686
2687auto mesh::get_non_skinned_submeshes_count(uint32_t lod_index) const -> size_t
2688{
2689 const auto& submeshes = get_submeshes(lod_index);
2690 size_t count = 0;
2691 for(const auto* submesh : submeshes)
2692 {
2693 if(submesh && !submesh->skinned)
2694 {
2695 count++;
2696 }
2697 }
2698 return count;
2699}
2700
2701auto mesh::get_non_skinned_submeshes_indices(uint32_t data_group_id, uint32_t lod_index) const -> const submesh_array_indices_t&
2702{
2703 // For base LOD (lod_index == 0), use cached map for performance
2704 if(lod_index == 0 || lods_.empty())
2705 {
2706 auto it = non_skinned_submesh_indices_.find(data_group_id);
2707 if(it != non_skinned_submesh_indices_.end())
2708 {
2709 return it->second;
2710 }
2711 static const submesh_array_indices_t empty;
2712 return empty;
2713 }
2714
2715 // For LOD levels, use cached map
2716 if(lod_index > 0 && lod_index <= lods_.size())
2717 {
2718 const auto& lod = lods_[lod_index - 1];
2719 auto it = lod.non_skinned_submesh_indices_.find(data_group_id);
2720 if(it != lod.non_skinned_submesh_indices_.end())
2721 {
2722 return it->second;
2723 }
2724 }
2725
2726 static const submesh_array_indices_t empty;
2727 return empty;
2728}
2729
2730auto mesh::get_bounds() const -> const math::bbox&
2731{
2732 return bbox_;
2733}
2734
2736{
2737 return prepare_status_;
2738}
2739
2740auto mesh::get_data_groups_count() const -> size_t
2741{
2743 {
2744 return data_groups_.size();
2745 }
2747 {
2748 uint32_t groups_count = 0;
2749 for(const auto& sub : preparation_data_.submeshes)
2750 {
2751 groups_count = std::max(groups_count, sub.data_group_id + 1);
2752 }
2753 return groups_count;
2754 }
2755
2756 return 0;
2757}
2758
2759auto mesh::get_default_material_uids() const -> const std::vector<hpp::uuid>&
2760{
2762}
2763
2764
2766{
2767 auto& ctx = engine::context();
2768 auto& am = ctx.get_cached<asset_manager>();
2769 std::vector<asset_handle<material>> imported_materials;
2770 for(const auto& uid : default_material_uids_)
2771 {
2772 auto mat = am.get_asset<material>(uid);
2773 imported_materials.push_back(mat);
2774 }
2775 return imported_materials;
2776}
2777
2778
2779auto operator<(const mesh::adjacent_edge_key& key1, const mesh::adjacent_edge_key& key2) -> bool
2780{
2781 // Test vertex positions.
2782 if(math::epsilonNotEqual(key1.vertex1->x, key2.vertex1->x, math::epsilon<float>()))
2783 {
2784 return (key2.vertex1->x < key1.vertex1->x);
2785 }
2786 if(math::epsilonNotEqual(key1.vertex1->y, key2.vertex1->y, math::epsilon<float>()))
2787 {
2788 return (key2.vertex1->y < key1.vertex1->y);
2789 }
2790 if(math::epsilonNotEqual(key1.vertex1->z, key2.vertex1->z, math::epsilon<float>()))
2791 {
2792 return (key2.vertex1->z < key1.vertex1->z);
2793 }
2794
2795 if(math::epsilonNotEqual(key1.vertex2->x, key2.vertex2->x, math::epsilon<float>()))
2796 {
2797 return (key2.vertex2->x < key1.vertex2->x);
2798 }
2799 if(math::epsilonNotEqual(key1.vertex2->y, key2.vertex2->y, math::epsilon<float>()))
2800 {
2801 return (key2.vertex2->y < key1.vertex2->y);
2802 }
2803 if(math::epsilonNotEqual(key1.vertex2->z, key2.vertex2->z, math::epsilon<float>()))
2804 {
2805 return (key2.vertex2->z < key1.vertex2->z);
2806 }
2807
2808 // Exactly equal
2809 return false;
2810}
2811
2812auto operator<(const mesh::mesh_submesh_key& key1, const mesh::mesh_submesh_key& key2) -> bool
2813{
2814 return key1.data_group_id < key2.data_group_id;
2815}
2816
2817auto operator<(const mesh::weld_key& key1, const mesh::weld_key& key2) -> bool
2818{
2819 auto vertex_compare =
2820 [](const uint8_t* pVtx1, const uint8_t* pVtx2, const gfx::vertex_layout& layout, float tolerance) -> int
2821 {
2822 float diff{};
2823 int ndifference{};
2824
2825 for(uint16_t i = 0; i < gfx::attribute::Count; ++i)
2826 {
2827 if(!layout.has(static_cast<gfx::attribute>(i)))
2828 {
2829 continue; // Skip attributes not present in this layout.
2830 }
2831
2832 // Get the offset for this attribute in the vertex data
2833 uint16_t offset = layout.getOffset(static_cast<gfx::attribute>(i));
2834
2835 // Retrieve the vertex data pointers
2836 const uint8_t* p1 = pVtx1 + offset;
2837 const uint8_t* p2 = pVtx2 + offset;
2838
2839 // Decode the attribute information
2840 uint8_t num_components{};
2841 bgfx::AttribType::Enum type{};
2842 bool normalized{}, as_int{};
2843 layout.decode(static_cast<gfx::attribute>(i), num_components, type, normalized, as_int);
2844
2845 // Compare the attributes based on the type
2846 switch(type)
2847 {
2848 case bgfx::AttribType::Float:
2849 {
2850 for(uint8_t j = 0; j < num_components; ++j)
2851 {
2852 diff = ((float*)p1)[j] - ((float*)p2)[j];
2853 if(fabsf(diff) > tolerance)
2854 return (diff < 0) ? -1 : 1;
2855 }
2856 break;
2857 }
2858
2859 case bgfx::AttribType::Uint8:
2860 case bgfx::AttribType::Int16:
2861 {
2862 if(as_int)
2863 {
2864 ndifference = memcmp(p1, p2, num_components * (type == bgfx::AttribType::Uint8 ? 1 : 2));
2865 if(ndifference != 0)
2866 {
2867 return (ndifference < 0) ? -1 : 1;
2868 }
2869 }
2870 else
2871 {
2872 for(uint8_t j = 0; j < num_components; ++j)
2873 {
2874 float f1{}, f2{};
2875 if(type == bgfx::AttribType::Uint8)
2876 {
2877 f1 = normalized ? ((float)p1[j] / 255.0f) : (float)p1[j];
2878 f2 = normalized ? ((float)p2[j] / 255.0f) : (float)p2[j];
2879 }
2880 else // Int16
2881 {
2882 f1 = normalized ? ((float)((int16_t*)p1)[j] / 32767.0f) : (float)((int16_t*)p1)[j];
2883 f2 = normalized ? ((float)((int16_t*)p2)[j] / 32767.0f) : (float)((int16_t*)p2)[j];
2884 }
2885 diff = f1 - f2;
2886 if(fabsf(diff) > tolerance)
2887 {
2888 return (diff < 0) ? -1 : 1;
2889 }
2890 }
2891 }
2892 break;
2893 }
2894
2895 default:
2896 // Handle other types if necessary.
2897 break;
2898 }
2899 }
2900
2901 // Both vertices are equal for the purposes of this test.
2902 return 0;
2903 };
2904
2905 int ndifference = vertex_compare(key1.vertex, key2.vertex, key1.format, key1.tolerance);
2906 if(ndifference != 0)
2907 {
2908 return (ndifference < 0);
2909 }
2910
2911 // Exactly equal
2912 return false;
2913}
2914
2916{
2917 // Data group id must match.
2918 if(key1.data_group_id != key2.data_group_id)
2919 {
2920 return key1.data_group_id < key2.data_group_id;
2921 }
2922
2923 const mesh::face_influences* p1 = key1.influences;
2924 const mesh::face_influences* p2 = key2.influences;
2925
2926 // The bone count must match.
2927 if(p1->bones.size() != p2->bones.size())
2928 {
2929 return p1->bones.size() < p2->bones.size();
2930 }
2931
2932 // Compare the bone indices in each list
2933 auto it_bone1 = p1->bones.begin();
2934 auto it_bone2 = p2->bones.begin();
2935 for(; it_bone1 != p1->bones.end() && it_bone2 != p2->bones.end(); ++it_bone1, ++it_bone2)
2936 {
2937 if(it_bone1->first != it_bone2->first)
2938 {
2939 return it_bone1->first < it_bone2->first;
2940 }
2941
2942 } // Next Bone
2943
2944 // Exact match (for the purposes of combining influences)
2945 return false;
2946}
2947
2949{
2950 // Vertex normals were requested (and at least some were not yet provided?)
2951 if(force_normal_generation_ || preparation_data_.compute_normals)
2952 {
2953 // Generate the adjacency information for vertex normal computation
2954 std::vector<uint32_t> adjacency;
2955 if(!generate_adjacency(adjacency))
2956 {
2957 APPLOG_ERROR("Failed to generate adjacency buffer mesh containing {0} faces.\n",
2958 preparation_data_.triangle_count);
2959 return false;
2960
2961 } // End if failed to generate
2962 if(force_barycentric_generation_ || preparation_data_.compute_barycentric)
2963 {
2964 // Generate any vertex barycentric coords that have not been provided
2965 if(!generate_vertex_barycentrics(&adjacency.front()))
2966 {
2967 APPLOG_ERROR("Failed to generate vertex barycentric coords for mesh "
2968 "containing {0} faces.\n",
2969 preparation_data_.triangle_count);
2970 return false;
2971
2972 } // End if failed to generate
2973
2974 } // End if compute
2975
2976 // Generate any vertex normals that have not been provided
2977 if(!generate_vertex_normals(&adjacency.front()))
2978 {
2979 APPLOG_ERROR("Failed to generate vertex normals for mesh containing {0} faces.\n",
2980 preparation_data_.triangle_count);
2981 return false;
2982
2983 } // End if failed to generate
2984
2985 } // End if compute
2986
2987 // Weld vertices at this point
2988 if(weld)
2989 {
2990 if(!weld_vertices())
2991 {
2992 APPLOG_ERROR("Failed to weld vertices for mesh containing {0} faces.\n", preparation_data_.triangle_count);
2993 return false;
2994
2995 } // End if failed to weld
2996
2997 } // End if optional weld
2998
2999 // Binormals and / or tangents were requested (and at least some where not yet
3000 // provided?)
3001 if(force_tangent_generation_ || preparation_data_.compute_binormals || preparation_data_.compute_tangents)
3002 {
3003 // Requires normals
3004 if(vertex_format_.has(gfx::attribute::Normal))
3005 {
3006 // Generate any vertex tangents that have not been provided
3007 if(!generate_vertex_tangents())
3008 {
3009 APPLOG_ERROR("Failed to generate vertex tangents for mesh containing "
3010 "{0} faces.\n",
3011 preparation_data_.triangle_count);
3012 return false;
3013
3014 } // End if failed to generate
3015
3016 } // End if has normals
3017
3018 } // End if compute
3019
3020 // Success!
3021 return true;
3022}
3023
3024auto mesh::generate_vertex_normals(uint32_t* adjacency_ptr, std::vector<uint32_t>* remap_array_ptr /* = nullptr */)
3025 -> bool
3026{
3027 uint32_t start_tri, previous_tri, current_tri;
3028 math::vec3 vec_edge1, vec_edge2, vec_normal;
3029 uint32_t i, j, k, index;
3030
3031 // Get access to useful data offset information.
3032 uint16_t position_offset = vertex_format_.getOffset(gfx::attribute::Position);
3033 bool has_normals = vertex_format_.has(gfx::attribute::Normal);
3034 uint16_t vertex_stride = vertex_format_.getStride();
3035
3036 // Final format requests vertex normals?
3037 if(!has_normals)
3038 {
3039 return true;
3040 }
3041
3042 // Size the remap array accordingly and populate it with the default mapping.
3043 uint32_t original_vertex_count = preparation_data_.vertex_count;
3044 if(remap_array_ptr)
3045 {
3046 remap_array_ptr->resize(preparation_data_.vertex_count);
3047 for(i = 0; i < preparation_data_.vertex_count; ++i)
3048 {
3049 (*remap_array_ptr)[i] = i;
3050 }
3051
3052 } // End if supplied
3053
3054 // Pre-compute surface normals for each triangle
3055 uint8_t* src_vertices_ptr = preparation_data_.vertex_data.data();
3056 auto* normals_ptr = new math::vec3[preparation_data_.triangle_count];
3057 memset(normals_ptr, 0, preparation_data_.triangle_count * sizeof(math::vec3));
3058 for(i = 0; i < preparation_data_.triangle_count; ++i)
3059 {
3060 // Retrieve positions of each referenced vertex.
3061 const triangle& tri = preparation_data_.triangle_data[i];
3062 const auto* v1 =
3063 reinterpret_cast<const math::vec3*>(src_vertices_ptr + (tri.indices[0] * vertex_stride) + position_offset);
3064 const auto* v2 =
3065 reinterpret_cast<const math::vec3*>(src_vertices_ptr + (tri.indices[1] * vertex_stride) + position_offset);
3066 const auto* v3 =
3067 reinterpret_cast<const math::vec3*>(src_vertices_ptr + (tri.indices[2] * vertex_stride) + position_offset);
3068
3069 // Compute the two edge vectors required for generating our normal
3070 // We normalize here to prevent problems when the triangles are very small.
3071 vec_edge1 = math::normalize(*v2 - *v1);
3072 vec_edge2 = math::normalize(*v3 - *v1);
3073
3074 // Generate the normal
3075 vec_normal = math::cross(vec_edge1, vec_edge2);
3076 normals_ptr[i] = math::normalize(vec_normal);
3077
3078 } // Next Face
3079
3080 // Now compute the actual VERTEX normals using face adjacency information
3081 for(i = 0; i < preparation_data_.triangle_count; ++i)
3082 {
3083 triangle& tri = preparation_data_.triangle_data[i];
3085 {
3086 continue;
3087 }
3088
3089 // Process each vertex in the face
3090 for(j = 0; j < 3; ++j)
3091 {
3092 // Retrieve the index for this vertex.
3093 index = tri.indices[j];
3094
3095 // Skip this vertex if normal information was already provided.
3096 if(!force_normal_generation_ &&
3097 (preparation_data_.vertex_flags[index] & preparation_data::source_contains_normal))
3098 {
3099 continue;
3100 }
3101
3102 // To generate vertex normals using the adjacency information we first
3103 // need to walk backwards
3104 // through the list to find the first triangle that references this vertex
3105 // (using entrance/exit
3106 // edge strategy).
3107 // Once we have the first triangle, step forwards and sum the normals of
3108 // each of the faces
3109 // for each triangle we touch. This is essentially a flood fill through
3110 // all of the triangles
3111 // that touch this vertex, without ever having to test the entire set for
3112 // shared vertices.
3113 // The initial backwards traversal prevents us from having to store (and
3114 // test) a 'visited' flag
3115 // for
3116 // every triangle in the buffer.
3117
3118 // First walk backwards...
3119 start_tri = i;
3120 previous_tri = i;
3121 current_tri = adjacency_ptr[(i * 3) + ((j + 2) % 3)];
3122 for(;;)
3123 {
3124 // Stop walking if we reach the starting triangle again, or if there
3125 // is no connectivity out of this edge
3126 if(current_tri == start_tri || current_tri == 0xFFFFFFFF)
3127 {
3128 break;
3129 }
3130
3131 // Find the edge in the adjacency list that we came in through
3132 for(k = 0; k < 3; ++k)
3133 {
3134 if(adjacency_ptr[(current_tri * 3) + k] == previous_tri)
3135 {
3136 break;
3137 }
3138
3139 } // Next item in adjacency list
3140
3141 // If we found the edge we entered through, the exit edge will
3142 // be the edge counter-clockwise from this one when walking backwards
3143 if(k < 3)
3144 {
3145 previous_tri = current_tri;
3146 current_tri = adjacency_ptr[(current_tri * 3) + ((k + 2) % 3)];
3147
3148 } // End if found entrance edge
3149 else
3150 {
3151 break;
3152
3153 } // End if failed to find entrance edge
3154
3155 } // Next Test
3156
3157 // We should now be at the starting triangle, we can start to walk
3158 // forwards
3159 // collecting the face normals. First find the exit edge so we can start
3160 // walking.
3161 if(current_tri != 0xFFFFFFFF)
3162 {
3163 for(k = 0; k < 3; ++k)
3164 {
3165 if(adjacency_ptr[(current_tri * 3) + k] == previous_tri)
3166 {
3167 break;
3168 }
3169
3170 } // Next item in adjacency list
3171 }
3172 else
3173 {
3174 // Couldn't step back, so first triangle is the current triangle
3175 current_tri = i;
3176 k = j;
3177 }
3178
3179 if(k < 3)
3180 {
3181 start_tri = current_tri;
3182 previous_tri = current_tri;
3183 current_tri = adjacency_ptr[(current_tri * 3) + k];
3184 vec_normal = normals_ptr[start_tri];
3185 for(;;)
3186 {
3187 // Stop walking if we reach the starting triangle again, or if there
3188 // is no connectivity out of this edge
3189 if(current_tri == start_tri || current_tri == 0xFFFFFFFF)
3190 {
3191 break;
3192 }
3193
3194 // Add this normal.
3195 vec_normal += normals_ptr[current_tri];
3196
3197 // Find the edge in the adjacency list that we came in through
3198 for(k = 0; k < 3; ++k)
3199 {
3200 if(adjacency_ptr[(current_tri * 3) + k] == previous_tri)
3201 {
3202 break;
3203 }
3204
3205 } // Next item in adjacency list
3206
3207 // If we found the edge we came entered through, the exit edge will
3208 // be the edge clockwise from this one when walking forwards
3209 if(k < 3)
3210 {
3211 previous_tri = current_tri;
3212 current_tri = adjacency_ptr[(current_tri * 3) + ((k + 1) % 3)];
3213
3214 } // End if found entrance edge
3215 else
3216 {
3217 break;
3218
3219 } // End if failed to find entrance edge
3220
3221 } // Next Test
3222
3223 } // End if found entrance edge
3224
3225 // Normalize the new vertex normal
3226 vec_normal = math::normalize(vec_normal);
3227
3228 // If the normal we are about to store is significantly different from any
3229 // normal
3230 // already stored in this vertex (excepting the case where it is <0,0,0>),
3231 // we need
3232 // to split the vertex into two.
3233 float fn[4];
3234 gfx::vertex_unpack(fn, gfx::attribute::Normal, vertex_format_, src_vertices_ptr, index);
3235 math::vec3 ref_normal;
3236 ref_normal[0] = fn[0];
3237 ref_normal[1] = fn[1];
3238 ref_normal[2] = fn[2];
3239 if(ref_normal.x == 0.0f && ref_normal.y == 0.0f && ref_normal.z == 0.0f)
3240 {
3241 gfx::vertex_pack(fn, true, gfx::attribute::Normal, vertex_format_, src_vertices_ptr, index);
3242 } // End if no normal stored here yet
3243 else
3244 {
3245 // Split and store in a new vertex if it is different (enough)
3246 if(math::abs(ref_normal.x - vec_normal.x) >= 1e-3f || math::abs(ref_normal.y - vec_normal.y) >= 1e-3f ||
3247 math::abs(ref_normal.z - vec_normal.z) >= 1e-3f)
3248 {
3249 // Make room for new vertex data.
3250 preparation_data_.vertex_data.resize(preparation_data_.vertex_data.size() + vertex_stride);
3251
3252 // Ensure that we update the 'src_vertices_ptr' pointer (used
3253 // throughout the
3254 // loop). The internal buffer wrapped by the resized vertex data
3255 // vector
3256 // may have been re-allocated.
3257 src_vertices_ptr = preparation_data_.vertex_data.data();
3258
3259 // Duplicate the vertex at the end of the buffer
3260 std::memcpy(src_vertices_ptr + (preparation_data_.vertex_count * vertex_stride),
3261 src_vertices_ptr + (index * vertex_stride),
3262 vertex_stride);
3263
3264 // Duplicate any other remaining information.
3265 preparation_data_.vertex_flags.push_back(preparation_data_.vertex_flags[index]);
3266
3267 // Record the split
3268 if(remap_array_ptr)
3269 {
3270 (*remap_array_ptr)[index] = preparation_data_.vertex_count;
3271 }
3272
3273 // Store the new normal and finally record the fact that we have
3274 // added a new vertex.
3275 index = preparation_data_.vertex_count++;
3276 math::vec4 norm(vec_normal, 0.0f);
3277 gfx::vertex_pack(math::value_ptr(norm),
3278 true,
3279 gfx::attribute::Normal,
3280 vertex_format_,
3281 src_vertices_ptr,
3282 index);
3283
3284 // Update the index
3285 tri.indices[j] = index;
3286
3287 } // End if normal is different
3288
3289 } // End if normal already stored here
3290
3291 } // Next Vertex
3292
3293 } // Next Face
3294
3295 // We're done with the surface normals
3296 checked_array_delete(normals_ptr);
3297
3298 // If no new vertices were introduced, then it is not necessary
3299 // for the caller to remap anything.
3300 if(remap_array_ptr && original_vertex_count == preparation_data_.vertex_count)
3301 {
3302 remap_array_ptr->clear();
3303 }
3304
3305 // Success!
3306 return true;
3307}
3308
3309auto mesh::generate_vertex_barycentrics(uint32_t* adjacency) -> bool
3310{
3311 (void)adjacency;
3312 return true;
3313}
3314
3316{
3317 math::vec3 *tangents = nullptr, *bitangents = nullptr;
3318 uint32_t i, i1, i2, i3, num_faces, num_verts;
3319 math::vec3 P, Q, T, B, cross_vec, normal_vec;
3320
3321 // Get access to useful data offset information.
3322 uint16_t vertex_stride = vertex_format_.getStride();
3323
3324 bool has_normals = vertex_format_.has(gfx::attribute::Normal);
3325 // This will fail if we don't already have normals however.
3326 if(!has_normals)
3327 {
3328 return false;
3329 }
3330
3331 // Final format requests tangents?
3332 bool requires_tangents = vertex_format_.has(gfx::attribute::Tangent);
3333 bool requires_bitangents = vertex_format_.has(gfx::attribute::Bitangent);
3334 if(!force_tangent_generation_ && !requires_bitangents && !requires_tangents)
3335 {
3336 return true;
3337 }
3338
3339 // Allocate storage space for the tangent and bitangent vectors
3340 // that we will effectively need to average for shared vertices.
3341 num_faces = preparation_data_.triangle_count;
3342 num_verts = preparation_data_.vertex_count;
3343 tangents = new math::vec3[num_verts];
3344 bitangents = new math::vec3[num_verts];
3345 memset(tangents, 0, sizeof(math::vec3) * num_verts);
3346 memset(bitangents, 0, sizeof(math::vec3) * num_verts);
3347
3348 // Iterate through each triangle in the mesh
3349 uint8_t* src_vertices_ptr = preparation_data_.vertex_data.data();
3350 for(i = 0; i < num_faces; ++i)
3351 {
3352 triangle& tri = preparation_data_.triangle_data[i];
3353
3354 // Compute the three indices for the triangle
3355 i1 = tri.indices[0];
3356 i2 = tri.indices[1];
3357 i3 = tri.indices[2];
3358
3359 // Retrieve references to the positions of the three vertices in the
3360 // triangle.
3361 math::vec3 E;
3362 float fE[4];
3363 gfx::vertex_unpack(fE, gfx::attribute::Position, vertex_format_, src_vertices_ptr, i1);
3364 math::vec3 F;
3365 float fF[4];
3366 gfx::vertex_unpack(fF, gfx::attribute::Position, vertex_format_, src_vertices_ptr, i2);
3367 math::vec3 G;
3368 float fG[4];
3369 gfx::vertex_unpack(fG, gfx::attribute::Position, vertex_format_, src_vertices_ptr, i3);
3370 std::memcpy(&E[0], fE, 3 * sizeof(float));
3371 std::memcpy(&F[0], fF, 3 * sizeof(float));
3372 std::memcpy(&G[0], fG, 3 * sizeof(float));
3373
3374 // Retrieve references to the base texture coordinates of the three vertices
3375 // in the triangle.
3376 // TODO: Allow customization of which tex coordinates to generate from.
3377 math::vec2 Et;
3378 float fEt[4];
3379 gfx::vertex_unpack(&fEt[0], gfx::attribute::TexCoord0, vertex_format_, src_vertices_ptr, i1);
3380 math::vec2 Ft;
3381 float fFt[4];
3382 gfx::vertex_unpack(&fFt[0], gfx::attribute::TexCoord0, vertex_format_, src_vertices_ptr, i2);
3383 math::vec2 Gt;
3384 float fGt[4];
3385 gfx::vertex_unpack(&fGt[0], gfx::attribute::TexCoord0, vertex_format_, src_vertices_ptr, i3);
3386 std::memcpy(&Et[0], fEt, 2 * sizeof(float));
3387 std::memcpy(&Ft[0], fFt, 2 * sizeof(float));
3388 std::memcpy(&Gt[0], fGt, 2 * sizeof(float));
3389
3390 // Compute the known variables P & Q, where "P = F-E" and "Q = G-E"
3391 // based on our original discussion of the tangent vector
3392 // calculation.
3393 P = F - E;
3394 Q = G - E;
3395
3396 // Also compute the know variables <s1,t1> and <s2,t2>. Recall that
3397 // these are the texture coordinate deltas similarly for "F-E"
3398 // and "G-E".
3399 float s1 = Ft.x - Et.x;
3400 float t1 = Ft.y - Et.y;
3401 float s2 = Gt.x - Et.x;
3402 float t2 = Gt.y - Et.y;
3403
3404 // Next we can pre-compute part of the equation we developed
3405 // earlier: "1/(s1 * t2 - s2 * t1)". We do this in two separate
3406 // stages here in order to ensure that the texture coordinates
3407 // are not invalid.
3408 float r = (s1 * t2 - s2 * t1);
3409 if(math::abs(r) < math::epsilon<float>())
3410 {
3411 continue;
3412 }
3413 r = 1.0f / r;
3414
3415 // All that's left for us to do now is to run the matrix
3416 // multiplication and multiply the result by the scalar portion
3417 // we precomputed earlier.
3418 T.x = r * (t2 * P.x - t1 * Q.x);
3419 T.y = r * (t2 * P.y - t1 * Q.y);
3420 T.z = r * (t2 * P.z - t1 * Q.z);
3421 B.x = r * (s1 * Q.x - s2 * P.x);
3422 B.y = r * (s1 * Q.y - s2 * P.y);
3423 B.z = r * (s1 * Q.z - s2 * P.z);
3424
3425 // Add the tangent and bitangent vectors (summed average) to
3426 // any previous values computed for each vertex.
3427 tangents[i1] += T;
3428 tangents[i2] += T;
3429 tangents[i3] += T;
3430 bitangents[i1] += B;
3431 bitangents[i2] += B;
3432 bitangents[i3] += B;
3433
3434 } // Next triangle
3435
3436 // Generate final tangent vectors
3437 for(i = 0; i < num_verts; i++, src_vertices_ptr += vertex_stride)
3438 {
3439 // Skip if the original imported data already provided a bitangent /
3440 // tangent.
3441 bool has_bitangent = false;
3442 bool has_tangent = false;
3443
3444 if(!preparation_data_.vertex_flags.empty())
3445 {
3446 has_bitangent = ((preparation_data_.vertex_flags[i] & preparation_data::source_contains_binormal) != 0);
3447 has_tangent = ((preparation_data_.vertex_flags[i] & preparation_data::source_contains_tangent) != 0);
3448 }
3449 if(!force_tangent_generation_ && has_bitangent && has_tangent)
3450 {
3451 continue;
3452 }
3453
3454 // Retrieve the normal vector from the vertex and the computed
3455 // tangent vector.
3456 float normal[4];
3457 gfx::vertex_unpack(normal, gfx::attribute::Normal, vertex_format_, src_vertices_ptr);
3458 std::memcpy(&normal_vec[0], normal, 3 * sizeof(float));
3459
3460 T = tangents[i];
3461
3462 // GramSchmidt orthogonalize
3463 T = T - (normal_vec * math::dot(normal_vec, T));
3464
3465 // Check if the resulting tangent is too small (parallel to normal case)
3466 float length_sq = math::dot(T, T);
3467 if(length_sq < 1e-6f)
3468 {
3469 // Tangent was parallel to normal, generate a perpendicular vector
3470 // Find the axis that the normal is least aligned with
3471 math::vec3 axis;
3472 if(std::abs(normal_vec.x) < std::abs(normal_vec.y) && std::abs(normal_vec.x) < std::abs(normal_vec.z))
3473 {
3474 axis = math::vec3(1.0f, 0.0f, 0.0f);
3475 }
3476 else if(std::abs(normal_vec.y) < std::abs(normal_vec.z))
3477 {
3478 axis = math::vec3(0.0f, 1.0f, 0.0f);
3479 }
3480 else
3481 {
3482 axis = math::vec3(0.0f, 0.0f, 1.0f);
3483 }
3484 T = math::cross(normal_vec, axis);
3485 }
3486
3487 T = math::normalize(T);
3488
3489 // Store tangent if required
3490 if(force_tangent_generation_ || (!has_tangent && requires_tangents))
3491 {
3492 math::vec4 t(T, 1.0f);
3493 gfx::vertex_pack(math::value_ptr(t), true, gfx::attribute::Tangent, vertex_format_, src_vertices_ptr);
3494 }
3495
3496 // Compute and store bitangent if required
3497 if(force_tangent_generation_ || (!has_bitangent && requires_bitangents))
3498 {
3499 // Calculate the new orthogonal bitangent
3500 B = math::cross(normal_vec, T);
3501 B = math::normalize(B);
3502
3503 // Compute the "handedness" of the tangent and bitangent. This
3504 // ensures the inverted / mirrored texture coordinates still have
3505 // an accurate matrix.
3506 cross_vec = math::cross(normal_vec, T);
3507 if(math::dot(cross_vec, bitangents[i]) < 0.0f)
3508 {
3509 // Flip the bitangent
3510 B = -B;
3511
3512 } // End if coordinates inverted
3513
3514 // Store.
3515 math::vec4 b(B, 1.0f);
3516 gfx::vertex_pack(math::value_ptr(b), true, gfx::attribute::Bitangent, vertex_format_, src_vertices_ptr);
3517
3518 } // End if requires bitangent
3519
3520 } // Next vertex
3521
3522 // Cleanup
3523 checked_array_delete(tangents);
3524 checked_array_delete(bitangents);
3525
3526 // Return success
3527 return true;
3528}
3529
3530auto mesh::weld_vertices(float tolerance, std::vector<uint32_t>* vertex_remap_ptr /* = nullptr */) -> bool
3531{
3532 weld_key key;
3533 std::map<weld_key, uint32_t> vertex_tree;
3534 std::map<weld_key, uint32_t>::const_iterator it_key;
3535 byte_array_t new_vertex_data, new_vertex_flags;
3536 uint32_t new_vertex_count = 0;
3537
3538 // Allocate enough space to build the remap array for the existing vertices
3539 if(vertex_remap_ptr)
3540 {
3541 vertex_remap_ptr->resize(preparation_data_.vertex_count);
3542 }
3543 auto collapse_map = new uint32_t[preparation_data_.vertex_count];
3544
3545 // Retrieve useful data offset information.
3546 uint16_t vertex_stride = vertex_format_.getStride();
3547
3548 // For each vertex to be welded.
3549 for(uint32_t i = 0; i < preparation_data_.vertex_count; ++i)
3550 {
3551 // Build a new key structure for inserting
3552 key.vertex = (&preparation_data_.vertex_data[0]) + (i * vertex_stride);
3553 key.format = vertex_format_;
3554 key.tolerance = tolerance;
3555
3556 // Does a vertex with matching details already exist in the tree.
3557 it_key = vertex_tree.find(key);
3558 if(it_key == vertex_tree.end())
3559 {
3560 // No matching vertex. Insert into the tree (value = NEW index of vertex).
3561 vertex_tree[key] = new_vertex_count;
3562 collapse_map[i] = new_vertex_count;
3563 if(vertex_remap_ptr)
3564 {
3565 (*vertex_remap_ptr)[i] = new_vertex_count;
3566 }
3567
3568 // Store the vertex in the new buffer
3569 new_vertex_data.resize((new_vertex_count + 1) * vertex_stride);
3570 std::memcpy(&new_vertex_data[new_vertex_count * vertex_stride], key.vertex, vertex_stride);
3571 new_vertex_flags.push_back(preparation_data_.vertex_flags[i]);
3572 new_vertex_count++;
3573
3574 } // End if no matching vertex
3575 else
3576 {
3577 // A vertex already existed at this location.
3578 // Just mark the 'collapsed' index for this vertex in the remap array.
3579 collapse_map[i] = it_key->second;
3580 if(vertex_remap_ptr)
3581 {
3582 (*vertex_remap_ptr)[i] = 0xFFFFFFFF;
3583 }
3584
3585 } // End if vertex already existed
3586
3587 } // Next Vertex
3588
3589 // If nothing was welded, just bail
3590 if(preparation_data_.vertex_count == new_vertex_count)
3591 {
3592 checked_array_delete(collapse_map);
3593
3594 if(vertex_remap_ptr)
3595 {
3596 vertex_remap_ptr->clear();
3597 }
3598 return true;
3599
3600 } // End if nothing to do
3601
3602 // Otherwise, replace the old preparation vertices and remap
3603 preparation_data_.vertex_data.clear();
3604 preparation_data_.vertex_data.resize(new_vertex_data.size());
3605 std::memcpy(preparation_data_.vertex_data.data(), new_vertex_data.data(), new_vertex_data.size());
3606 preparation_data_.vertex_flags.clear();
3607 preparation_data_.vertex_flags.resize(new_vertex_flags.size());
3608 std::memcpy(preparation_data_.vertex_flags.data(), new_vertex_flags.data(), new_vertex_flags.size());
3609 preparation_data_.vertex_count = new_vertex_count;
3610
3611 // Now remap all the triangle indices
3612 for(uint32_t i = 0; i < preparation_data_.triangle_count; ++i)
3613 {
3614 triangle& tri = preparation_data_.triangle_data[i];
3615 tri.indices[0] = collapse_map[tri.indices[0]];
3616 tri.indices[1] = collapse_map[tri.indices[1]];
3617 tri.indices[2] = collapse_map[tri.indices[2]];
3618
3619 } // Next triangle
3620
3621 // Clean up
3622 checked_array_delete(collapse_map);
3623
3624 // Success!
3625 return true;
3626}
3627
3629// skin_bind_data Member Definitions
3632{
3633 bones_.push_back(bone);
3634}
3635
3637{
3638 for(size_t i = 0; i < bones_.size();)
3639 {
3640 if(bones_[i].influences.empty())
3641 {
3642 bones_.erase(bones_.begin() + static_cast<int>(i));
3643
3644 } // End if empty
3645 else
3646 {
3647 ++i;
3648 }
3649
3650 } // Next Bone
3651}
3652
3654{
3655 for(auto& bone : bones_)
3656 {
3657 bone.influences.clear();
3658 }
3659}
3660
3662{
3663 bones_.clear();
3664}
3665
3666void skin_bind_data::remap_vertices(const std::vector<uint32_t>& remap)
3667{
3668 // Iterate through all bone information and remap vertex indices.
3669 for(auto& bone : bones_)
3670 {
3671 vertex_influence_array_t new_influences;
3672 vertex_influence_array_t& influences = bone.influences;
3673 new_influences.reserve(influences.size());
3674 for(auto& influence : influences)
3675 {
3676 uint32_t new_index = remap[influence.vertex_index];
3677 if(new_index != 0xFFFFFFFF)
3678 {
3679 // Insert an influence at the new index
3680 new_influences.push_back(vertex_influence{new_index, influence.weight});
3681
3682 // If the vertex was split into two, we want to retain an
3683 // influence to the original index too.
3684 if(new_index >= remap.size())
3685 {
3686 new_influences.push_back(vertex_influence{influence.vertex_index, influence.weight});
3687 }
3688
3689 } // End if !removed
3690
3691 } // Next source influence
3692 bone.influences = new_influences;
3693
3694 } // Next bone
3695}
3696
3697void skin_bind_data::build_vertex_table(uint32_t vertex_count,
3698 const std::vector<uint32_t>& vertex_remap,
3699 vertex_data_array_t& table)
3700{
3701 uint32_t vertex{};
3702
3703 // Initialize the vertex table with the required number of vertices.
3704 table.reserve(vertex_count);
3705 for(vertex = 0; vertex < vertex_count; ++vertex)
3706 {
3707 vertex_data data;
3708 data.palette = -1;
3709 data.original_vertex = vertex;
3710 table.push_back(data);
3711
3712 } // Next Vertex
3713
3714 // Iterate through all bone information and populate the above array.
3715 for(size_t i = 0; i < bones_.size(); ++i)
3716 {
3717 vertex_influence_array_t& influences = bones_[i].influences;
3718 for(auto& influence : influences)
3719 {
3720 // Vertex data has been remapped?
3721 if(!vertex_remap.empty())
3722 {
3723 vertex = vertex_remap[influence.vertex_index];
3724 if(vertex == 0xFFFFFFFF)
3725 {
3726 continue;
3727 }
3728 auto& data = table[vertex];
3729 // Push influence data.
3730 data.influences.push_back(static_cast<int32_t>(i));
3731 data.weights.push_back(influence.weight);
3732 } // End if remap
3733 else
3734 {
3735 auto& data = table[influence.vertex_index];
3736 // Push influence data.
3737 data.influences.push_back(static_cast<int32_t>(i));
3738 data.weights.push_back(influence.weight);
3739 }
3740
3741 } // Next Influence
3742
3743 } // Next Bone
3744}
3745
3747{
3748 return bones_;
3749}
3750
3751auto skin_bind_data::get_bones() -> std::vector<skin_bind_data::bone_influence>&
3752{
3753 return bones_;
3754}
3755
3756auto skin_bind_data::has_bones() const -> bool
3757{
3758 return !get_bones().empty();
3759}
3760
3761auto skin_bind_data::find_bone_by_id(const std::string& name) const -> bone_query
3762{
3763 bone_query query{};
3764 auto it = std::find_if(std::begin(bones_),
3765 std::end(bones_),
3766 [name](const auto& bone)
3767 {
3768 return name == bone.bone_id;
3769 });
3770 if(it != std::end(bones_))
3771 {
3772 query.bone = &(*it);
3773 query.index = std::distance(std::begin(bones_), it);
3774 }
3775
3776 return query;
3777}
3778
3780// bone_palette Member Definitions
3782//-----------------------------------------------------------------------------
3783// Name : bone_palette() (Constructor)
3787//-----------------------------------------------------------------------------
3788bone_palette::bone_palette(uint32_t palette_size)
3789 : data_group_id_(0)
3790 , maximum_size_(palette_size)
3791 , maximum_blend_index_(-1)
3792{
3793}
3794
3795auto bone_palette::get_skinning_matrices(const std::vector<math::transform>& node_transforms,
3796 const skin_bind_data& bind_data) const -> const std::vector<math::mat4>&
3797{
3798 // Retrieve the main list of bones from the skin bind data that will
3799 // be referenced by the palette's bone index list.
3800 const auto& bind_list = bind_data.get_bones();
3801
3802 // Compute transformation matrix for each bone in the palette
3803 const size_t bones_size = bones_.size();
3804 const size_t count = std::min(bones_size, node_transforms.size());
3805
3806 thread_local static std::vector<math::mat4> skinning_transforms_;
3807 skinning_transforms_.resize(bones_size, math::identity<math::mat4>());
3808
3809 // Optimize: cache bind list size check
3810 const size_t bind_list_size = bind_list.size();
3811
3812 for(size_t i = 0; i < count; ++i)
3813 {
3814 const auto bone = bones_[i];
3815
3816 // Bounds check to avoid out-of-range access
3817 if(bone >= node_transforms.size() || bone >= bind_list_size)
3818 {
3819 continue;
3820 }
3821
3822 const auto& bone_transform = node_transforms[bone];
3823 const auto& bone_data = bind_list[bone];
3824
3825 // Direct matrix multiplication - cache the bind pose matrix
3826 const auto& bind_pose_matrix = bone_data.bind_pose_transform.get_matrix();
3827 skinning_transforms_[i] = bone_transform.get_matrix() * bind_pose_matrix;
3828 }
3829
3830 return skinning_transforms_;
3831}
3832
3833auto bone_palette::get_skinning_matrices(const std::vector<math::mat4>& node_transforms,
3834 const skin_bind_data& bind_data) const -> const std::vector<math::mat4>&
3835{
3836 // Retrieve the main list of bones from the skin bind data that will
3837 // be referenced by the palette's bone index list.
3838 const auto& bind_list = bind_data.get_bones();
3839
3840 // Compute transformation matrix for each bone in the palette
3841 const size_t bones_size = bones_.size();
3842 const size_t count = std::min(bones_size, node_transforms.size());
3843
3844 thread_local static std::vector<math::mat4> skinning_transforms_;
3845 skinning_transforms_.resize(bones_size, math::identity<math::mat4>());
3846
3847 // Optimize: cache bind list size check
3848 const size_t bind_list_size = bind_list.size();
3849
3850 for(size_t i = 0; i < count; ++i)
3851 {
3852 const auto bone = bones_[i];
3853
3854 // Bounds check to avoid out-of-range access
3855 if(bone >= node_transforms.size() || bone >= bind_list_size)
3856 {
3857 continue;
3858 }
3859
3860 const auto& bone_transform = node_transforms[bone];
3861 const auto& bone_data = bind_list[bone];
3862
3863 // Direct matrix multiplication - cache the bind pose matrix
3864 const auto& bind_pose_matrix = bone_data.bind_pose_transform.get_matrix();
3865 skinning_transforms_[i] = bone_transform * bind_pose_matrix;
3866 }
3867
3868 return skinning_transforms_;
3869}
3870
3871void bone_palette::assign_bones(bone_index_map_t& bones, std::vector<uint32_t>& faces)
3872{
3873 bone_index_map_t::iterator it_bone, it_bone2;
3874
3875 // Iterate through newly specified input bones and add any unique ones to the
3876 // palette.
3877 for(it_bone = bones.begin(); it_bone != bones.end(); ++it_bone)
3878 {
3879 it_bone2 = bones_lut_.find(it_bone->first);
3880 if(it_bone2 == bones_lut_.end())
3881 {
3882 bones_lut_[it_bone->first] = static_cast<uint32_t>(bones_.size());
3883 bones_.push_back(it_bone->first);
3884
3885 } // End if not already added
3886
3887 } // Next Bone
3888
3889 // Merge the new face list with ours.
3890 // faces_.insert(faces_.end(), faces.begin(), faces.end());
3891
3892 faces_.resize(faces_.size() + faces.size());
3893 std::memcpy(faces_.data(), faces.data(), faces.size() * sizeof(uint32_t));
3894}
3895
3896void bone_palette::assign_bones(std::vector<bool>& bones, std::vector<uint32_t>& faces)
3897{
3898 bone_index_map_t::iterator it_bone, it_bone2;
3899
3900 // Iterate through newly specified input bones and add any unique ones to the
3901 // palette.
3902 // for(it_bone = bones.begin(); it_bone != bones.end(); ++it_bone)
3903 for(size_t i = 0, j = bones.size(); i < j; ++i)
3904 {
3905 if(!bones[i])
3906 {
3907 continue;
3908 }
3909
3910 it_bone2 = bones_lut_.find(i);
3911 if(it_bone2 == bones_lut_.end())
3912 {
3913 bones_lut_[i] = static_cast<uint32_t>(bones_.size());
3914 bones_.push_back(i);
3915
3916 } // End if not already added
3917
3918 } // Next Bone
3919
3920 // Merge the new face list with ours.
3921 // faces_.insert(faces_.end(), faces.begin(), faces.end());
3922
3923 faces_.resize(faces_.size() + faces.size());
3924 std::memcpy(faces_.data(), faces.data(), faces.size() * sizeof(uint32_t));
3925}
3926
3927void bone_palette::assign_bones(const std::vector<uint32_t>& bones)
3928{
3929 bone_index_map_t::iterator it_bone;
3930
3931 // Clear out prior data.
3932 bones_.clear();
3933 bones_lut_.clear();
3934
3935 // Iterate through newly specified input bones and add any unique ones to the
3936 // palette.
3937 for(size_t i = 0; i < bones.size(); ++i)
3938 {
3939 it_bone = bones_lut_.find(bones[i]);
3940 if(it_bone == bones_lut_.end())
3941 {
3942 bones_lut_[bones[i]] = static_cast<uint32_t>(bones_.size());
3943 bones_.push_back(bones[i]);
3944
3945 } // End if not already added
3946
3947 } // Next Bone
3948}
3949
3951 int32_t& current_space,
3952 int32_t& common_bones,
3953 int32_t& additional_bones)
3954{
3955 // Reset values
3956 current_space = static_cast<int32_t>(maximum_size_ - static_cast<uint32_t>(bones_.size()));
3957 common_bones = 0;
3958 additional_bones = 0;
3959
3960 // Early out if possible
3961 if(bones_.size() == 0)
3962 {
3963 additional_bones = static_cast<int32_t>(input.size());
3964 return;
3965
3966 } // End if no bones stored
3967 else if(input.size() == 0)
3968 {
3969 return;
3970
3971 } // End if no bones input
3972
3973 // Iterate through newly specified input bones and see how many
3974 // indices it has in common with our existing set.
3975 bone_index_map_t::iterator it_bone, it_bone2;
3976 for(it_bone = input.begin(); it_bone != input.end(); ++it_bone)
3977 {
3978 it_bone2 = bones_lut_.find(it_bone->first);
3979 if(it_bone2 != bones_lut_.end())
3980 common_bones++;
3981 else
3982 additional_bones++;
3983
3984 } // Next Bone
3985}
3986
3987auto bone_palette::translate_bone_to_palette(uint32_t bone_index) const -> uint32_t
3988{
3989 auto it_bone = bones_lut_.find(bone_index);
3990 if(it_bone == bones_lut_.end())
3991 return 0xFFFFFFFF;
3992 return it_bone->second;
3993}
3994
3995auto bone_palette::get_data_group() const -> uint32_t
3996{
3997 return data_group_id_;
3998}
3999
4001{
4002 data_group_id_ = group;
4003}
4004
4006{
4007 return maximum_blend_index_;
4008}
4009
4011{
4012 maximum_blend_index_ = nIndex;
4013}
4014
4015auto bone_palette::get_maximum_size() const -> uint32_t
4016{
4017 return maximum_size_;
4018}
4019
4020auto bone_palette::get_influenced_faces() -> std::vector<uint32_t>&
4021{
4022 return faces_;
4023}
4024
4026{
4027 faces_.clear();
4028}
4029
4030auto bone_palette::get_bones() const -> const std::vector<uint32_t>&
4031{
4032 return bones_;
4033}
4034
4036// Global Operator Definitions
4039{
4040 if(preparation_data_.compute_per_triangle_material_data)
4041 {
4042 // math::transform triangle indices, material and data group information
4043 // to the final triangle data arrays. We keep the latter two handy so
4044 // that we know precisely which material each triangle belongs to.
4045 triangle_data_.resize(face_count_);
4046 }
4047 uint32_t* dst_indices_ptr = system_ib_;
4048 for(uint32_t i = 0; i < face_count_; ++i)
4049 {
4050 // Copy indices.
4051 const triangle& tri_in = preparation_data_.triangle_data[i];
4052 *dst_indices_ptr++ = tri_in.indices[0];
4053 *dst_indices_ptr++ = tri_in.indices[1];
4054 *dst_indices_ptr++ = tri_in.indices[2];
4055
4056 if(preparation_data_.compute_per_triangle_material_data)
4057 {
4058 // Copy triangle submesh information.
4059 mesh_submesh_key& tri_out = triangle_data_[i];
4060 tri_out.data_group_id = tri_in.data_group_id;
4061 }
4062
4063 } // Next triangle
4064
4065 preparation_data_.triangle_count = 0;
4066 preparation_data_.triangle_data.clear();
4067
4068 // Clear out any old data EXCEPT the old submesh index
4069 // We'll need this in order to understand how to update
4070 // the material reference counting later on.
4071 data_groups_.clear();
4072 // Destroy old submesh data.
4073 for(auto submesh : mesh_submeshes_)
4074 {
4076 }
4077 mesh_submeshes_.clear();
4078
4079 skinned_submesh_indices_.clear();
4080 skinned_submesh_count_ = {};
4081
4082 non_skinned_submesh_indices_.clear();
4083 non_skinned_submesh_count_ = {};
4084
4085 for(size_t i = 0; i < preparation_data_.submeshes.size(); ++i)
4086 {
4087 const auto& s = preparation_data_.submeshes[i];
4088 auto* sub = new submesh(s);
4089
4090 if(sub->skinned)
4091 {
4092 skinned_submesh_count_++;
4093 skinned_submesh_indices_[sub->data_group_id].emplace_back(i);
4094 }
4095 else
4096 {
4097 non_skinned_submesh_count_++;
4098 non_skinned_submesh_indices_[sub->data_group_id].emplace_back(i);
4099 }
4100
4101 mesh_submeshes_.emplace_back(sub);
4102 data_groups_[sub->data_group_id].emplace_back(sub);
4103 }
4104
4105 preparation_data_.submeshes.clear();
4106
4107 return true;
4108}
4109
4111{
4112 // Select appropriate index buffer based on LOD
4113 uint32_t* ib_data = nullptr;
4114 std::shared_ptr<void> ib_hardware = nullptr;
4115 uint32_t lod_face_count = 0;
4116
4117 if(lod_index == 0)
4118 {
4119 // Use base LOD (LOD 0)
4120 ib_data = system_ib_;
4121 ib_hardware = hardware_ib_;
4122 lod_face_count = face_count_;
4123 }
4124 else if(lod_index > 0 && lod_index <= lods_.size())
4125 {
4126 // Use simplified LOD
4127 const auto& lod = lods_[lod_index - 1];
4128 ib_data = lod.system_ib_;
4129 ib_hardware = lod.hardware_ib_;
4130 lod_face_count = lod.face_count_;
4131 }
4132 else
4133 {
4134 // Invalid LOD index, fall back to base
4135 ib_data = system_ib_;
4136 ib_hardware = hardware_ib_;
4137 lod_face_count = face_count_;
4138 }
4139
4140 uint32_t index_start = submesh->face_start * 3;
4141 uint32_t index_count = submesh->face_count * 3;
4142
4143 // Hardware or software rendering?
4144 if(hardware_mesh_)
4145 {
4146 // Render using hardware streams
4147 auto vb = std::static_pointer_cast<gfx::vertex_buffer>(hardware_vb_);
4148 auto ib = std::static_pointer_cast<gfx::index_buffer>(ib_hardware);
4149
4150 gfx::set_vertex_buffer(0, vb->native_handle()); // submesh->vertex_start, submesh->vertex_count);
4151 gfx::set_index_buffer(ib->native_handle(), index_start, index_count);
4152
4153 } // End if has hardware copy
4154 else
4155 {
4157 {
4160 std::memcpy(vb.data,
4162 vb.size); // Adjust the pointer to start at the correct vertex
4164 }
4165
4166 if(index_count == gfx::get_avail_transient_index_buffer(index_count, true))
4167 {
4169 gfx::alloc_transient_index_buffer(&ib, index_count, true);
4170 std::memcpy(ib.data,
4171 ib_data + index_start,
4172 index_count * sizeof(uint32_t)); // Adjust the pointer to start at the correct index
4173 gfx::set_index_buffer(&ib, 0, index_count);
4174 }
4175
4176 } // End if software only copy
4177}
4178
4179} // namespace unravel
uint32_t width
uint32_t height
gfx::texture_format format
entt::handle b
void checked_array_delete(T *&x)
void checked_delete(T *&x)
A type erasing container that can store any mesh.
Definition any_mesh.hpp:15
any_generator< mesh_vertex_t > vertices() const noexcept
Definition any_mesh.cpp:22
any_generator< triangle_t > triangles() const noexcept
Definition any_mesh.cpp:17
A mesh with values evaluated using a callback function.
General purpose transformation class designed to maintain each component of the transformation separa...
Definition transform.hpp:27
Manages assets, including loading, unloading, and storage.
Outlines a collection of bones that influence a given set of faces/vertices in the mesh.
Definition mesh.h:186
std::map< uint32_t, uint32_t > bone_index_map_t
Definition mesh.h:188
auto get_bones() const -> const std::vector< uint32_t > &
Retrieves the indices of the bones referenced by this palette.
Definition mesh.cpp:4030
auto get_maximum_size() const -> uint32_t
Retrieves the maximum size of the palette.
Definition mesh.cpp:4015
bone_index_map_t bones_lut_
< Sorted list of bones in this palette.
Definition mesh.h:306
void clear_influenced_faces()
Clears out the temporary face influences array.
Definition mesh.cpp:4025
uint32_t data_group_id_
The maximum size of the palette.
Definition mesh.h:312
void assign_bones(bone_index_map_t &bones, std::vector< uint32_t > &faces)
Assigns the specified bones (and faces) to this bone palette.
Definition mesh.cpp:3871
std::vector< uint32_t > faces_
The data group identifier used to separate the mesh data into submeshes relevant tothis bone palette.
Definition mesh.h:310
auto get_maximum_blend_index() const -> int32_t
Retrieves the maximum vertex blend index for this palette.
Definition mesh.cpp:4005
auto get_influenced_faces() -> std::vector< uint32_t > &
Retrieves the list of faces assigned to this palette.
Definition mesh.cpp:4020
auto get_skinning_matrices(const std::vector< math::transform > &node_transforms, const skin_bind_data &bind_data) const -> const std::vector< math::mat4 > &
Gathers the bone/palette information and matrices ready for drawing the skinned mesh.
Definition mesh.cpp:3795
uint32_t maximum_size_
The maximum vertex blend index for this palette.
Definition mesh.h:314
void compute_palette_fit(bone_index_map_t &input, int32_t &current_space, int32_t &common_base, int32_t &additional_bones)
Determines the relevant "fit" information that can be used to discover if and how the specified combi...
Definition mesh.cpp:3950
void set_data_group(uint32_t group)
Sets the identifier of the data group assigned to the submesh of the mesh reserved for this bone pale...
Definition mesh.cpp:4000
void set_maximum_blend_index(int index)
Sets the maximum vertex blend index for this palette.
Definition mesh.cpp:4010
int32_t maximum_blend_index_
Definition mesh.h:316
auto translate_bone_to_palette(uint32_t bone_index) const -> uint32_t
Translates the specified bone index into its associated position in the palette.
Definition mesh.cpp:3987
bone_palette(uint32_t paletteSize)
Constructs a bone palette with the given size.
Definition mesh.cpp:3788
auto get_data_group() const -> uint32_t
Retrieves the identifier of the data group assigned to the submesh of the mesh reserved for this bone...
Definition mesh.cpp:3995
std::vector< uint32_t > bones_
List of faces assigned to this palette.
Definition mesh.h:308
Class representing a camera. Contains functionality for manipulating and updating a camera....
Definition camera.h:62
Base class for materials used in rendering.
Definition material.h:44
Main class representing a 3D mesh with support for different LODs, submeshes, and skinning.
Definition mesh.h:323
static auto apply_skin_to_load_data(load_data &data) -> bool
Applies skinning vertex duplication to load_data (for offline LOD generation).
Definition mesh.cpp:2033
gfx::vertex_layout vertex_format_
The final system memory copy of the index buffer (LOD 0 - base mesh).
Definition mesh.h:1277
auto get_lod_index_data(uint32_t lod_index, std::vector< uint32_t > &out_indices, float &out_error) const -> bool
Gets index buffer data for a specific LOD level (for serialization).
Definition mesh.cpp:1977
auto generate_vertex_normals(uint32_t *adjacency_ptr, std::vector< uint32_t > *remap_array_ptr=nullptr) -> bool
Generates vertex normals for the mesh.
Definition mesh.cpp:3024
auto create_dodecahedron(const gfx::vertex_layout &format, bool hardware_copy=true) -> bool
Creates a dodecahedron geometry.
Definition mesh.cpp:1152
auto create_plane(const gfx::vertex_layout &format, float width, float height, uint32_t width_segments, uint32_t height_segments, mesh_create_origin origin, bool hardware_copy=true) -> bool
Creates a plane geometry.
Definition mesh.cpp:879
auto restore_lods_from_load_data(const load_data &data) -> bool
Restores LOD levels from load_data into the mesh.
Definition mesh.cpp:2570
static auto generate_lods_for_load_data(load_data &data, const std::vector< std::pair< size_t, float > > &lod_configs) -> bool
Generates LOD levels directly in load_data without creating GPU buffers.
Definition mesh.cpp:2236
bone_palette_array_t bone_palettes_
List of armature nodes.
Definition mesh.h:1325
std::shared_ptr< void > hardware_vb_
The actual hardware index buffer resource (LOD 0 - base mesh).
Definition mesh.h:1283
auto set_vertex_source(void *source, uint32_t vertex_count, const gfx::vertex_layout &source_format) -> bool
Sets the source of the vertex buffer to pull data from while preparing the mesh.
std::unique_ptr< armature_node > root_
UIDs of default materials generated during import, indexed by data_group_id.
Definition mesh.h:1327
auto create_sphere(const gfx::vertex_layout &format, float radius, uint32_t stacks, uint32_t slices, mesh_create_origin origin, bool hardware_copy=true) -> bool
Creates a sphere geometry.
Definition mesh.cpp:1013
auto create_capsule(const gfx::vertex_layout &format, float radius, float height, uint32_t stacks, uint32_t slices, mesh_create_origin origin, bool hardware_copy=true) -> bool
Creates a capsule geometry.
Definition mesh.cpp:1058
submesh_array_t mesh_submeshes_
Indices in the subset array which are skinned.
Definition mesh.h:1293
auto get_bone_palettes() const -> const bone_palette_array_t &
Retrieves the compiled bone combination palette data if this mesh has been bound as a skin.
Definition mesh.cpp:1637
auto get_lod_count() const -> uint32_t
Gets the number of LOD levels available.
Definition mesh.cpp:1942
void check_for_degenerates()
Definition mesh.cpp:1183
auto create_icosahedron(const gfx::vertex_layout &format, bool hardware_copy=true) -> bool
Creates an icosahedron geometry.
Definition mesh.cpp:1137
auto set_submeshes(const std::vector< submesh > &submeshes) -> bool
Definition mesh.cpp:406
auto generate_vertex_barycentrics(uint32_t *adjacency) -> bool
Generates vertex barycentric coordinates for the mesh.
Definition mesh.cpp:3309
auto get_imported_materials() const -> std::vector< asset_handle< material > >
Gets the imported materials generated during import.
Definition mesh.cpp:2765
uint32_t face_count_
Total number of vertices in the prepared mesh.
Definition mesh.h:1313
std::vector< uint8_t > byte_array_t
Definition mesh.h:395
auto get_info() const -> info
Definition mesh.cpp:248
auto get_default_material_uids() const -> const std::vector< hpp::uuid > &
Gets the default material UIDs generated during import.
Definition mesh.cpp:2759
auto get_system_ib() -> uint32_t *
Retrieves the underlying index data from the mesh.
Definition mesh.cpp:1602
auto calculate_screen_rect(const math::transform &world, const camera &cam) const -> irect32_t
Calculates the screen rectangle of the mesh based on its world transform and the camera....
Definition mesh.cpp:1790
auto get_vertex_count() const -> uint32_t
Determines the number of vertices stored in the mesh.
Definition mesh.cpp:1583
auto get_hardware_vb() const -> std::shared_ptr< gfx::vertex_buffer >
Retrieves the hardware vertex buffer for the mesh (shared across all LODs).
Definition mesh.cpp:1612
auto set_primitives(triangle_array_t &&triangles) -> bool
Adds primitives (triangles) to the mesh.
Definition mesh.cpp:424
auto create_teapot(const gfx::vertex_layout &format, bool hardware_copy=true) -> bool
Creates a teapot geometry.
Definition mesh.cpp:1122
auto end_prepare(bool hardware_copy=true, bool build_buffers=true, bool weld=false, bool optimize=false) -> bool
Ends the preparation of the mesh and builds the render data.
Definition mesh.cpp:1218
void bind_render_buffers_for_submesh(const submesh *submesh, uint32_t lod_index=0)
Binds the mesh data for rendering the selected batch of primitives.
Definition mesh.cpp:4110
auto get_bounds() const -> const math::bbox &
Gets the local bounding box for this mesh.
Definition mesh.cpp:2730
auto create_cone(const gfx::vertex_layout &format, float radius, float radius_tip, float height, uint32_t stacks, uint32_t slices, mesh_create_origin origin, bool hardware_copy=true) -> bool
Creates a cone geometry.
Definition mesh.cpp:1079
bool force_normal_generation_
Whether to force the generation of vertex barycentric coordinates.
Definition mesh.h:1268
std::vector< hpp::uuid > default_material_uids_
Definition mesh.h:1330
static auto generate_default_lod_configs(const load_data &data, float target_error=0.01f) -> std::vector< std::pair< size_t, float > >
Generates default LOD configurations based on mesh triangle count.
Definition mesh.cpp:2018
math::bbox bbox_
Total number of faces in the prepared mesh.
Definition mesh.h:1311
auto get_submeshes_count(uint32_t lod_index=0) const -> size_t
Gets the number of submeshes for this mesh.
Definition mesh.cpp:1880
data_group_submesh_map_t data_groups_
Whether the mesh uses a hardware vertex/index buffer.
Definition mesh.h:1304
auto sort_mesh_data() -> bool
Sorts the data in the mesh into material and data group order.
Definition mesh.cpp:4038
auto generate_vertex_tangents() -> bool
Generates vertex tangents for the mesh.
Definition mesh.cpp:3315
auto generate_adjacency(std::vector< uint32_t > &adjacency) -> bool
Generates edge-triangle adjacency information for the mesh data.
Definition mesh.cpp:1344
auto get_lod_submeshes(uint32_t lod_index) const -> const submesh_array_t *
Gets submeshes for a specific LOD level.
Definition mesh.cpp:1947
std::shared_ptr< void > hardware_ib_
Additional LOD levels (LOD 1, 2, 3, ...) with simplified index buffers.
Definition mesh.h:1285
uint8_t * system_vb_
The vertex format used for the mesh internal vertex data.
Definition mesh.h:1275
auto create_rounded_cube(const gfx::vertex_layout &format, float width, float height, float depth, uint32_t width_segments, uint32_t height_segments, uint32_t depth_segments, mesh_create_origin origin, bool hardware_copy=true) -> bool
Definition mesh.cpp:990
auto get_vertex_format() const -> const gfx::vertex_layout &
Retrieves the format of the underlying mesh vertex data.
Definition mesh.cpp:1607
auto get_skinned_submeshes_count(uint32_t lod_index=0) const -> size_t
Gets the number of skinned submeshes for this mesh.
Definition mesh.cpp:2644
bool hardware_mesh_
Whether the mesh was optimized when it was prepared.
Definition mesh.h:1307
auto bind_skin(const skin_bind_data &bind_data) -> bool
Binds the mesh as a skin with the specified skin binding data.
Definition mesh.cpp:579
auto get_non_skinned_submeshes_count(uint32_t lod_index=0) const -> size_t
Gets the number of non-skinned submeshes for this mesh.
Definition mesh.cpp:2687
bool force_tangent_generation_
< Whether to force the generation of tangent space vectors.
Definition mesh.h:1266
auto get_status() const -> mesh_status
Gets the preparation status for this mesh.
Definition mesh.cpp:2735
auto create_icosphere(const gfx::vertex_layout &format, int tesselation_level, bool hardware_copy=true) -> bool
Creates an icosphere geometry.
Definition mesh.cpp:1167
uint32_t vertex_count_
Preparation status of the mesh.
Definition mesh.h:1315
auto load_mesh(load_data &&data) -> bool
Definition mesh.cpp:824
void dispose()
Clears out all the mesh data.
Definition mesh.cpp:168
std::vector< lod_level > lods_
Current LOD count (including base LOD 0).
Definition mesh.h:1288
auto find_submesh_index_by_stable_id(uint32_t stable_id, uint32_t lod_index=0) const -> int
Finds the submesh array index matching an import-stable submesh identifier.
Definition mesh.cpp:1922
void build_vb(bool hardware_copy=true)
Builds the internal vertex buffer.
Definition mesh.cpp:1291
auto generate_vertex_components(bool weld) -> bool
Generates any vertex components that may be missing, such as normals, tangents, or binormals.
Definition mesh.cpp:2948
auto get_lod_face_count(uint32_t lod_index) const -> uint32_t
Gets the face count for a specific LOD level.
Definition mesh.cpp:1962
auto create_cube(const gfx::vertex_layout &format, float width, float height, float depth, uint32_t width_segments, uint32_t height_segments, uint32_t depth_segments, mesh_create_origin origin, bool hardware_copy=true) -> bool
Creates a cube geometry.
Definition mesh.cpp:966
auto get_skinned_submeshes_indices(uint32_t data_group_id, uint32_t lod_index=0) const -> const submesh_array_indices_t &
Gets the indices of skinned submeshes for a specific data group.
Definition mesh.cpp:2658
auto weld_vertices(float tolerance=0.000001f, std::vector< uint32_t > *vertex_remap_ptr=nullptr) -> bool
Welds the vertices together that can be combined.
Definition mesh.cpp:3530
auto bind_armature(std::unique_ptr< armature_node > &root) -> bool
Binds the armature tree.
Definition mesh.cpp:816
auto get_armature() const -> const std::unique_ptr< armature_node > &
Retrieves the armature tree of the mesh.
Definition mesh.cpp:1642
bool force_barycentric_generation_
Whether to disable the automatic re-sort operation.
Definition mesh.h:1270
skin_bind_data skin_bind_data_
List of unique combinations of bones to use during rendering.
Definition mesh.h:1323
auto get_submeshes(uint32_t lod_index=0) const -> const submesh_array_t &
Retrieves information about the submesh of the mesh associated with the specified data group identifi...
Definition mesh.cpp:1864
auto create_cylinder(const gfx::vertex_layout &format, float radius, float height, uint32_t stacks, uint32_t slices, mesh_create_origin origin, bool hardware_copy=true) -> bool
Creates a cylinder geometry.
Definition mesh.cpp:1033
preparation_data preparation_data_
Data describing how the mesh should be bound as a skin with supplied bone matrices.
Definition mesh.h:1320
auto get_skin_bind_data() const -> const skin_bind_data &
Retrieves the skin bind data if this mesh has been bound as a skin.
Definition mesh.cpp:1632
std::vector< size_t > submesh_array_indices_t
Definition mesh.h:391
auto get_hardware_ib(uint32_t lod_index=0) const -> std::shared_ptr< gfx::index_buffer >
Retrieves the hardware index buffer for a given LOD.
Definition mesh.cpp:1617
auto prepare_mesh(const gfx::vertex_layout &vertex_format) -> bool
Prepares the mesh with the specified vertex format.
Definition mesh.cpp:270
auto set_bounding_box(const math::bbox &box) -> bool
Definition mesh.cpp:398
~mesh()
Destructor.
Definition mesh.cpp:163
auto get_submesh_node_transforms(uint32_t lod_index=0) const -> std::vector< math::transform >
Computes the accumulated node transform for each submesh.
Definition mesh.cpp:1672
static auto get_max_lod_count() -> uint32_t
Gets the maximum number of LODs that can be used for this mesh including base LOD.
Definition mesh.cpp:2008
mesh()
Constructs a mesh object.
Definition mesh.cpp:159
uint32_t * system_ib_
Material and data group information for each triangle.
Definition mesh.h:1279
auto get_submesh(uint32_t submesh_index=0, uint32_t lod_index=0) const -> const submesh *
Definition mesh.cpp:1896
auto calculate_screen_rect_precise(const math::transform &world, const camera &cam) const -> irect32_t
Calculates the screen rectangle of the mesh with precise near-plane clipping. This version properly c...
Definition mesh.cpp:1680
std::vector< bone_palette > bone_palette_array_t
Definition mesh.h:390
auto get_non_skinned_submeshes_indices(uint32_t data_group_id, uint32_t lod_index=0) const -> const submesh_array_indices_t &
Gets the indices of non-skinned submeshes for a specific data group.
Definition mesh.cpp:2701
std::vector< submesh * > submesh_array_t
Definition mesh.h:389
auto get_submesh_index(const submesh *s, uint32_t lod_index=0) const -> int
Gets the index of a submesh within the submesh array.
Definition mesh.cpp:1906
auto get_data_groups_count() const -> size_t
Gets the number of data groups(materials) for this mesh.
Definition mesh.cpp:2740
void build_ib(bool hardware_copy=true)
Builds the internal index buffer.
Definition mesh.cpp:1310
std::vector< triangle > triangle_array_t
Definition mesh.h:388
auto create_torus(const gfx::vertex_layout &format, float outer_radius, float inner_radius, uint32_t bands, uint32_t sides, mesh_create_origin origin, bool hardware_copy=true) -> bool
Creates a torus geometry.
Definition mesh.cpp:1101
mesh_status prepare_status_
Input data used for constructing the final mesh.
Definition mesh.h:1318
auto get_face_count() const -> uint32_t
Determines the number of faces stored in the mesh.
Definition mesh.cpp:1569
auto create_heightfield(const gfx::vertex_layout &format, hpp::span< const float > heights, uint32_t segments_x, uint32_t segments_z, float half_extent_x, float half_extent_z, float height_scale, mesh_create_origin origin, bool hardware_copy=true) -> bool
Creates a heightfield on the XZ plane (Y up): vertex grid (segments_x+1)*(segments_z+1).
Definition mesh.cpp:904
submesh_key_array_t triangle_data_
The actual hardware vertex buffer resource (shared by all LODs).
Definition mesh.h:1281
auto get_system_vb() -> uint8_t *
Retrieves the underlying vertex data from the mesh.
Definition mesh.cpp:1597
static auto get_max_generated_lod_count() -> uint32_t
Gets the maximum number of LODs that can be generated for a mesh.
Definition mesh.cpp:2013
uint32_t lod_count_
The actual list of submeshes maintained by this mesh.
Definition mesh.h:1290
Structure describing how a skinned mesh should be bound to any bones that influence its vertices.
Definition mesh.h:53
void build_vertex_table(uint32_t vertex_count, const std::vector< uint32_t > &vertex_remap, vertex_data_array_t &table)
Constructs a list of bone influences and weights for each vertex based on the binding data provided.
Definition mesh.cpp:3697
void remap_vertices(const std::vector< uint32_t > &remap)
Remaps the vertex references stored in the binding based on the supplied remap array.
Definition mesh.cpp:3666
auto get_bones() const -> const bone_influence_array_t &
Retrieves a list of all bones that influence the skin in some way.
Definition mesh.cpp:3746
std::vector< vertex_data > vertex_data_array_t
Definition mesh.h:100
auto find_bone_by_id(const std::string &id) const -> bone_query
Finds a bone by its unique identifier.
Definition mesh.cpp:3761
void clear()
Clears out the bone information stored in this object.
Definition mesh.cpp:3661
auto has_bones() const -> bool
Checks whether the skin data has any bones.
Definition mesh.cpp:3756
void remove_empty_bones()
Removes any bones that do not contain any influences.
Definition mesh.cpp:3636
std::vector< vertex_influence > vertex_influence_array_t
Definition mesh.h:65
void clear_vertex_influences()
Releases memory allocated for vertex influences in each stored bone.
Definition mesh.cpp:3653
void add_bone(const bone_influence &bone)
Adds influence information for a specific bone.
Definition mesh.cpp:3631
rect< std::int32_t > irect32_t
math::vec3 position
Definition defaults.cpp:52
math::vec3 normal
Definition defaults.cpp:53
uint16_t index
std::string name
Definition hub.cpp:33
#define APPLOG_WARNING(...)
Definition logging.h:19
#define APPLOG_ERROR(...)
Definition logging.h:20
#define APPLOG_INFO(...)
Definition logging.h:18
texture_job_type type
int count(const generator_t &generator) noexcept
Counts the number of steps left in the generator.
Definition utils.hpp:70
void alloc_transient_index_buffer(transient_index_buffer *_tib, uint32_t _num, bool _index32)
Definition graphics.cpp:582
void alloc_transient_vertex_buffer(transient_vertex_buffer *_tvb, uint32_t _num, const vertex_layout &_decl)
Definition graphics.cpp:587
void vertex_convert(const vertex_layout &_destDecl, void *_destData, const vertex_layout &_srcDecl, const void *_srcData, uint32_t _num)
Definition graphics.cpp:369
bgfx::VertexLayout vertex_layout
Definition vertex_decl.h:8
uint32_t get_avail_transient_index_buffer(uint32_t _num, bool _index32)
Definition graphics.cpp:567
bgfx::Attrib::Enum attribute
Definition vertex_decl.h:9
const memory_view * make_ref(const void *_data, uint32_t _size, release_fn _releaseFn, void *_userData)
Definition graphics.cpp:465
bgfx::TransientVertexBuffer transient_vertex_buffer
Definition graphics.h:56
void vertex_pack(const float _input[4], bool _inputNormalized, attribute _attr, const vertex_layout &_decl, void *_data, uint32_t _index)
Definition graphics.cpp:354
bgfx::TransientIndexBuffer transient_index_buffer
Definition graphics.h:57
void set_vertex_buffer(uint8_t _stream, vertex_buffer_handle _handle)
auto get_max_blend_transforms() -> uint32_t
void set_index_buffer(index_buffer_handle _handle)
Definition graphics.cpp:992
uint32_t get_avail_transient_vertex_buffer(uint32_t _num, const vertex_layout &_decl)
Definition graphics.cpp:572
bgfx::Memory memory_view
Definition graphics.h:23
void vertex_unpack(float _output[4], attribute _attr, const vertex_layout &_decl, const void *_data, uint32_t _index)
Definition graphics.cpp:364
Definition bbox.cpp:5
Hash specialization for batch_key to enable use in std::unordered_map.
mesh_status
Definition mesh.h:35
auto operator<(const mesh::adjacent_edge_key &key1, const mesh::adjacent_edge_key &key2) -> bool
Definition mesh.cpp:2779
@ sphere
Sphere type reflection probe.
@ box
Box type reflection probe.
mesh_create_origin
Definition mesh.h:42
std::vector< uint32_t > indices
Thread-safe handle to an asset.
Storage for box vector values and wraps up common functionality.
Definition bbox.h:21
bbox & add_point(const vec3 &point)
Grows the bounding box based on the point passed.
Definition bbox.cpp:924
void reset()
Resets the bounding box values.
Definition bbox.cpp:28
bbox & mul(const transform &t)
Transforms an axis aligned bounding box by the specified matrix.
Definition bbox.cpp:876
bool is_populated() const
Checks if the bounding box is populated.
Definition bbox.cpp:36
std::int32_t value_type
static auto context() -> rtti::context &
Definition engine.cpp:111
Contains level of detail (LOD) data for an entity per view. Uses distance-based hysteresis for stable...
Definition model.h:34
const math::vec3 * vertex2
Definition mesh.h:1164
const math::vec3 * vertex1
< Pointer to the first vertex in the edge.
Definition mesh.h:1162
bone_palette::bone_index_map_t bones
< List of unique bones that influence a given number of faces.
Definition mesh.h:1189
< Structure describing LOD level information. Total number of vertices.
Definition mesh.h:356
Struct used for mesh construction.
Definition mesh.h:451
Structure describing a LOD level with its own index buffer and submeshes.
Definition mesh.h:401
uint32_t face_count_
Submeshes for this LOD (face ranges may differ from base LOD)
Definition mesh.h:407
std::shared_ptr< void > hardware_ib_
Number of faces in this LOD.
Definition mesh.h:405
uint32_t * system_ib_
< System memory index buffer for this LOD
Definition mesh.h:403
submesh_array_t submeshes_
Cached skinned submesh indices per data group.
Definition mesh.h:409
submesh_array_map_t non_skinned_submesh_indices_
Error metric for this LOD (from meshoptimizer)
Definition mesh.h:413
submesh_array_map_t skinned_submesh_indices_
Cached non-skinned submesh indices per data group.
Definition mesh.h:411
float simplification_error_
Definition mesh.h:415
Structure describing a LOD level in load_data (for serialization).
Definition mesh.h:436
uint32_t data_group_id
< The data group identifier for this submesh.
Definition mesh.h:1170
bool owns_source
The format of the vertex data currently being used to prepare the mesh.
Definition mesh.h:1107
uint32_t vertex_count
Prepared substs information.
Definition mesh.h:1121
std::vector< uint32_t > vertex_records
Final vertex buffer currently being prepared.
Definition mesh.h:1111
uint32_t triangle_count
Total number of vertices currently stored.
Definition mesh.h:1119
bool compute_binormals
Whether to compute vertex tangents.
Definition mesh.h:1127
triangle_array_t triangle_data
Total number of triangles currently stored.
Definition mesh.h:1117
gfx::vertex_layout source_format
Records the location in the vertex buffer that each vertex has been placed during data insertion.
Definition mesh.h:1109
bool compute_normals
Whether to compute vertex binormals.
Definition mesh.h:1125
uint8_t * vertex_source
Whether the source data is owned by this object.
Definition mesh.h:1105
bool compute_tangents
Whether to compute vertex barycentric coordinates.
Definition mesh.h:1129
std::vector< submesh > submeshes
Whether to compute vertex normals.
Definition mesh.h:1123
byte_array_t vertex_data
Additional descriptive information about the vertices.
Definition mesh.h:1113
byte_array_t vertex_flags
Stores the current face/triangle data.
Definition mesh.h:1115
Structure describing an individual "piece" of the mesh, often grouped by material,...
Definition mesh.h:330
uint32_t vertex_count
The initial face, from the index buffer, to render in this batch.
Definition mesh.h:336
int32_t vertex_start
Number of vertices included in this batch.
Definition mesh.h:334
math::bbox bbox
Definition mesh.h:342
int32_t face_start
Number of faces to render in this batch.
Definition mesh.h:338
uint32_t face_count
Definition mesh.h:340
uint32_t data_group_id
< The unique user assigned "data group" that can be used to separate submeshes.
Definition mesh.h:332
Structure describing data for a single triangle in the mesh.
Definition mesh.h:379
std::array< uint32_t, 3 > indices
Flags for this triangle.
Definition mesh.h:383
uint32_t data_group_id
< Data group identifier for this triangle.
Definition mesh.h:381
Describes the vertices that are connected to the referenced bone and how much influence it has on the...
Definition mesh.h:71
Contains per-vertex influence and weight information.
Definition mesh.h:90
int32_t palette
The index of the original vertex.
Definition mesh.h:96
Describes how a bone influences a specific vertex.
Definition mesh.h:59