diff --git a/Resources/Engine/Lua/Components/SkinnedMeshRenderer.lua b/Resources/Engine/Lua/Components/SkinnedMeshRenderer.lua index 991fe6d7..7ee6eca0 100644 --- a/Resources/Engine/Lua/Components/SkinnedMeshRenderer.lua +++ b/Resources/Engine/Lua/Components/SkinnedMeshRenderer.lua @@ -37,6 +37,16 @@ function SkinnedMeshRenderer:SetPlaybackSpeed(speed) end ---@return number 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 + --- Sets playback time in seconds ---@param timeSeconds number function SkinnedMeshRenderer:SetTime(timeSeconds) end diff --git a/Sources/OvCore/include/OvCore/ECS/Components/CModelRenderer.h b/Sources/OvCore/include/OvCore/ECS/Components/CModelRenderer.h index 66af257f..07fefa2d 100644 --- a/Sources/OvCore/include/OvCore/ECS/Components/CModelRenderer.h +++ b/Sources/OvCore/include/OvCore/ECS/Components/CModelRenderer.h @@ -113,4 +113,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/include/OvCore/ECS/Components/CSkinnedMeshRenderer.h b/Sources/OvCore/include/OvCore/ECS/Components/CSkinnedMeshRenderer.h index b009b815..e7410538 100644 --- a/Sources/OvCore/include/OvCore/ECS/Components/CSkinnedMeshRenderer.h +++ b/Sources/OvCore/include/OvCore/ECS/Components/CSkinnedMeshRenderer.h @@ -93,6 +93,19 @@ namespace OvCore::ECS::Components */ float GetPlaybackSpeed() const; + /** + * 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); + /** * Sets the current playback time in seconds * @param p_timeSeconds @@ -187,6 +200,7 @@ namespace OvCore::ECS::Components bool m_playing = true; bool m_looping = true; float m_playbackSpeed = 1.0f; + float m_meshBoundsScale = 1.5f; float m_poseEvaluationRate = 60.0f; float m_poseEvaluationAccumulator = 0.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/CSkinnedMeshRenderer.cpp b/Sources/OvCore/src/OvCore/ECS/Components/CSkinnedMeshRenderer.cpp index 055a19d5..aa621d1d 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::GetMeshBoundsScale() const +{ + return m_meshBoundsScale; +} + +void OvCore::ECS::Components::CSkinnedMeshRenderer::SetMeshBoundsScale(float p_scale) +{ + m_meshBoundsScale = 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, "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{})); @@ -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, "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); + SetMeshBoundsScale(m_meshBoundsScale); m_poseEvaluationRate = std::max(0.0f, m_poseEvaluationRate); m_poseEvaluationAccumulator = 0.0f; @@ -375,6 +388,7 @@ 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, "Mesh Bounds Scale", m_meshBoundsScale, 0.05f, 1.0f, 10.0f); 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 349cf475..982ab65d 100644 --- a/Sources/OvCore/src/OvCore/Rendering/SceneRenderer.cpp +++ b/Sources/OvCore/src/OvCore/Rendering/SceneRenderer.cpp @@ -280,7 +280,7 @@ SceneRenderer::SceneDrawablesDescriptor OvCore::Rendering::SceneRenderer::ParseS if (!materialRenderer) continue; const auto* skinnedRenderer = owner.GetComponent(); const bool hasSkinning = SkinningUtils::IsSkinningActive(skinnedRenderer); - + const auto& transform = owner.transform.GetFTransform(); const auto& materials = materialRenderer->GetMaterials(); @@ -314,7 +314,7 @@ SceneRenderer::SceneDrawablesDescriptor OvCore::Rendering::SceneRenderer::ParseS drawable.AddDescriptor({ .actor = modelRenderer->owner, .visibilityFlags = materialRenderer->GetVisibilityFlags(), - .bounds = bounds, + .bounds = bounds }); drawable.AddDescriptor({ @@ -359,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)) @@ -386,11 +387,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 *= skinningDescriptor->boundsScale; + } + + if (!frustum->BoundingSphereInFrustum(cullingBounds, desc.actor.transform.GetFTransform())) { continue; // Skip this drawable as it's outside the frustum } diff --git a/Sources/OvCore/src/OvCore/Rendering/SkinningUtils.cpp b/Sources/OvCore/src/OvCore/Rendering/SkinningUtils.cpp index 05d7802b..6221acdf 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.GetMeshBoundsScale() }); } diff --git a/Sources/OvCore/src/OvCore/Scripting/Lua/Bindings/LuaComponentsBindings.cpp b/Sources/OvCore/src/OvCore/Scripting/Lua/Bindings/LuaComponentsBindings.cpp index 27f0bdd9..9fbe84f2 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, + "SetMeshBoundsScale", &CSkinnedMeshRenderer::SetMeshBoundsScale, + "GetMeshBoundsScale", &CSkinnedMeshRenderer::GetMeshBoundsScale, "SetTime", &CSkinnedMeshRenderer::SetTime, "GetTime", &CSkinnedMeshRenderer::GetTime, "GetAnimationCount", &CSkinnedMeshRenderer::GetAnimationCount,