15auto get_bone_entity(
const std::string& bone_id,
const std::vector<entt::handle>& entities) -> entt::handle
17 for(
const auto& e : entities)
21 const auto&
tag =
e.get<tag_component>();
22 if(
tag.name == bone_id)
43using submesh_accum_list = std::vector<submesh_accum>;
55void rebuild_submesh_entries(submesh_component& comp,
56 const std::vector<uint32_t>& node_submeshes,
57 const mesh& render_mesh)
59 const std::vector<submesh_entry> previous_entries = std::move(comp.entries);
61 const auto& submeshes = render_mesh.get_submeshes(0);
64 comp.entries.reserve(node_submeshes.size());
65 for(uint32_t
index : node_submeshes)
69 entry.stable_id =
index < submeshes.size() && submeshes[
index] !=
nullptr ? submeshes[
index]->stable_id : 0;
73 const submesh_entry* match =
nullptr;
74 if(
entry.stable_id != 0)
76 for(
const auto& previous : previous_entries)
78 if(previous.stable_id ==
entry.stable_id)
87 for(
const auto& previous : previous_entries)
89 if(previous.stable_id == 0 && previous.submesh_index ==
index)
98 entry.material_override = match->material_override;
99 entry.casts_shadow = match->casts_shadow;
100 entry.enabled = match->enabled;
103 comp.entries.emplace_back(std::move(
entry));
107auto process_node_impl(
const std::unique_ptr<mesh::armature_node>& node,
108 const mesh& render_mesh,
109 entt::handle& parent,
110 std::vector<entt::handle>& nodes,
111 animation_pose& ref_pose,
112 submesh_accum_list& submesh_accums) -> entt::handle
114 const auto& bind_data = render_mesh.get_skin_bind_data();
115 auto entity_node = parent;
117 if(entity_node == parent)
119 auto& parent_trans_comp = parent.get<transform_component>();
120 const auto&
children = parent_trans_comp.get_children();
121 auto found_node = get_bone_entity(node->name,
children);
124 entity_node = found_node;
128 auto& reg = *entity_node.registry();
131 auto& trans_comp = entity_node.get<transform_component>();
132 trans_comp.set_transform_local(node->local_transform);
134 nodes.emplace_back(entity_node);
136 if(!node->submeshes.empty())
142 auto it = std::find_if(submesh_accums.begin(),
143 submesh_accums.end(),
144 [&](
const submesh_accum& accum) ->
bool
146 return accum.entity == entity_node;
148 if(it == submesh_accums.end())
150 submesh_accums.push_back({entity_node, node->submeshes});
154 it->indices.insert(it->indices.end(), node->submeshes.begin(), node->submeshes.end());
158 auto query = bind_data.find_bone_by_id(node->name);
159 if(query.bone && query.index >= 0)
161 auto& comp = entity_node.get_or_emplace<bone_component>();
162 comp.bone_index = query.index;
167 animation_pose::node ref_node;
168 ref_node.desc.index = node->index;
169 ref_node.transform = node->local_transform;
170 ref_pose.nodes.push_back(ref_node);
176void process_node(
const std::unique_ptr<mesh::armature_node>& node,
177 const mesh& render_mesh,
179 std::vector<entt::handle>& nodes,
180 animation_pose& ref_pose,
181 submesh_accum_list& submesh_accums)
188 auto entity_node = process_node_impl(node, render_mesh, parent, nodes, ref_pose, submesh_accums);
189 for(
auto& child : node->children)
191 process_node(child, render_mesh, entity_node, nodes, ref_pose, submesh_accums);
195auto process_armature(
const mesh& render_mesh,
197 std::vector<entt::handle>& nodes,
198 animation_pose& ref_pose) ->
bool
200 const auto& root = render_mesh.get_armature();
206 submesh_accum_list submesh_accums;
207 process_node(root, render_mesh, parent, nodes, ref_pose, submesh_accums);
212 for(
auto& accum : submesh_accums)
214 auto& comp = accum.entity.get_or_emplace<submesh_component>();
215 rebuild_submesh_entries(comp, accum.indices, render_mesh);
236auto get_transforms_for_entities(
const std::vector<entt::handle>& entities,
237 const mesh& render_mesh,
238 submesh_pose_mat4& submesh_pose,
239 pose_mat4& bone_pose,
240 submesh_render_proxies& proxies,
241 std::vector<material::sptr>& material_overrides,
245 thread_local std::vector<transform_component*> transform_scratch;
246 transform_scratch.clear();
247 transform_scratch.reserve(entities.size());
249 bool any_dirty =
force;
250 for(
const auto& e : entities)
252 auto* transform_comp =
e.try_get<transform_component>();
253 transform_scratch.push_back(transform_comp);
254 any_dirty |= transform_comp !=
nullptr && transform_comp->is_dirty(pose_dirty_id);
262 const size_t submesh_count = render_mesh.get_submeshes_count(0);
263 const size_t bone_count = render_mesh.get_skin_bind_data().get_bones().size();
264 const auto& submeshes = render_mesh.get_submeshes(0);
266 submesh_pose.clear();
267 submesh_pose.reserve(submesh_count);
268 bone_pose.transforms.resize(bone_count);
269 proxies.begin_refresh(submesh_count);
270 material_overrides.assign(submesh_count,
nullptr);
272 for(
size_t i = 0;
i < entities.size(); ++
i)
274 auto* transform_comp = transform_scratch[
i];
275 if(transform_comp ==
nullptr)
280 const auto e = entities[
i];
281 auto&& [submesh_comp, bone_comp, active_comp] =
282 e.try_get<submesh_component, bone_component, active_component>();
284 const auto& transform_global = transform_comp->get_transform_global();
285 const auto& transform_matrix = transform_global.get_matrix();
289 transform_comp->set_dirty(pose_dirty_id,
false);
291 if(submesh_comp && !submesh_comp->entries.empty())
293 const bool node_active = active_comp !=
nullptr;
297 const uint32_t trans_index = submesh_pose.add_transform(transform_matrix);
299 for(
const auto&
entry : submesh_comp->entries)
301 const uint32_t submesh_index =
entry.submesh_index;
302 submesh_pose.map_submesh(submesh_index,
304 node_active &&
entry.enabled,
318 const auto* sm = submesh_index < submeshes.size() ? submeshes[submesh_index] :
nullptr;
319 if(sm !=
nullptr && !sm->skinned && sm->bbox.is_populated())
323 proxies.add_instance_bounds(submesh_index, world_bounds);
325 if(submesh_index < material_overrides.size() &&
entry.material_override.is_valid())
327 material_overrides[submesh_index] =
entry.material_override.get();
334 auto bone_index = bone_comp->bone_index;
335 if(bone_index < bone_pose.transforms.size())
337 bone_pose.transforms[bone_index] = transform_matrix;
347auto model_component::create_armature(
bool force) ->
bool
349 bool has_processed_armature = !get_armature_entities().empty();
351 if(
force || !has_processed_armature)
353 auto lod = model_.get_lod(0);
358 auto mesh = lod.get();
360 auto owner = get_owner();
362 std::vector<entt::handle> armature_entities;
363 if(process_armature(*mesh,
owner, armature_entities, bind_pose_))
365 set_armature_entities(armature_entities);
367 const auto& skin_data = mesh->get_skin_bind_data();
369 if(skin_data.has_bones())
394 if(!pose_dirty_ && !was_used_last_frame() && culling_bounds_local_.is_populated())
398 render_proxies_stale_ =
true;
402 auto lod = model_.get_lod(0);
408 auto mesh = lod.get();
410 const auto& armature_entities = get_armature_entities();
417 const bool refreshed = get_transforms_for_entities(armature_entities,
422 submesh_material_overrides_,
428 render_proxies_stale_ =
false;
432 render_proxies_stale_ =
false;
435 if(skin_data.has_bones())
438 const size_t palette_count = palettes.size();
441 if(palette_count == 0)
446 skinning_pose_.resize(palette_count);
449 const auto& bone_transforms = bone_pose_.transforms;
450 const auto& bones = skin_data.get_bones();
459 for(
size_t i = 0; i < palette_count; ++i)
461 const auto& palette = palettes[i];
463 skinning_pose_[i].transforms = palette.get_skinning_matrices(bone_transforms, skin_data);
467 if(i < submeshes.size() && submeshes[i] !=
nullptr && !submeshes[i]->skinned)
473 for(uint32_t bone_index : palette.get_bones())
480 if(bone_index >= bones.size() || bone_index >= bone_transforms.size() ||
481 !bones[bone_index].bounds.is_populated())
486 const auto bone_world_bounds =
488 submesh_bounds.add_point(bone_world_bounds.min);
489 submesh_bounds.add_point(bone_world_bounds.max);
492 if(submesh_bounds.is_populated() && i < render_proxies_.skinned_bounds.size())
494 render_proxies_.skinned_bounds[i] = submesh_bounds;
495 render_proxies_.animated_bounds.add_point(submesh_bounds.min);
496 render_proxies_.animated_bounds.add_point(submesh_bounds.max);
506 auto lod = model_.get_lod(0);
512 auto mesh = lod.get();
516 bool recreate_armature =
force;
517 recreate_armature |= armature && submesh_pose_.submesh_to_transform_indices.empty();
518 recreate_armature |= skin_data.has_bones() && skinning_pose_.empty();
520 if(recreate_armature)
522 if(create_armature(
force))
524 return update_armature();
539 auto mesh = lod.get();
545 world_bounds_transform_ = world_transform;
556 anchor_transform = anchor_transform_comp->get_transform_global();
562 constexpr float min_scale = 0.000001f;
563 const auto scale = t.get_scale();
564 return std::abs(
scale.x) > min_scale && std::abs(
scale.y) > min_scale && std::abs(
scale.z) > min_scale;
575 if(render_proxies_.
version != captured_proxies_version_)
579 if(has_instance || has_animated)
604 if(is_invertible(world_transform) && is_invertible(anchor_transform))
609 culling_bounds_local_.
add_point(anchor_local.min);
610 culling_bounds_local_.
add_point(anchor_local.max);
612 captured_proxies_version_ = render_proxies_.
version;
626 if(render_proxies_stale_ && culling_bounds_local_.
is_populated())
628 world_bounds_ =
math::bbox::mul(culling_bounds_local_, anchor_transform);
647 return world_bounds_;
652 return world_bounds_transform_;
657 auto lod = model_.get_lod(lod_index);
663 auto mesh = lod.get();
674 last_render_frame_ =
frame;
679 return last_render_frame_;
684 return last_render_frame_ == 0;
691 bool was_used_recently = current_frame - last_render_frame_ <= 1;
692 return is_new || was_used_recently;
703 auto mesh = lod.get();
725 const auto mesh = lod.get();
732 for(
const auto&
entity : armature_entities_)
740 if(!armature_entities_.empty())
742 return armature_entities_.front();
755 if(!bind_pose_.
nodes.empty())
757 return bind_pose_.
nodes.front().transform.get_rotation();
760 return math::identity<math::quat>();
765 entt::handle
entity(r, e);
770 component.set_armature_entities({});
791 if(casts_shadow_ == cast_shadow)
798 casts_shadow_ = cast_shadow;
820 return casts_shadow_;
839 pose_local_bounds_ = {};
840 culling_bounds_local_ = {};
841 captured_proxies_version_ = ~0ULL;
854 return skinning_pose_;
859 return submesh_pose_;
864 return render_proxies_;
869 return submesh_material_overrides_;
879 extras.
proxies = render_proxies_stale_ ? nullptr : &render_proxies_;
887 armature_entities_ = entities;
888 rebuild_armature_cache();
894 for(
const auto& e : armature_entities_)
904 pose_local_bounds_ = {};
905 culling_bounds_local_ = {};
906 captured_proxies_version_ = ~0ULL;
917void model_component::rebuild_armature_cache()
919 armature_name_to_index_.clear();
920 armature_name_to_index_.reserve(armature_entities_.size());
922 for(
size_t i = 0; i < armature_entities_.size(); ++i)
924 const auto& e = armature_entities_[i];
928 armature_name_to_index_[tag_comp.name] = i;
935 auto it = armature_name_to_index_.find(node_name);
936 if(it != armature_name_to_index_.end())
938 return static_cast<int>(it->second);
945 return armature_entities_;
950 if(
index >= armature_entities_.size())
955 return armature_entities_[
index];
962 static thread_local lod_data empty_lod_data;
963 return empty_lod_data;
965 const auto unique_id =
reinterpret_cast<uintptr_t
>(cam);
967 auto& camera_state = per_camera_lod_data_[unique_id];
970 camera_state.last_access_frame = current_frame;
972 return camera_state.data;
978 for(
auto it = per_camera_lod_data_.begin(); it != per_camera_lod_data_.end();)
980 const auto frames_since_access = current_frame - it->second.last_access_frame;
981 if(frames_since_access > max_frames_inactive)
983 it = per_camera_lod_data_.erase(it);
Class representing a camera. Contains functionality for manipulating and updating a camera....
Base class for materials used in rendering.
Main class representing a 3D mesh with support for different LODs, submeshes, and skinning.
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.
auto get_bounds() const -> const math::bbox &
Gets the local bounding box for this mesh.
auto get_submeshes_count(uint32_t lod_index=0) const -> size_t
Gets the number of submeshes for this mesh.
auto get_skinned_submeshes_count(uint32_t lod_index=0) const -> size_t
Gets the number of skinned submeshes for this mesh.
auto get_armature() const -> const std::unique_ptr< armature_node > &
Retrieves the armature tree of the mesh.
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...
auto get_skin_bind_data() const -> const skin_bind_data &
Retrieves the skin bind data if this mesh has been bound as a skin.
Class that contains core data for meshes.
static void on_create_component(entt::registry &r, entt::entity e)
Called when the component is created.
auto get_facing_adjustment_rotation() const -> math::quat
Local rotation of the armature root (used for root motion / IK remapping).
auto was_used_last_frame() const noexcept -> bool
auto get_submesh_material_overrides() const -> const std::vector< material::sptr > &
Per-submesh material overrides (indexed by submesh index; null = model material), resolved from the s...
auto get_armature_by_index(size_t index) const -> entt::handle
auto get_last_render_frame() const noexcept -> uint64_t
auto get_submesh_transforms() const -> const submesh_pose_mat4 &
Gets the submesh transforms.
auto get_bone_transforms() const -> const pose_mat4 &
Gets the bone transforms.
void set_enabled(bool enabled)
Sets whether the model is enabled.
auto get_submit_extras(bool shadow_pass=false) const -> model_submit_extras
Convenience: builds the submit extras referencing the retained proxy data.
auto is_static() const -> bool
Checks if the model is static.
auto get_lod_data_for_camera(const camera *cam, uint64_t current_frame) -> lod_data &
Gets the per-view LOD data for a specific camera/view. Creates a new entry if this is the first acces...
auto is_newly_created() const noexcept -> bool
auto get_bind_pose() const -> const animation_pose &
auto casts_shadow() const -> bool
Checks if the model casts shadows.
auto init_armature(bool force) -> bool
Updates the armature of the model.
static void on_destroy_component(entt::registry &r, entt::entity e)
Called when the component is destroyed.
auto get_model() const -> const model &
Gets the model.
void cleanup_stale_lod_data(uint64_t current_frame, uint64_t max_frames_inactive=120)
Cleans up stale per-view LOD data entries that haven't been accessed recently. Call this periodically...
auto get_armature_index_by_name_cached(const std::string &node_name) const -> int
Gets armature index by name using cached lookup (O(1)).
auto is_enabled() const -> bool
Checks if the model is enabled.
void mark_pose_dirty() noexcept
Forces the next update_armature call to run a full refresh even when no armature transform is dirty (...
auto get_render_proxies() const -> const submesh_render_proxies &
Retained render proxies: cached world-space per-submesh bounds (including animated bounds for skinned...
auto is_skinned() const -> bool
auto get_skinning_transforms() const -> const std::vector< pose_mat4 > &
void set_model(const model &model)
Sets the model.
auto get_world_bounds_transform() const -> const math::transform &
auto get_local_bounds(uint32_t lod_index) const -> const math::bbox &
Gets the bind-pose local bounding box of the mesh asset for a given LOD.
auto get_armature_root_entity() const -> entt::handle
Armature root entity (first node of the imported skeleton hierarchy).
auto get_armature_entities() const -> const std::vector< entt::handle > &
Gets the armature entities.
void set_static(bool is_static)
Sets whether the model is static.
auto get_world_bounds() const -> const math::bbox &
Gets the pose-aware world-space bounding box for this model.
void update_world_bounds(const math::transform &world_transform)
void set_armature_entities(const std::vector< entt::handle > &submesh_entities)
Sets the armature entities.
auto update_armature() -> bool
Refreshes pose-derived render data: submesh/bone poses, cached world-space render-proxy bounds,...
void set_last_render_frame(uint64_t frame)
void set_casts_shadow(bool cast_shadow)
Sets whether the model casts shadows.
Structure describing a LOD group (set of meshes), LOD transitions, and their materials.
auto get_lod(uint32_t lod) const -> asset_handle< mesh >
Gets the LOD (Level of Detail) mesh for the specified level.
void set_owner(entt::handle owner)
Sets the owner of the component.
uint32_t get_render_frame()
auto inverse(transform_t< T, Q > const &t) noexcept -> transform_t< T, Q >
Hash specialization for batch_key to enable use in std::unordered_map.
std::vector< float > scale
std::vector< uint32_t > indices
Storage for box vector values and wraps up common functionality.
bbox & add_point(const vec3 &point)
Grows the bounding box based on the point passed.
vec3 max
The maximum vector value of the bounding box.
bbox & mul(const transform &t)
Transforms an axis aligned bounding box by the specified matrix.
vec3 min
The minimum vector value of the bounding box.
static bbox empty
An empty bounding box.
bool is_populated() const
Checks if the bounding box is populated.
std::vector< node > nodes
void touch()
Marks the component as 'touched'.
Contains level of detail (LOD) data for an entity per view. Uses distance-based hysteresis for stable...
auto create_entity(const std::string &tag={}, entt::handle parent={}) -> entt::handle
Creates an entity in the scene with an optional tag and parent.
Retained per-model render proxy data used by the culling/LOD code.
auto has_instance_bounds() const -> bool
auto has_animated_bounds() const -> bool
math::bbox instance_bounds_union
uint64_t version
Increments on every refresh so consumers can detect staleness.
math::bbox animated_bounds
Component that provides a tag (name or label) for an entity.
std::string name
The name of the entity.