From 06514855ed23f6d9f2a62c6f3cbfc7efc842650e Mon Sep 17 00:00:00 2001 From: Gopmyc Date: Thu, 9 Apr 2026 02:25:42 +0200 Subject: [PATCH 1/6] Added skinned bounds scale settings to model renderer --- .../OvCore/ECS/Components/CModelRenderer.h | 14 +++++++++++++- .../OvCore/ECS/Components/CModelRenderer.cpp | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/Sources/OvCore/include/OvCore/ECS/Components/CModelRenderer.h b/Sources/OvCore/include/OvCore/ECS/Components/CModelRenderer.h index 66af257f..11d22bfb 100644 --- a/Sources/OvCore/include/OvCore/ECS/Components/CModelRenderer.h +++ b/Sources/OvCore/include/OvCore/ECS/Components/CModelRenderer.h @@ -81,6 +81,17 @@ namespace OvCore::ECS::Components */ void SetCustomBoundingSphere(const OvRendering::Geometry::BoundingSphere& p_boundingSphere); + /** + * Returns the scale applied to skinned mesh bounds for frustum culling + */ + float GetSkinningBoundsScale() const; + + /** + * Sets the scale applied to skinned mesh bounds for frustum culling + * @param p_scale + */ + void SetSkinningBoundsScale(float p_scale); + /** * Serialize the component * @param p_doc @@ -106,6 +117,7 @@ namespace OvCore::ECS::Components OvTools::Eventing::Event<> m_modelChangedEvent; OvRendering::Geometry::BoundingSphere m_customBoundingSphere = { {}, 1.0f }; EFrustumBehaviour m_frustumBehaviour = EFrustumBehaviour::MESH_BOUNDS; + float m_skinningBoundsScale = 1.5f; }; template<> @@ -113,4 +125,4 @@ namespace OvCore::ECS::Components { static constexpr std::string_view Name = "class OvCore::ECS::Components::CModelRenderer"; }; -} \ No newline at end of file +} diff --git a/Sources/OvCore/src/OvCore/ECS/Components/CModelRenderer.cpp b/Sources/OvCore/src/OvCore/ECS/Components/CModelRenderer.cpp index 22c38c6f..167ba1f7 100644 --- a/Sources/OvCore/src/OvCore/ECS/Components/CModelRenderer.cpp +++ b/Sources/OvCore/src/OvCore/ECS/Components/CModelRenderer.cpp @@ -4,6 +4,8 @@ * @licence: MIT */ +#include + #include #include #include @@ -74,12 +76,23 @@ void OvCore::ECS::Components::CModelRenderer::SetCustomBoundingSphere(const OvRe m_customBoundingSphere = p_boundingSphere; } +float OvCore::ECS::Components::CModelRenderer::GetSkinningBoundsScale() const +{ + return m_skinningBoundsScale; +} + +void OvCore::ECS::Components::CModelRenderer::SetSkinningBoundsScale(float p_scale) +{ + m_skinningBoundsScale = std::max(1.0f, p_scale); +} + void OvCore::ECS::Components::CModelRenderer::OnSerialize(tinyxml2::XMLDocument & p_doc, tinyxml2::XMLNode * p_node) { OvCore::Helpers::Serializer::SerializeModel(p_doc, p_node, "model", m_model); OvCore::Helpers::Serializer::SerializeInt(p_doc, p_node, "frustum_behaviour", reinterpret_cast(m_frustumBehaviour)); OvCore::Helpers::Serializer::SerializeVec3(p_doc, p_node, "custom_bounding_sphere_position", m_customBoundingSphere.position); OvCore::Helpers::Serializer::SerializeFloat(p_doc, p_node, "custom_bounding_sphere_radius", m_customBoundingSphere.radius); + OvCore::Helpers::Serializer::SerializeFloat(p_doc, p_node, "skinning_bounds_scale", m_skinningBoundsScale); } void OvCore::ECS::Components::CModelRenderer::OnDeserialize(tinyxml2::XMLDocument & p_doc, tinyxml2::XMLNode* p_node) @@ -90,6 +103,8 @@ void OvCore::ECS::Components::CModelRenderer::OnDeserialize(tinyxml2::XMLDocumen OvCore::Helpers::Serializer::DeserializeInt(p_doc, p_node, "frustum_behaviour", reinterpret_cast(m_frustumBehaviour)); OvCore::Helpers::Serializer::DeserializeVec3(p_doc, p_node, "custom_bounding_sphere_position", m_customBoundingSphere.position); OvCore::Helpers::Serializer::DeserializeFloat(p_doc, p_node, "custom_bounding_sphere_radius", m_customBoundingSphere.radius); + OvCore::Helpers::Serializer::DeserializeFloat(p_doc, p_node, "skinning_bounds_scale", m_skinningBoundsScale); + SetSkinningBoundsScale(m_skinningBoundsScale); } void OvCore::ECS::Components::CModelRenderer::OnInspector(OvUI::Internal::WidgetContainer& p_root) @@ -107,6 +122,9 @@ void OvCore::ECS::Components::CModelRenderer::OnInspector(OvUI::Internal::Widget auto& boundingModeDispatcher = boundingMode.AddPlugin>(); boundingModeDispatcher.RegisterReference(reinterpret_cast(m_frustumBehaviour)); + GUIDrawer::DrawScalar(p_root, "Skinned Bounds Scale", m_skinningBoundsScale, 0.05f, 1.0f, 10.0f); + SetSkinningBoundsScale(m_skinningBoundsScale); + auto& centerLabel = p_root.CreateWidget("Bounding Sphere Center", GUIDrawer::TitleColor); auto& centerWidget = p_root.CreateWidget>(GUIDrawer::GetDataType(), GUIDrawer::_MIN_FLOAT, GUIDrawer::_MAX_FLOAT, 0.f, 0.05f, "", GUIDrawer::GetFormat()); auto& centerDispatcher = centerWidget.AddPlugin>>(); From e978023a69edbc355f25919e101e7c8200ff0c34 Mon Sep 17 00:00:00 2001 From: Gopmyc Date: Thu, 9 Apr 2026 02:25:45 +0200 Subject: [PATCH 2/6] Implemented frustum culling support for skinned meshes (#654) --- .../include/OvCore/Rendering/SceneRenderer.h | 1 + .../src/OvCore/Rendering/SceneRenderer.cpp | 20 +++++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/Sources/OvCore/include/OvCore/Rendering/SceneRenderer.h b/Sources/OvCore/include/OvCore/Rendering/SceneRenderer.h index 596c3a5f..663a0315 100644 --- a/Sources/OvCore/include/OvCore/Rendering/SceneRenderer.h +++ b/Sources/OvCore/include/OvCore/Rendering/SceneRenderer.h @@ -101,6 +101,7 @@ namespace OvCore::Rendering OvCore::ECS::Actor& actor; EVisibilityFlags visibilityFlags = EVisibilityFlags::NONE; std::optional bounds; + float skinningBoundsScale = 1.0f; }; /** diff --git a/Sources/OvCore/src/OvCore/Rendering/SceneRenderer.cpp b/Sources/OvCore/src/OvCore/Rendering/SceneRenderer.cpp index 349cf475..716741e8 100644 --- a/Sources/OvCore/src/OvCore/Rendering/SceneRenderer.cpp +++ b/Sources/OvCore/src/OvCore/Rendering/SceneRenderer.cpp @@ -280,9 +280,14 @@ SceneRenderer::SceneDrawablesDescriptor OvCore::Rendering::SceneRenderer::ParseS if (!materialRenderer) continue; const auto* skinnedRenderer = owner.GetComponent(); const bool hasSkinning = SkinningUtils::IsSkinningActive(skinnedRenderer); - + const auto frustumBehaviour = modelRenderer->GetFrustumBehaviour(); + const bool useComputedBounds = + frustumBehaviour == CModelRenderer::EFrustumBehaviour::MESH_BOUNDS || + frustumBehaviour == CModelRenderer::EFrustumBehaviour::DEPRECATED_MODEL_BOUNDS; + const auto& transform = owner.transform.GetFTransform(); const auto& materials = materialRenderer->GetMaterials(); + const float skinningBoundsScale = hasSkinning && useComputedBounds ? modelRenderer->GetSkinningBoundsScale() : 1.0f; for (auto& mesh : model->GetMeshes()) { @@ -301,7 +306,7 @@ SceneRenderer::SceneDrawablesDescriptor OvCore::Rendering::SceneRenderer::ParseS auto bounds = [&]() -> std::optional { using enum CModelRenderer::EFrustumBehaviour; - switch (modelRenderer->GetFrustumBehaviour()) + switch (frustumBehaviour) { case MESH_BOUNDS: return mesh->GetBoundingSphere(); case DEPRECATED_MODEL_BOUNDS: return model->GetBoundingSphere(); @@ -315,6 +320,7 @@ SceneRenderer::SceneDrawablesDescriptor OvCore::Rendering::SceneRenderer::ParseS .actor = modelRenderer->owner, .visibilityFlags = materialRenderer->GetVisibilityFlags(), .bounds = bounds, + .skinningBoundsScale = skinningBoundsScale }); drawable.AddDescriptor({ @@ -386,11 +392,17 @@ SceneRenderer::SceneFilteredDrawablesDescriptor OvCore::Rendering::SceneRenderer } // Perform frustum culling if enabled - if (frustum && desc.bounds.has_value() && !hasSkinningDescriptor) + if (frustum && desc.bounds.has_value()) { ZoneScopedN("Frustum Culling"); - if (!frustum->BoundingSphereInFrustum(desc.bounds.value(), desc.actor.transform.GetFTransform())) + auto cullingBounds = desc.bounds.value(); + if (hasSkinningDescriptor) + { + cullingBounds.radius *= desc.skinningBoundsScale; + } + + if (!frustum->BoundingSphereInFrustum(cullingBounds, desc.actor.transform.GetFTransform())) { continue; // Skip this drawable as it's outside the frustum } From 19f0c449ada5d147d703b25d74148b8e55ff4d8d Mon Sep 17 00:00:00 2001 From: Gopmyc Date: Thu, 9 Apr 2026 04:06:05 +0200 Subject: [PATCH 3/6] Refactored skinned bounds scaling to CSkinnedMeshRenderer (#654) --- .../OvCore/ECS/Components/CModelRenderer.h | 12 ------------ .../ECS/Components/CSkinnedMeshRenderer.h | 12 ++++++++++++ .../include/OvCore/Rendering/SceneRenderer.h | 1 - .../Rendering/SkinningDrawableDescriptor.h | 1 + .../OvCore/ECS/Components/CModelRenderer.cpp | 18 ------------------ .../ECS/Components/CSkinnedMeshRenderer.cpp | 15 +++++++++++++++ .../src/OvCore/Rendering/SceneRenderer.cpp | 15 +++++---------- .../src/OvCore/Rendering/SkinningUtils.cpp | 3 ++- 8 files changed, 35 insertions(+), 42 deletions(-) diff --git a/Sources/OvCore/include/OvCore/ECS/Components/CModelRenderer.h b/Sources/OvCore/include/OvCore/ECS/Components/CModelRenderer.h index 11d22bfb..07fefa2d 100644 --- a/Sources/OvCore/include/OvCore/ECS/Components/CModelRenderer.h +++ b/Sources/OvCore/include/OvCore/ECS/Components/CModelRenderer.h @@ -81,17 +81,6 @@ namespace OvCore::ECS::Components */ void SetCustomBoundingSphere(const OvRendering::Geometry::BoundingSphere& p_boundingSphere); - /** - * Returns the scale applied to skinned mesh bounds for frustum culling - */ - float GetSkinningBoundsScale() const; - - /** - * Sets the scale applied to skinned mesh bounds for frustum culling - * @param p_scale - */ - void SetSkinningBoundsScale(float p_scale); - /** * Serialize the component * @param p_doc @@ -117,7 +106,6 @@ namespace OvCore::ECS::Components OvTools::Eventing::Event<> m_modelChangedEvent; OvRendering::Geometry::BoundingSphere m_customBoundingSphere = { {}, 1.0f }; EFrustumBehaviour m_frustumBehaviour = EFrustumBehaviour::MESH_BOUNDS; - float m_skinningBoundsScale = 1.5f; }; template<> diff --git a/Sources/OvCore/include/OvCore/ECS/Components/CSkinnedMeshRenderer.h b/Sources/OvCore/include/OvCore/ECS/Components/CSkinnedMeshRenderer.h index b009b815..538b4df2 100644 --- a/Sources/OvCore/include/OvCore/ECS/Components/CSkinnedMeshRenderer.h +++ b/Sources/OvCore/include/OvCore/ECS/Components/CSkinnedMeshRenderer.h @@ -93,6 +93,17 @@ namespace OvCore::ECS::Components */ float GetPlaybackSpeed() const; + /** + * Returns the scale applied to skinned bounds during frustum culling + */ + float GetSkinningBoundsScale() const; + + /** + * Sets the scale applied to skinned bounds during frustum culling + * @param p_scale + */ + void SetSkinningBoundsScale(float p_scale); + /** * Sets the current playback time in seconds * @param p_timeSeconds @@ -187,6 +198,7 @@ namespace OvCore::ECS::Components bool m_playing = true; bool m_looping = true; float m_playbackSpeed = 1.0f; + float m_skinningBoundsScale = 1.5f; float m_poseEvaluationRate = 60.0f; float m_poseEvaluationAccumulator = 0.0f; diff --git a/Sources/OvCore/include/OvCore/Rendering/SceneRenderer.h b/Sources/OvCore/include/OvCore/Rendering/SceneRenderer.h index 663a0315..596c3a5f 100644 --- a/Sources/OvCore/include/OvCore/Rendering/SceneRenderer.h +++ b/Sources/OvCore/include/OvCore/Rendering/SceneRenderer.h @@ -101,7 +101,6 @@ namespace OvCore::Rendering OvCore::ECS::Actor& actor; EVisibilityFlags visibilityFlags = EVisibilityFlags::NONE; std::optional bounds; - float skinningBoundsScale = 1.0f; }; /** diff --git a/Sources/OvCore/include/OvCore/Rendering/SkinningDrawableDescriptor.h b/Sources/OvCore/include/OvCore/Rendering/SkinningDrawableDescriptor.h index d34c139a..b90b433b 100644 --- a/Sources/OvCore/include/OvCore/Rendering/SkinningDrawableDescriptor.h +++ b/Sources/OvCore/include/OvCore/Rendering/SkinningDrawableDescriptor.h @@ -20,5 +20,6 @@ namespace OvCore::Rendering const OvMaths::FMatrix4* matrices = nullptr; uint32_t count = 0; uint64_t poseVersion = 0; + float boundsScale = 1.0f; }; } diff --git a/Sources/OvCore/src/OvCore/ECS/Components/CModelRenderer.cpp b/Sources/OvCore/src/OvCore/ECS/Components/CModelRenderer.cpp index 167ba1f7..22c38c6f 100644 --- a/Sources/OvCore/src/OvCore/ECS/Components/CModelRenderer.cpp +++ b/Sources/OvCore/src/OvCore/ECS/Components/CModelRenderer.cpp @@ -4,8 +4,6 @@ * @licence: MIT */ -#include - #include #include #include @@ -76,23 +74,12 @@ void OvCore::ECS::Components::CModelRenderer::SetCustomBoundingSphere(const OvRe m_customBoundingSphere = p_boundingSphere; } -float OvCore::ECS::Components::CModelRenderer::GetSkinningBoundsScale() const -{ - return m_skinningBoundsScale; -} - -void OvCore::ECS::Components::CModelRenderer::SetSkinningBoundsScale(float p_scale) -{ - m_skinningBoundsScale = std::max(1.0f, p_scale); -} - void OvCore::ECS::Components::CModelRenderer::OnSerialize(tinyxml2::XMLDocument & p_doc, tinyxml2::XMLNode * p_node) { OvCore::Helpers::Serializer::SerializeModel(p_doc, p_node, "model", m_model); OvCore::Helpers::Serializer::SerializeInt(p_doc, p_node, "frustum_behaviour", reinterpret_cast(m_frustumBehaviour)); OvCore::Helpers::Serializer::SerializeVec3(p_doc, p_node, "custom_bounding_sphere_position", m_customBoundingSphere.position); OvCore::Helpers::Serializer::SerializeFloat(p_doc, p_node, "custom_bounding_sphere_radius", m_customBoundingSphere.radius); - OvCore::Helpers::Serializer::SerializeFloat(p_doc, p_node, "skinning_bounds_scale", m_skinningBoundsScale); } void OvCore::ECS::Components::CModelRenderer::OnDeserialize(tinyxml2::XMLDocument & p_doc, tinyxml2::XMLNode* p_node) @@ -103,8 +90,6 @@ void OvCore::ECS::Components::CModelRenderer::OnDeserialize(tinyxml2::XMLDocumen OvCore::Helpers::Serializer::DeserializeInt(p_doc, p_node, "frustum_behaviour", reinterpret_cast(m_frustumBehaviour)); OvCore::Helpers::Serializer::DeserializeVec3(p_doc, p_node, "custom_bounding_sphere_position", m_customBoundingSphere.position); OvCore::Helpers::Serializer::DeserializeFloat(p_doc, p_node, "custom_bounding_sphere_radius", m_customBoundingSphere.radius); - OvCore::Helpers::Serializer::DeserializeFloat(p_doc, p_node, "skinning_bounds_scale", m_skinningBoundsScale); - SetSkinningBoundsScale(m_skinningBoundsScale); } void OvCore::ECS::Components::CModelRenderer::OnInspector(OvUI::Internal::WidgetContainer& p_root) @@ -122,9 +107,6 @@ void OvCore::ECS::Components::CModelRenderer::OnInspector(OvUI::Internal::Widget auto& boundingModeDispatcher = boundingMode.AddPlugin>(); boundingModeDispatcher.RegisterReference(reinterpret_cast(m_frustumBehaviour)); - GUIDrawer::DrawScalar(p_root, "Skinned Bounds Scale", m_skinningBoundsScale, 0.05f, 1.0f, 10.0f); - SetSkinningBoundsScale(m_skinningBoundsScale); - auto& centerLabel = p_root.CreateWidget("Bounding Sphere Center", GUIDrawer::TitleColor); auto& centerWidget = p_root.CreateWidget>(GUIDrawer::GetDataType(), GUIDrawer::_MIN_FLOAT, GUIDrawer::_MAX_FLOAT, 0.f, 0.05f, "", GUIDrawer::GetFormat()); auto& centerDispatcher = centerWidget.AddPlugin>>(); diff --git a/Sources/OvCore/src/OvCore/ECS/Components/CSkinnedMeshRenderer.cpp b/Sources/OvCore/src/OvCore/ECS/Components/CSkinnedMeshRenderer.cpp index 055a19d5..c09502a3 100644 --- a/Sources/OvCore/src/OvCore/ECS/Components/CSkinnedMeshRenderer.cpp +++ b/Sources/OvCore/src/OvCore/ECS/Components/CSkinnedMeshRenderer.cpp @@ -174,6 +174,16 @@ float OvCore::ECS::Components::CSkinnedMeshRenderer::GetPlaybackSpeed() const return m_playbackSpeed; } +float OvCore::ECS::Components::CSkinnedMeshRenderer::GetSkinningBoundsScale() const +{ + return m_skinningBoundsScale; +} + +void OvCore::ECS::Components::CSkinnedMeshRenderer::SetSkinningBoundsScale(float p_scale) +{ + m_skinningBoundsScale = std::max(1.0f, p_scale); +} + void OvCore::ECS::Components::CSkinnedMeshRenderer::SetTime(float p_timeSeconds) { if (!HasCompatibleModel() || !m_animationIndex.has_value()) @@ -347,6 +357,7 @@ void OvCore::ECS::Components::CSkinnedMeshRenderer::OnSerialize(tinyxml2::XMLDoc OvCore::Helpers::Serializer::SerializeBoolean(p_doc, p_node, "playing", m_playing); OvCore::Helpers::Serializer::SerializeBoolean(p_doc, p_node, "looping", m_looping); OvCore::Helpers::Serializer::SerializeFloat(p_doc, p_node, "playback_speed", m_playbackSpeed); + OvCore::Helpers::Serializer::SerializeFloat(p_doc, p_node, "skinning_bounds_scale", m_skinningBoundsScale); OvCore::Helpers::Serializer::SerializeFloat(p_doc, p_node, "pose_eval_rate", m_poseEvaluationRate); OvCore::Helpers::Serializer::SerializeFloat(p_doc, p_node, "time_ticks", m_currentTimeTicks); OvCore::Helpers::Serializer::SerializeString(p_doc, p_node, "animation", GetActiveAnimationName().value_or(std::string{})); @@ -357,9 +368,11 @@ void OvCore::ECS::Components::CSkinnedMeshRenderer::OnDeserialize(tinyxml2::XMLD OvCore::Helpers::Serializer::DeserializeBoolean(p_doc, p_node, "playing", m_playing); OvCore::Helpers::Serializer::DeserializeBoolean(p_doc, p_node, "looping", m_looping); OvCore::Helpers::Serializer::DeserializeFloat(p_doc, p_node, "playback_speed", m_playbackSpeed); + OvCore::Helpers::Serializer::DeserializeFloat(p_doc, p_node, "skinning_bounds_scale", m_skinningBoundsScale); OvCore::Helpers::Serializer::DeserializeFloat(p_doc, p_node, "pose_eval_rate", m_poseEvaluationRate); OvCore::Helpers::Serializer::DeserializeFloat(p_doc, p_node, "time_ticks", m_currentTimeTicks); OvCore::Helpers::Serializer::DeserializeString(p_doc, p_node, "animation", m_deserializedAnimationName); + SetSkinningBoundsScale(m_skinningBoundsScale); m_poseEvaluationRate = std::max(0.0f, m_poseEvaluationRate); m_poseEvaluationAccumulator = 0.0f; @@ -375,6 +388,8 @@ void OvCore::ECS::Components::CSkinnedMeshRenderer::OnInspector(OvUI::Internal:: GUIDrawer::DrawBoolean(p_root, "Playing", m_playing); GUIDrawer::DrawBoolean(p_root, "Looping", m_looping); GUIDrawer::DrawScalar(p_root, "Playback Speed", m_playbackSpeed, 0.01f, -10.0f, 10.0f); + GUIDrawer::DrawScalar(p_root, "Skinned Bounds Scale", m_skinningBoundsScale, 0.05f, 1.0f, 10.0f); + SetSkinningBoundsScale(m_skinningBoundsScale); GUIDrawer::DrawScalar(p_root, "Pose Eval Rate", m_poseEvaluationRate, 1.0f, 0.0f, 240.0f); m_poseEvaluationRate = std::max(0.0f, m_poseEvaluationRate); GUIDrawer::DrawScalar( diff --git a/Sources/OvCore/src/OvCore/Rendering/SceneRenderer.cpp b/Sources/OvCore/src/OvCore/Rendering/SceneRenderer.cpp index 716741e8..982ab65d 100644 --- a/Sources/OvCore/src/OvCore/Rendering/SceneRenderer.cpp +++ b/Sources/OvCore/src/OvCore/Rendering/SceneRenderer.cpp @@ -280,14 +280,9 @@ SceneRenderer::SceneDrawablesDescriptor OvCore::Rendering::SceneRenderer::ParseS if (!materialRenderer) continue; const auto* skinnedRenderer = owner.GetComponent(); const bool hasSkinning = SkinningUtils::IsSkinningActive(skinnedRenderer); - const auto frustumBehaviour = modelRenderer->GetFrustumBehaviour(); - const bool useComputedBounds = - frustumBehaviour == CModelRenderer::EFrustumBehaviour::MESH_BOUNDS || - frustumBehaviour == CModelRenderer::EFrustumBehaviour::DEPRECATED_MODEL_BOUNDS; const auto& transform = owner.transform.GetFTransform(); const auto& materials = materialRenderer->GetMaterials(); - const float skinningBoundsScale = hasSkinning && useComputedBounds ? modelRenderer->GetSkinningBoundsScale() : 1.0f; for (auto& mesh : model->GetMeshes()) { @@ -306,7 +301,7 @@ SceneRenderer::SceneDrawablesDescriptor OvCore::Rendering::SceneRenderer::ParseS auto bounds = [&]() -> std::optional { using enum CModelRenderer::EFrustumBehaviour; - switch (frustumBehaviour) + switch (modelRenderer->GetFrustumBehaviour()) { case MESH_BOUNDS: return mesh->GetBoundingSphere(); case DEPRECATED_MODEL_BOUNDS: return model->GetBoundingSphere(); @@ -319,8 +314,7 @@ SceneRenderer::SceneDrawablesDescriptor OvCore::Rendering::SceneRenderer::ParseS drawable.AddDescriptor({ .actor = modelRenderer->owner, .visibilityFlags = materialRenderer->GetVisibilityFlags(), - .bounds = bounds, - .skinningBoundsScale = skinningBoundsScale + .bounds = bounds }); drawable.AddDescriptor({ @@ -365,7 +359,8 @@ SceneRenderer::SceneFilteredDrawablesDescriptor OvCore::Rendering::SceneRenderer for (const auto& drawable : p_drawables.drawables) { const auto& desc = drawable.GetDescriptor(); - const bool hasSkinningDescriptor = drawable.HasDescriptor(); + OvTools::Utils::OptRef skinningDescriptor; + const bool hasSkinningDescriptor = drawable.TryGetDescriptor(skinningDescriptor); // Skip drawables that do not satisfy the required visibility flags if (!SatisfiesVisibility(desc.visibilityFlags, p_filteringInput.requiredVisibilityFlags)) @@ -399,7 +394,7 @@ SceneRenderer::SceneFilteredDrawablesDescriptor OvCore::Rendering::SceneRenderer auto cullingBounds = desc.bounds.value(); if (hasSkinningDescriptor) { - cullingBounds.radius *= desc.skinningBoundsScale; + cullingBounds.radius *= skinningDescriptor->boundsScale; } if (!frustum->BoundingSphereInFrustum(cullingBounds, desc.actor.transform.GetFTransform())) diff --git a/Sources/OvCore/src/OvCore/Rendering/SkinningUtils.cpp b/Sources/OvCore/src/OvCore/Rendering/SkinningUtils.cpp index 05d7802b..3d93f4d8 100644 --- a/Sources/OvCore/src/OvCore/Rendering/SkinningUtils.cpp +++ b/Sources/OvCore/src/OvCore/Rendering/SkinningUtils.cpp @@ -34,7 +34,8 @@ void OvCore::Rendering::SkinningUtils::ApplyDescriptor( p_drawable.SetDescriptor({ .matrices = boneMatrices.data(), .count = static_cast(boneMatrices.size()), - .poseVersion = p_renderer.GetPoseVersion() + .poseVersion = p_renderer.GetPoseVersion(), + .boundsScale = p_renderer.GetSkinningBoundsScale() }); } From eff1f65cd67f96b580a87ce6d76b28f98edf4cd3 Mon Sep 17 00:00:00 2001 From: Gopmyc Date: Thu, 9 Apr 2026 04:06:09 +0200 Subject: [PATCH 4/6] Exposed skinned bounds scale in Lua API --- Resources/Engine/Lua/Components/SkinnedMeshRenderer.lua | 8 ++++++++ .../Scripting/Lua/Bindings/LuaComponentsBindings.cpp | 2 ++ 2 files changed, 10 insertions(+) diff --git a/Resources/Engine/Lua/Components/SkinnedMeshRenderer.lua b/Resources/Engine/Lua/Components/SkinnedMeshRenderer.lua index 991fe6d7..ae2071cd 100644 --- a/Resources/Engine/Lua/Components/SkinnedMeshRenderer.lua +++ b/Resources/Engine/Lua/Components/SkinnedMeshRenderer.lua @@ -37,6 +37,14 @@ function SkinnedMeshRenderer:SetPlaybackSpeed(speed) end ---@return number function SkinnedMeshRenderer:GetPlaybackSpeed() end +--- Sets the bounds scale used during frustum culling for skinned meshes +---@param scale number +function SkinnedMeshRenderer:SetSkinningBoundsScale(scale) end + +--- Returns the bounds scale used during frustum culling for skinned meshes +---@return number +function SkinnedMeshRenderer:GetSkinningBoundsScale() end + --- Sets playback time in seconds ---@param timeSeconds number function SkinnedMeshRenderer:SetTime(timeSeconds) end diff --git a/Sources/OvCore/src/OvCore/Scripting/Lua/Bindings/LuaComponentsBindings.cpp b/Sources/OvCore/src/OvCore/Scripting/Lua/Bindings/LuaComponentsBindings.cpp index 27f0bdd9..74185e85 100644 --- a/Sources/OvCore/src/OvCore/Scripting/Lua/Bindings/LuaComponentsBindings.cpp +++ b/Sources/OvCore/src/OvCore/Scripting/Lua/Bindings/LuaComponentsBindings.cpp @@ -98,6 +98,8 @@ void BindLuaComponents(sol::state& p_luaState) "IsLooping", &CSkinnedMeshRenderer::IsLooping, "SetPlaybackSpeed", &CSkinnedMeshRenderer::SetPlaybackSpeed, "GetPlaybackSpeed", &CSkinnedMeshRenderer::GetPlaybackSpeed, + "SetSkinningBoundsScale", &CSkinnedMeshRenderer::SetSkinningBoundsScale, + "GetSkinningBoundsScale", &CSkinnedMeshRenderer::GetSkinningBoundsScale, "SetTime", &CSkinnedMeshRenderer::SetTime, "GetTime", &CSkinnedMeshRenderer::GetTime, "GetAnimationCount", &CSkinnedMeshRenderer::GetAnimationCount, From eed36fcb36d038024005ac637ac17eed049e559b Mon Sep 17 00:00:00 2001 From: Gopmyc Date: Thu, 9 Apr 2026 05:22:32 +0200 Subject: [PATCH 5/6] Renamed skinned bounds scale to mesh bounds scale --- .../Lua/Components/SkinnedMeshRenderer.lua | 4 ++-- .../ECS/Components/CSkinnedMeshRenderer.h | 10 +++++----- .../ECS/Components/CSkinnedMeshRenderer.cpp | 20 ++++++++++--------- .../src/OvCore/Rendering/SkinningUtils.cpp | 2 +- .../Lua/Bindings/LuaComponentsBindings.cpp | 4 ++-- 5 files changed, 21 insertions(+), 19 deletions(-) diff --git a/Resources/Engine/Lua/Components/SkinnedMeshRenderer.lua b/Resources/Engine/Lua/Components/SkinnedMeshRenderer.lua index ae2071cd..657422d8 100644 --- a/Resources/Engine/Lua/Components/SkinnedMeshRenderer.lua +++ b/Resources/Engine/Lua/Components/SkinnedMeshRenderer.lua @@ -39,11 +39,11 @@ function SkinnedMeshRenderer:GetPlaybackSpeed() end --- Sets the bounds scale used during frustum culling for skinned meshes ---@param scale number -function SkinnedMeshRenderer:SetSkinningBoundsScale(scale) end +function SkinnedMeshRenderer:SetMeshBoundsScale(scale) end --- Returns the bounds scale used during frustum culling for skinned meshes ---@return number -function SkinnedMeshRenderer:GetSkinningBoundsScale() end +function SkinnedMeshRenderer:GetMeshBoundsScale() end --- Sets playback time in seconds ---@param timeSeconds number diff --git a/Sources/OvCore/include/OvCore/ECS/Components/CSkinnedMeshRenderer.h b/Sources/OvCore/include/OvCore/ECS/Components/CSkinnedMeshRenderer.h index 538b4df2..919801a1 100644 --- a/Sources/OvCore/include/OvCore/ECS/Components/CSkinnedMeshRenderer.h +++ b/Sources/OvCore/include/OvCore/ECS/Components/CSkinnedMeshRenderer.h @@ -94,15 +94,15 @@ namespace OvCore::ECS::Components float GetPlaybackSpeed() const; /** - * Returns the scale applied to skinned bounds during frustum culling + * Returns the scale applied to mesh bounds during frustum culling */ - float GetSkinningBoundsScale() const; + float GetMeshBoundsScale() const; /** - * Sets the scale applied to skinned bounds during frustum culling + * Sets the scale applied to mesh bounds during frustum culling * @param p_scale */ - void SetSkinningBoundsScale(float p_scale); + void SetMeshBoundsScale(float p_scale); /** * Sets the current playback time in seconds @@ -198,7 +198,7 @@ namespace OvCore::ECS::Components bool m_playing = true; bool m_looping = true; float m_playbackSpeed = 1.0f; - float m_skinningBoundsScale = 1.5f; + float m_meshBoundsScale = 1.5f; float m_poseEvaluationRate = 60.0f; float m_poseEvaluationAccumulator = 0.0f; diff --git a/Sources/OvCore/src/OvCore/ECS/Components/CSkinnedMeshRenderer.cpp b/Sources/OvCore/src/OvCore/ECS/Components/CSkinnedMeshRenderer.cpp index c09502a3..2ec36bf0 100644 --- a/Sources/OvCore/src/OvCore/ECS/Components/CSkinnedMeshRenderer.cpp +++ b/Sources/OvCore/src/OvCore/ECS/Components/CSkinnedMeshRenderer.cpp @@ -174,14 +174,14 @@ float OvCore::ECS::Components::CSkinnedMeshRenderer::GetPlaybackSpeed() const return m_playbackSpeed; } -float OvCore::ECS::Components::CSkinnedMeshRenderer::GetSkinningBoundsScale() const +float OvCore::ECS::Components::CSkinnedMeshRenderer::GetMeshBoundsScale() const { - return m_skinningBoundsScale; + return m_meshBoundsScale; } -void OvCore::ECS::Components::CSkinnedMeshRenderer::SetSkinningBoundsScale(float p_scale) +void OvCore::ECS::Components::CSkinnedMeshRenderer::SetMeshBoundsScale(float p_scale) { - m_skinningBoundsScale = std::max(1.0f, p_scale); + m_meshBoundsScale = std::max(1.0f, p_scale); } void OvCore::ECS::Components::CSkinnedMeshRenderer::SetTime(float p_timeSeconds) @@ -357,7 +357,7 @@ void OvCore::ECS::Components::CSkinnedMeshRenderer::OnSerialize(tinyxml2::XMLDoc OvCore::Helpers::Serializer::SerializeBoolean(p_doc, p_node, "playing", m_playing); OvCore::Helpers::Serializer::SerializeBoolean(p_doc, p_node, "looping", m_looping); OvCore::Helpers::Serializer::SerializeFloat(p_doc, p_node, "playback_speed", m_playbackSpeed); - OvCore::Helpers::Serializer::SerializeFloat(p_doc, p_node, "skinning_bounds_scale", m_skinningBoundsScale); + OvCore::Helpers::Serializer::SerializeFloat(p_doc, p_node, "mesh_bounds_scale", m_meshBoundsScale); OvCore::Helpers::Serializer::SerializeFloat(p_doc, p_node, "pose_eval_rate", m_poseEvaluationRate); OvCore::Helpers::Serializer::SerializeFloat(p_doc, p_node, "time_ticks", m_currentTimeTicks); OvCore::Helpers::Serializer::SerializeString(p_doc, p_node, "animation", GetActiveAnimationName().value_or(std::string{})); @@ -368,11 +368,13 @@ void OvCore::ECS::Components::CSkinnedMeshRenderer::OnDeserialize(tinyxml2::XMLD OvCore::Helpers::Serializer::DeserializeBoolean(p_doc, p_node, "playing", m_playing); OvCore::Helpers::Serializer::DeserializeBoolean(p_doc, p_node, "looping", m_looping); OvCore::Helpers::Serializer::DeserializeFloat(p_doc, p_node, "playback_speed", m_playbackSpeed); - OvCore::Helpers::Serializer::DeserializeFloat(p_doc, p_node, "skinning_bounds_scale", m_skinningBoundsScale); + // Keep reading legacy field name for backward compatibility. + OvCore::Helpers::Serializer::DeserializeFloat(p_doc, p_node, "skinning_bounds_scale", m_meshBoundsScale); + OvCore::Helpers::Serializer::DeserializeFloat(p_doc, p_node, "mesh_bounds_scale", m_meshBoundsScale); OvCore::Helpers::Serializer::DeserializeFloat(p_doc, p_node, "pose_eval_rate", m_poseEvaluationRate); OvCore::Helpers::Serializer::DeserializeFloat(p_doc, p_node, "time_ticks", m_currentTimeTicks); OvCore::Helpers::Serializer::DeserializeString(p_doc, p_node, "animation", m_deserializedAnimationName); - SetSkinningBoundsScale(m_skinningBoundsScale); + SetMeshBoundsScale(m_meshBoundsScale); m_poseEvaluationRate = std::max(0.0f, m_poseEvaluationRate); m_poseEvaluationAccumulator = 0.0f; @@ -388,8 +390,8 @@ void OvCore::ECS::Components::CSkinnedMeshRenderer::OnInspector(OvUI::Internal:: GUIDrawer::DrawBoolean(p_root, "Playing", m_playing); GUIDrawer::DrawBoolean(p_root, "Looping", m_looping); GUIDrawer::DrawScalar(p_root, "Playback Speed", m_playbackSpeed, 0.01f, -10.0f, 10.0f); - GUIDrawer::DrawScalar(p_root, "Skinned Bounds Scale", m_skinningBoundsScale, 0.05f, 1.0f, 10.0f); - SetSkinningBoundsScale(m_skinningBoundsScale); + GUIDrawer::DrawScalar(p_root, "Mesh Bounds Scale", m_meshBoundsScale, 0.05f, 1.0f, 10.0f); + SetMeshBoundsScale(m_meshBoundsScale); GUIDrawer::DrawScalar(p_root, "Pose Eval Rate", m_poseEvaluationRate, 1.0f, 0.0f, 240.0f); m_poseEvaluationRate = std::max(0.0f, m_poseEvaluationRate); GUIDrawer::DrawScalar( diff --git a/Sources/OvCore/src/OvCore/Rendering/SkinningUtils.cpp b/Sources/OvCore/src/OvCore/Rendering/SkinningUtils.cpp index 3d93f4d8..6221acdf 100644 --- a/Sources/OvCore/src/OvCore/Rendering/SkinningUtils.cpp +++ b/Sources/OvCore/src/OvCore/Rendering/SkinningUtils.cpp @@ -35,7 +35,7 @@ void OvCore::Rendering::SkinningUtils::ApplyDescriptor( .matrices = boneMatrices.data(), .count = static_cast(boneMatrices.size()), .poseVersion = p_renderer.GetPoseVersion(), - .boundsScale = p_renderer.GetSkinningBoundsScale() + .boundsScale = p_renderer.GetMeshBoundsScale() }); } diff --git a/Sources/OvCore/src/OvCore/Scripting/Lua/Bindings/LuaComponentsBindings.cpp b/Sources/OvCore/src/OvCore/Scripting/Lua/Bindings/LuaComponentsBindings.cpp index 74185e85..9fbe84f2 100644 --- a/Sources/OvCore/src/OvCore/Scripting/Lua/Bindings/LuaComponentsBindings.cpp +++ b/Sources/OvCore/src/OvCore/Scripting/Lua/Bindings/LuaComponentsBindings.cpp @@ -98,8 +98,8 @@ void BindLuaComponents(sol::state& p_luaState) "IsLooping", &CSkinnedMeshRenderer::IsLooping, "SetPlaybackSpeed", &CSkinnedMeshRenderer::SetPlaybackSpeed, "GetPlaybackSpeed", &CSkinnedMeshRenderer::GetPlaybackSpeed, - "SetSkinningBoundsScale", &CSkinnedMeshRenderer::SetSkinningBoundsScale, - "GetSkinningBoundsScale", &CSkinnedMeshRenderer::GetSkinningBoundsScale, + "SetMeshBoundsScale", &CSkinnedMeshRenderer::SetMeshBoundsScale, + "GetMeshBoundsScale", &CSkinnedMeshRenderer::GetMeshBoundsScale, "SetTime", &CSkinnedMeshRenderer::SetTime, "GetTime", &CSkinnedMeshRenderer::GetTime, "GetAnimationCount", &CSkinnedMeshRenderer::GetAnimationCount, From 500534dba33687950cf830608f79db3eff5407c7 Mon Sep 17 00:00:00 2001 From: Gopmyc Date: Thu, 9 Apr 2026 06:19:51 +0200 Subject: [PATCH 6/6] Addressed final review notes for mesh bounds scale --- Resources/Engine/Lua/Components/SkinnedMeshRenderer.lua | 2 ++ .../include/OvCore/ECS/Components/CSkinnedMeshRenderer.h | 2 ++ .../OvCore/src/OvCore/ECS/Components/CSkinnedMeshRenderer.cpp | 3 --- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Resources/Engine/Lua/Components/SkinnedMeshRenderer.lua b/Resources/Engine/Lua/Components/SkinnedMeshRenderer.lua index 657422d8..7ee6eca0 100644 --- a/Resources/Engine/Lua/Components/SkinnedMeshRenderer.lua +++ b/Resources/Engine/Lua/Components/SkinnedMeshRenderer.lua @@ -38,10 +38,12 @@ function SkinnedMeshRenderer:SetPlaybackSpeed(speed) end function SkinnedMeshRenderer:GetPlaybackSpeed() end --- Sets the bounds scale used during frustum culling for skinned meshes +--- Values below 1.0 are clamped to 1.0 ---@param scale number function SkinnedMeshRenderer:SetMeshBoundsScale(scale) end --- Returns the bounds scale used during frustum culling for skinned meshes +--- Returned value is always >= 1.0 ---@return number function SkinnedMeshRenderer:GetMeshBoundsScale() end diff --git a/Sources/OvCore/include/OvCore/ECS/Components/CSkinnedMeshRenderer.h b/Sources/OvCore/include/OvCore/ECS/Components/CSkinnedMeshRenderer.h index 919801a1..e7410538 100644 --- a/Sources/OvCore/include/OvCore/ECS/Components/CSkinnedMeshRenderer.h +++ b/Sources/OvCore/include/OvCore/ECS/Components/CSkinnedMeshRenderer.h @@ -95,11 +95,13 @@ namespace OvCore::ECS::Components /** * Returns the scale applied to mesh bounds during frustum culling + * Returned value is always >= 1.0f */ float GetMeshBoundsScale() const; /** * Sets the scale applied to mesh bounds during frustum culling + * Any value below 1.0f will be clamped to 1.0f * @param p_scale */ void SetMeshBoundsScale(float p_scale); diff --git a/Sources/OvCore/src/OvCore/ECS/Components/CSkinnedMeshRenderer.cpp b/Sources/OvCore/src/OvCore/ECS/Components/CSkinnedMeshRenderer.cpp index 2ec36bf0..aa621d1d 100644 --- a/Sources/OvCore/src/OvCore/ECS/Components/CSkinnedMeshRenderer.cpp +++ b/Sources/OvCore/src/OvCore/ECS/Components/CSkinnedMeshRenderer.cpp @@ -368,8 +368,6 @@ void OvCore::ECS::Components::CSkinnedMeshRenderer::OnDeserialize(tinyxml2::XMLD OvCore::Helpers::Serializer::DeserializeBoolean(p_doc, p_node, "playing", m_playing); OvCore::Helpers::Serializer::DeserializeBoolean(p_doc, p_node, "looping", m_looping); OvCore::Helpers::Serializer::DeserializeFloat(p_doc, p_node, "playback_speed", m_playbackSpeed); - // Keep reading legacy field name for backward compatibility. - OvCore::Helpers::Serializer::DeserializeFloat(p_doc, p_node, "skinning_bounds_scale", m_meshBoundsScale); OvCore::Helpers::Serializer::DeserializeFloat(p_doc, p_node, "mesh_bounds_scale", m_meshBoundsScale); OvCore::Helpers::Serializer::DeserializeFloat(p_doc, p_node, "pose_eval_rate", m_poseEvaluationRate); OvCore::Helpers::Serializer::DeserializeFloat(p_doc, p_node, "time_ticks", m_currentTimeTicks); @@ -391,7 +389,6 @@ void OvCore::ECS::Components::CSkinnedMeshRenderer::OnInspector(OvUI::Internal:: GUIDrawer::DrawBoolean(p_root, "Looping", m_looping); GUIDrawer::DrawScalar(p_root, "Playback Speed", m_playbackSpeed, 0.01f, -10.0f, 10.0f); GUIDrawer::DrawScalar(p_root, "Mesh Bounds Scale", m_meshBoundsScale, 0.05f, 1.0f, 10.0f); - SetMeshBoundsScale(m_meshBoundsScale); GUIDrawer::DrawScalar(p_root, "Pose Eval Rate", m_poseEvaluationRate, 1.0f, 0.0f, 240.0f); m_poseEvaluationRate = std::max(0.0f, m_poseEvaluationRate); GUIDrawer::DrawScalar(