/
redgpu
/
ezEngine
Обзор
Документация
Войти
/
redgpu
/
ezEngine
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
dev
Code/Engine/RendererCore/Components/Implementation/SplineComponent.cpp
890 строк
27 KB
C-Core
Custom spline tangents local to node, up dir calculation fixes (#2002)
23 июл 2026, 13:48
Не верифицирован
23 июл 2026, 13:48
e33599d
Код
Авторство
О чём код?
#include <GameEngine/GameEnginePCH.h> #include <Core/Messages/HierarchyChangedMessages.h> #include <Core/Messages/TransformChangedMessage.h> #include <Core/WorldSerializer/WorldReader.h> #include <Core/WorldSerializer/WorldWriter.h> #include <Foundation/Serialization/AbstractObjectGraph.h> #include <RendererCore/Components/SplineComponent.h> #include <RendererCore/Debug/DebugRenderer.h> #include <RendererCore/Pipeline/RenderData.h> #include <RendererCore/RenderWorld/RenderWorld.h> // clang-format off EZ_BEGIN_STATIC_REFLECTED_BITFLAGS(ezSplineComponentFlags, 1) EZ_BITFLAGS_CONSTANTS(ezSplineComponentFlags::VisualizeSpline, ezSplineComponentFlags::VisualizeUpDir, ezSplineComponentFlags::VisualizeTangents) EZ_END_STATIC_REFLECTED_BITFLAGS; EZ_BEGIN_STATIC_REFLECTED_ENUM(ezSplineComponentSpace, 1) EZ_ENUM_CONSTANTS(ezSplineComponentSpace::Local, ezSplineComponentSpace::Global) EZ_END_STATIC_REFLECTED_ENUM; EZ_IMPLEMENT_MESSAGE_TYPE(ezMsgSplineChanged); EZ_BEGIN_DYNAMIC_REFLECTED_TYPE(ezMsgSplineChanged, 1, ezRTTIDefaultAllocator<ezMsgSplineChanged>) { EZ_BEGIN_PROPERTIES { EZ_MEMBER_PROPERTY("ChangeCounter", m_uiChangeCounter), } EZ_END_PROPERTIES; } EZ_END_DYNAMIC_REFLECTED_TYPE; // clang-format on ////////////////////////////////////////////////////////////////////////// ezSplineComponentManager::ezSplineComponentManager(ezWorld* pWorld) : ezComponentManager(pWorld) { } void ezSplineComponentManager::SetEnableUpdate(ezSplineComponent* pThis, bool bEnable) { if (bEnable) { if (!m_NeedUpdate.Contains(pThis)) m_NeedUpdate.PushBack(pThis); } else { m_NeedUpdate.RemoveAndSwap(pThis); } } void ezSplineComponentManager::Initialize() { auto desc = EZ_CREATE_MODULE_UPDATE_FUNCTION_DESC(ezSplineComponentManager::Update, this); desc.m_bOnlyUpdateWhenSimulating = false; desc.m_Phase = ezWorldUpdatePhase::PostTransform; this->RegisterUpdateFunction(desc); } void ezSplineComponentManager::Update(const ezWorldModule::UpdateContext& context) { for (ezSplineComponent* pComponent : m_NeedUpdate) { if (pComponent->IsActiveAndInitialized()) { pComponent->DrawDebugVisualizations(pComponent->GetSplineFlags()); } } } ////////////////////////////////////////////////////////////////////////// // clang-format off EZ_BEGIN_COMPONENT_TYPE(ezSplineComponent, 2, ezComponentMode::Static) { EZ_BEGIN_PROPERTIES { EZ_BITFLAGS_ACCESSOR_PROPERTY("Flags", ezSplineComponentFlags, GetSplineFlags, SetSplineFlags), EZ_ACCESSOR_PROPERTY("Closed", GetClosed, SetClosed), EZ_MEMBER_PROPERTY("EditNodes", m_bEditDummy), } EZ_END_PROPERTIES; EZ_BEGIN_MESSAGEHANDLERS { EZ_MESSAGE_HANDLER(ezMsgSplineChanged, OnMsgSplineChanged), EZ_MESSAGE_HANDLER(ezMsgExtractRenderData, OnMsgExtractRenderData), } EZ_END_MESSAGEHANDLERS; EZ_BEGIN_FUNCTIONS { EZ_SCRIPT_FUNCTION_PROPERTY(GetPositionAtKey, In, "Key", In, "Space"), EZ_SCRIPT_FUNCTION_PROPERTY(GetForwardDirAtKey, In, "Key", In, "Space"), EZ_SCRIPT_FUNCTION_PROPERTY(GetUpDirAtKey, In, "Key", In, "Space"), EZ_SCRIPT_FUNCTION_PROPERTY(GetScaleAtKey, In, "Key", In, "Space"), EZ_SCRIPT_FUNCTION_PROPERTY(GetTransformAtKey, In, "Key", In, "Space"), EZ_SCRIPT_FUNCTION_PROPERTY(GetTotalLength), EZ_SCRIPT_FUNCTION_PROPERTY(GetKeyAtDistance, In, "Distance"), EZ_SCRIPT_FUNCTION_PROPERTY(GetPositionAtDistance, In, "Distance", In, "Space"), EZ_SCRIPT_FUNCTION_PROPERTY(GetForwardDirAtDistance, In, "Distance", In, "Space"), EZ_SCRIPT_FUNCTION_PROPERTY(GetUpDirAtDistance, In, "Distance", In, "Space"), EZ_SCRIPT_FUNCTION_PROPERTY(GetScaleAtDistance, In, "Distance", In, "Space"), EZ_SCRIPT_FUNCTION_PROPERTY(GetTransformAtDistance, In, "Distance", In, "Space"), EZ_SCRIPT_FUNCTION_PROPERTY(FindKeyClosestToPoint, In, "Point", Out, "DistanceToPoint", In, "Space", In, "MaxError")->AddAttributes(new ezFunctionArgumentAttributes(3, new ezDefaultValueAttribute(0.1))), EZ_SCRIPT_FUNCTION_PROPERTY(GetChangeCounter), EZ_FUNCTION_PROPERTY(OnObjectCreated), EZ_FUNCTION_PROPERTY(SetChildOrder), } EZ_END_FUNCTIONS; EZ_BEGIN_ATTRIBUTES { new ezCategoryAttribute("Utilities/Splines"), new ezSyncChildOrderAttribute(), new ezSplineManipulatorAttribute("EditNodes", "Closed"), } EZ_END_ATTRIBUTES; } EZ_END_COMPONENT_TYPE // clang-format on enum SplineComponentInternalFlags { DisallowUpdateFromNodes = 0, }; ezSplineComponent::ezSplineComponent() = default; ezSplineComponent::~ezSplineComponent() = default; void ezSplineComponent::SerializeComponent(ezWorldWriter& ref_stream) const { SUPER::SerializeComponent(ref_stream); auto& s = ref_stream.GetStream(); s << m_SplineFlags; m_Spline.Serialize(s).AssertSuccess(); s << m_Uuid; } void ezSplineComponent::DeserializeComponent(ezWorldReader& ref_stream) { SUPER::DeserializeComponent(ref_stream); ezUInt32 uiVersion = ref_stream.GetComponentTypeVersion(GetStaticRTTI()); auto& s = ref_stream.GetStream(); s >> m_SplineFlags; m_Spline.Deserialize(s).AssertSuccess(); if (uiVersion >= 2) { s >> m_Uuid; } // This is to prevent the spline from getting cleared in the Editor when it is deserialized as part of a prefab. // In this case it still has a unique id so the 'in editor' check in UpdateSpline alone would not be sufficient. SetUserFlag(SplineComponentInternalFlags::DisallowUpdateFromNodes, true); } void ezSplineComponent::OnActivated() { SUPER::OnActivated(); if (m_SplineFlags.IsAnyFlagSet()) { static_cast<ezSplineComponentManager*>(GetOwningManager())->SetEnableUpdate(this, true); } UpdateSpline(); } void ezSplineComponent::OnDeactivated() { SUPER::OnDeactivated(); if (m_SplineFlags.IsAnyFlagSet()) { // assume that if no flag is set, update is already disabled static_cast<ezSplineComponentManager*>(GetOwningManager())->SetEnableUpdate(this, false); } } void ezSplineComponent::SetClosed(bool bClosed) { if (m_Spline.m_bClosed == bClosed) return; m_Spline.m_bClosed = bClosed; UpdateSpline(); } void ezSplineComponent::SetSplineFlags(ezBitflags<ezSplineComponentFlags> flags) { if (m_SplineFlags == flags) return; m_SplineFlags = flags; if (IsActiveAndInitialized()) { static_cast<ezSplineComponentManager*>(GetOwningManager())->SetEnableUpdate(this, m_SplineFlags.IsAnyFlagSet()); } } ezVec3 ezSplineComponent::GetPositionAtKey(float fKey, ezEnum<ezSplineComponentSpace> space /* = ezSplineComponentSpace::Default*/) const { ezSimdVec4f pos = m_Spline.EvaluatePosition(fKey); if (space == ezSplineComponentSpace::Global) { pos = GetOwner()->GetGlobalTransformSimd().TransformPosition(pos); } return ezSimdConversion::ToVec3(pos); } ezVec3 ezSplineComponent::GetForwardDirAtKey(float fKey, ezEnum<ezSplineComponentSpace> space /* = ezSplineComponentSpace::Default*/) const { ezSimdVec4f dir = m_Spline.EvaluateDerivative(fKey); dir.NormalizeIfNotZero<3>(ezSimdVec4f(1, 0, 0, 0), 0.0001f); if (space == ezSplineComponentSpace::Global) { dir = GetOwner()->GetGlobalTransformSimd().TransformDirection(dir); } return ezSimdConversion::ToVec3(dir); } ezVec3 ezSplineComponent::GetUpDirAtKey(float fKey, ezEnum<ezSplineComponentSpace> space /* = ezSplineComponentSpace::Default*/) const { ezSimdVec4f dir = m_Spline.EvaluateUpDirection(fKey); if (space == ezSplineComponentSpace::Global) { dir = GetOwner()->GetGlobalTransformSimd().TransformDirection(dir); } return ezSimdConversion::ToVec3(dir); } ezVec3 ezSplineComponent::GetScaleAtKey(float fKey, ezEnum<ezSplineComponentSpace> space /* = ezSplineComponentSpace::Default*/) const { ezSimdVec4f scale = m_Spline.EvaluateScale(fKey); if (space == ezSplineComponentSpace::Global) { scale = scale.CompMul(GetOwner()->GetGlobalTransformSimd().m_Scale); } return ezSimdConversion::ToVec3(scale); } ezTransform ezSplineComponent::GetTransformAtKey(float fKey, ezEnum<ezSplineComponentSpace> space /* = ezSplineComponentSpace::Default*/) const { ezSimdTransform t = m_Spline.EvaluateTransform(fKey); if (space == ezSplineComponentSpace::Global) { t = ezSimdTransform::MakeGlobalTransform(GetOwner()->GetGlobalTransformSimd(), t); } return ezSimdConversion::ToTransform(t); } float ezSplineComponent::GetSegmentLength(ezUInt32 uiSegmentIndex) const { const float fSegmentKey = static_cast<float>(uiSegmentIndex); const float fNextSegmentKey = static_cast<float>(uiSegmentIndex + 1); float fStartDistance = 0.0f; float fEndDistance = 0.0f; for (auto it : m_DistanceToKey) { if (ezMath::IsEqual(it.value, fSegmentKey, ezMath::DefaultEpsilon<float>())) { fStartDistance = it.key; } if (ezMath::IsEqual(it.value, fNextSegmentKey, ezMath::DefaultEpsilon<float>())) { fEndDistance = it.key; break; } } return fEndDistance - fStartDistance; } ezVec3 ezSplineComponent::GetPositionAtDistance(float fDistance, ezEnum<ezSplineComponentSpace> space /* = ezSplineComponentSpace::Default*/) const { const float fKey = GetKeyAtDistance(fDistance); return GetPositionAtKey(fKey, space); } ezVec3 ezSplineComponent::GetForwardDirAtDistance(float fDistance, ezEnum<ezSplineComponentSpace> space /* = ezSplineComponentSpace::Default*/) const { const float fKey = GetKeyAtDistance(fDistance); return GetForwardDirAtKey(fKey, space); } ezVec3 ezSplineComponent::GetUpDirAtDistance(float fDistance, ezEnum<ezSplineComponentSpace> space /* = ezSplineComponentSpace::Default*/) const { const float fKey = GetKeyAtDistance(fDistance); return GetUpDirAtKey(fKey, space); } ezVec3 ezSplineComponent::GetScaleAtDistance(float fDistance, ezEnum<ezSplineComponentSpace> space /* = ezSplineComponentSpace::Default*/) const { const float fKey = GetKeyAtDistance(fDistance); return GetScaleAtKey(fKey, space); } ezTransform ezSplineComponent::GetTransformAtDistance(float fDistance, ezEnum<ezSplineComponentSpace> space /* = ezSplineComponentSpace::Default*/) const { const float fKey = GetKeyAtDistance(fDistance); return GetTransformAtKey(fKey, space); } float ezSplineComponent::FindKeyClosestToPoint(const ezVec3& vPoint, float& out_fDistance, ezEnum<ezSplineComponentSpace> space /* = ezSplineComponentSpace::Default*/, float fMaxError /*= 0.1f*/) const { ezSimdVec4f p = ezSimdConversion::ToVec3(vPoint); if (space == ezSplineComponentSpace::Global) { p = GetOwner()->GetGlobalTransformSimd().GetInverse().TransformPosition(p); } float fClosestKey = 0.0f; float fClosestDistSqr = 0.0f; m_Spline.FindClosestPoint(p, fClosestKey, fClosestDistSqr, fMaxError); out_fDistance = ezMath::Sqrt(fClosestDistSqr); return fClosestKey; } void ezSplineComponent::SetSpline(ezSpline&& spline) { ezUInt32 uiOldChangeCounter = m_Spline.m_uiChangeCounter; m_Spline = std::move(spline); m_Spline.m_uiChangeCounter = uiOldChangeCounter + 1; CreateDistanceToKeyRemapping(); SendSplineChangedEvent(); } void ezSplineComponent::EndModifySpline(bool bRecreateDistanceToKeyMapping /*= true*/) { m_Spline.m_uiChangeCounter++; if (bRecreateDistanceToKeyMapping || m_DistanceToKey.IsEmpty()) { CreateDistanceToKeyRemapping(); } SendSplineChangedEvent(); } float ezSplineComponent::GetKeyAtDistanceHelper(const ezArrayMap<float, float>& distanceToKey, float fDistance) { if (distanceToKey.IsEmpty()) return 0.0f; const ezUInt32 uiUpperIndex = ezMath::Min(distanceToKey.UpperBound(fDistance), distanceToKey.GetCount() - 1); const ezUInt32 uiLowerIndex = uiUpperIndex > 0 ? uiUpperIndex - 1 : 0; const float fLowerDistance = distanceToKey.GetKey(uiLowerIndex); const float fUpperDistance = distanceToKey.GetKey(uiUpperIndex); const float fLowerKey = distanceToKey.GetValue(uiLowerIndex); const float fUpperKey = distanceToKey.GetValue(uiUpperIndex); return ezMath::Lerp(fLowerKey, fUpperKey, ezMath::Saturate(ezMath::Unlerp(fLowerDistance, fUpperDistance, fDistance))); } void ezSplineComponent::OnMsgSplineChanged(ezMsgSplineChanged& ref_msg) { UpdateSpline(); } void ezSplineComponent::SetChildOrder(const ezVariantArray& handles) { m_Nodes.Clear(); m_Nodes.Reserve(handles.GetCount()); for (const ezVariant& v : handles) m_Nodes.PushBack(v.Get<ezGameObjectHandle>()); UpdateSpline(); } void ezSplineComponent::SendSplineChangedEvent() { ezMsgSplineChanged msg; msg.m_uiChangeCounter = m_Spline.m_uiChangeCounter; for (ezComponent* pComp : GetOwner()->GetComponents()) { if (pComp != this) { pComp->SendMessage(msg); } } } void ezSplineComponent::UpdateSpline(bool bSendChangedEvent /* = true*/) { if (!IsActiveAndInitialized()) return; if (GetUniqueID() != ezInvalidIndex && !GetUserFlag(SplineComponentInternalFlags::DisallowUpdateFromNodes)) { // Only in Editor UpdateFromNodeObjects(); } CreateDistanceToKeyRemapping(); if (bSendChangedEvent) { SendSplineChangedEvent(); } } void ezSplineComponent::UpdateFromNodeObjects() { EZ_ASSERT_DEV(!GetUserFlag(SplineComponentInternalFlags::DisallowUpdateFromNodes), "This function should not be called when updates from nodes are disabled."); auto& points = m_Spline.m_ControlPoints; points.Clear(); if (m_Nodes.GetCount() < 2) return; for (ezGameObjectHandle hNode : m_Nodes) { ezGameObject* pNode; if (!GetWorld()->TryGetObject(hNode, pNode)) continue; ezSplineNodeComponent* pNodeComponent = nullptr; if (!pNode->TryGetComponentOfBaseType(pNodeComponent)) continue; pNodeComponent->m_uiNodeIndex = points.GetCount(); const ezSimdTransform localNodeTransform = pNodeComponent->GetOwner()->GetLocalTransformSimd(); auto& cp = points.ExpandAndGetRef(); cp.SetPosition(localNodeTransform.m_Position); cp.SetTangentIn(pNodeComponent->GetFinalCustomTangentIn(), pNodeComponent->GetTangentModeIn()); if (pNodeComponent->GetLinkCustomTangents() && pNodeComponent->GetTangentModeIn() == ezSplineTangentMode::Custom && pNodeComponent->GetTangentModeOut() == ezSplineTangentMode::Custom) { cp.SetTangentOut(-pNodeComponent->GetFinalCustomTangentIn(), pNodeComponent->GetTangentModeOut()); } else { cp.SetTangentOut(pNodeComponent->GetFinalCustomTangentOut(), pNodeComponent->GetTangentModeOut()); } cp.SetRoll(pNodeComponent->GetRoll()); cp.SetScale(localNodeTransform.m_Scale); } if (points.GetCount() < 2) { points.Clear(); return; } ezCoordinateSystem coordinateSystem; GetWorld()->GetCoordinateSystem(GetOwner()->GetGlobalPosition(), coordinateSystem); m_Spline.CalculateUpDirAndAutoTangents(ezSimdConversion::ToVec3(coordinateSystem.m_vUpDir), ezSimdConversion::ToVec3(coordinateSystem.m_vForwardDir)); ++m_Spline.m_uiChangeCounter; if (m_Spline.m_uiChangeCounter == ezInvalidIndex) m_Spline.m_uiChangeCounter = 0; } void ezSplineComponent::InsertHalfPoint(ezDynamicArray<float>& ref_Ts, ezUInt32 uiCp0, float fLowerT, float fUpperT, const ezSimdVec4f& vLowerPos, const ezSimdVec4f& vUpperPos, float fDistSqr, ezInt32 iMinSteps, ezInt32 iMaxSteps) const { const float fHalfT = ezMath::Lerp(fLowerT, fUpperT, 0.5f); const ezSimdVec4f vHalfPos = m_Spline.EvaluatePosition(uiCp0, fHalfT); if (iMinSteps <= 0) { const ezSimdVec4f vInterpPos = ezSimdVec4f::Lerp(vLowerPos, vUpperPos, ezSimdVec4f(0.5f)); if ((vHalfPos - vInterpPos).GetLengthSquared<3>() < fDistSqr) { return; } } if (iMaxSteps > 0) { InsertHalfPoint(ref_Ts, uiCp0, fLowerT, fHalfT, vLowerPos, vHalfPos, fDistSqr, iMinSteps - 1, iMaxSteps - 1); } ref_Ts.PushBack(fHalfT); if (iMaxSteps > 0) { InsertHalfPoint(ref_Ts, uiCp0, fHalfT, fUpperT, vHalfPos, vUpperPos, fDistSqr, iMinSteps - 1, iMaxSteps - 1); } } void ezSplineComponent::CreateDistanceToKeyRemapping() { m_DistanceToKey.Clear(); m_fTotalLength = 0.0f; const auto& points = m_Spline.m_ControlPoints; const ezUInt32 uiNumCPs = points.GetCount(); if (uiNumCPs < 2) return; m_DistanceToKey.Insert(0.0f, 0.0f); constexpr float fMaxErrorSqr = ezMath::Square(0.1f); ezTempHybridArray<float, 64> segmentTs; const ezUInt32 uiNumSegments = m_Spline.m_bClosed ? uiNumCPs : uiNumCPs - 1; for (ezUInt32 uiSegment = 0; uiSegment < uiNumSegments; ++uiSegment) { segmentTs.Clear(); segmentTs.PushBack(1.0f); const ezUInt32 uiCp0 = uiSegment; const ezUInt32 uiCp1 = (uiCp0 + 1 < uiNumCPs) ? uiCp0 + 1 : 0; const auto& vLowerPos = points[uiCp0].m_vPos; const auto& vUpperPos = points[uiCp1].m_vPos; EZ_ASSERT_DEBUG(vLowerPos.IsValid<3>() && vUpperPos.IsValid<3>(), "Invalid control point position."); InsertHalfPoint(segmentTs, uiCp0, 0.0f, 1.0f, vLowerPos, vUpperPos, fMaxErrorSqr, 0, 7); segmentTs.Sort(); ezSimdVec4f vLastPos = vLowerPos; for (float t : segmentTs) { const ezSimdVec4f vCurPos = m_Spline.EvaluatePosition(uiCp0, t); m_fTotalLength += (vLastPos - vCurPos).GetLength<3>(); m_DistanceToKey.Insert(m_fTotalLength, t + uiSegment); vLastPos = vCurPos; } } } void ezSplineComponent::DrawDebugVisualizations(ezBitflags<ezSplineComponentFlags> flags) const { if (flags.IsNoFlagSet()) return; if (m_DistanceToKey.IsEmpty()) return; const bool bVisPath = flags.IsSet(ezSplineComponentFlags::VisualizeSpline); const bool bVisUp = flags.IsSet(ezSplineComponentFlags::VisualizeUpDir); ezTempHybridArray<ezDebugRendererLine, 32> lines; ezColor c = ezColorScheme::LightUI(ezColorScheme::Pink); ezColor cUp = ezColorScheme::LightUI(ezColorScheme::Blue); ezVec3 lastPos = GetPositionAtKey(0); float fLastKey = 0.0f; for (ezUInt32 i = 1; i < m_DistanceToKey.GetCount(); ++i) { const float fKey = m_DistanceToKey.GetValue(i); const float fSubStep = 1.0f / 4.0f; for (float fT = fSubStep; fT <= 1.01f; fT += fSubStep) { const float fSubKey = ezMath::Lerp(fLastKey, fKey, fT); const ezVec3 curPos = GetPositionAtKey(fSubKey); if (bVisPath) { auto& line = lines.ExpandAndGetRef(); line.m_start = lastPos; line.m_end = curPos; line.m_startColor = c; line.m_endColor = c; } if (bVisUp) { auto& line = lines.ExpandAndGetRef(); line.m_start = curPos; line.m_end = curPos + GetUpDirAtKey(fSubKey) * 0.25f; line.m_startColor = cUp; line.m_endColor = cUp; } lastPos = curPos; } fLastKey = fKey; } ezDebugRenderer::DrawLinesOccluded(GetWorld(), lines, ezColor::White.GetDarker(), GetOwner()->GetGlobalTransform()); ezDebugRenderer::DrawLines(GetWorld(), lines, ezColor::White, GetOwner()->GetGlobalTransform()); const bool bVisTangents = flags.IsSet(ezSplineComponentFlags::VisualizeTangents); if (bVisTangents) { for (ezUInt32 i = 0; i < m_Spline.m_ControlPoints.GetCount(); ++i) { DrawDebugTangents(i); } } } void ezSplineComponent::DrawDebugTangents(ezUInt32 uiPointIndex, ezSplineTangentMode::Enum tangentModeIn /*= ezSplineTangentMode::Default*/, ezSplineTangentMode::Enum tangentModeOut /*= ezSplineTangentMode::Default*/) const { if (uiPointIndex < m_Spline.m_ControlPoints.GetCount()) { const ezSpline::ControlPoint& cp = m_Spline.m_ControlPoints[uiPointIndex]; const ezColor tInColor = ezColorScheme::DarkUI(tangentModeIn == ezSplineTangentMode::Custom ? ezColorScheme::Grape : ezColorScheme::Gray); const ezColor tOutColor = ezColorScheme::DarkUI(tangentModeOut == ezSplineTangentMode::Custom ? ezColorScheme::Grape : ezColorScheme::Gray); const ezVec3 vTangentIn = ezSimdConversion::ToVec3(cp.m_vPosTangentIn); const ezVec3 vTangentOut = ezSimdConversion::ToVec3(cp.m_vPosTangentOut); const ezVec3 vGlobalPos = ezSimdConversion::ToVec3(GetOwner()->GetGlobalTransformSimd().TransformPosition(cp.m_vPos)); const ezTransform t = ezTransform::Make(vGlobalPos, GetOwner()->GetGlobalRotation(), GetOwner()->GetGlobalScaling()); ezDebugRenderer::DrawLineSphere(GetWorld(), ezBoundingSphere::MakeFromCenterAndRadius(vTangentIn, 0.05f), tInColor, t); ezDebugRenderer::DrawLineSphere(GetWorld(), ezBoundingSphere::MakeFromCenterAndRadius(vTangentOut, 0.05f), tOutColor, t); ezTempHybridArray<ezDebugRendererLine, 2> lines; lines.PushBack(ezDebugRendererLine(ezVec3::MakeZero(), vTangentIn, tInColor)); lines.PushBack(ezDebugRendererLine(ezVec3::MakeZero(), vTangentOut, tOutColor)); ezDebugRenderer::DrawLines(GetWorld(), lines, ezColor::White, t); } } bool ezSplineComponent::DrawSplineOnSelection() const { const ezUInt16 uiFrame = static_cast<ezUInt16>(ezRenderWorld::GetFrameCounter()); if (m_uiExtractedFrame == uiFrame) { return false; // already drawn this frame } m_uiExtractedFrame = uiFrame; if (!m_SplineFlags.IsSet(ezSplineComponentFlags::VisualizeSpline)) { DrawDebugVisualizations(ezSplineComponentFlags::VisualizeSpline); } return true; } void ezSplineComponent::OnMsgExtractRenderData(ezMsgExtractRenderData& msg) const { if (msg.m_OverrideCategory != ezDefaultRenderDataCategories::Selection) return; DrawSplineOnSelection(); } void ezSplineComponent::OnObjectCreated(const ezAbstractObjectNode& node) { m_Uuid = node.GetGuid(); } ////////////////////////////////////////////////////////////////////////// // clang-format off EZ_BEGIN_COMPONENT_TYPE(ezSplineNodeComponent, 1, ezComponentMode::Static) { EZ_BEGIN_PROPERTIES { EZ_ACCESSOR_PROPERTY("Roll", GetRoll, SetRoll), EZ_ENUM_ACCESSOR_PROPERTY("TangentModeIn", ezSplineTangentMode, GetTangentModeIn, SetTangentModeIn), EZ_ACCESSOR_PROPERTY("CustomTangentIn", GetCustomTangentIn, SetCustomTangentIn), EZ_ENUM_ACCESSOR_PROPERTY("TangentModeOut", ezSplineTangentMode, GetTangentModeOut, SetTangentModeOut), EZ_ACCESSOR_PROPERTY("CustomTangentOut", GetCustomTangentOut, SetCustomTangentOut), EZ_ACCESSOR_PROPERTY("LinkCustomTangents", GetLinkCustomTangents, SetLinkCustomTangents), EZ_MEMBER_PROPERTY("EditNodes", m_bEditDummy), } EZ_END_PROPERTIES; EZ_BEGIN_MESSAGEHANDLERS { EZ_MESSAGE_HANDLER(ezMsgTransformChanged, OnMsgTransformChanged), EZ_MESSAGE_HANDLER(ezMsgParentChanged, OnMsgParentChanged), EZ_MESSAGE_HANDLER(ezMsgExtractRenderData, OnMsgExtractRenderData), } EZ_END_MESSAGEHANDLERS; EZ_BEGIN_ATTRIBUTES { new ezCategoryAttribute("Utilities/Splines"), new ezShapeIconAlwaysVisibleAttribute(), new ezSplineTangentManipulatorAttribute("TangentModeIn", "CustomTangentIn"), new ezSplineTangentManipulatorAttribute("TangentModeOut", "CustomTangentOut"), new ezSplineManipulatorAttribute("EditNodes", "Closed"), } EZ_END_ATTRIBUTES; } EZ_END_COMPONENT_TYPE // clang-format on ezSplineNodeComponent::ezSplineNodeComponent() = default; ezSplineNodeComponent::~ezSplineNodeComponent() = default; void ezSplineNodeComponent::SetRoll(ezAngle roll) { if (m_Roll != roll) { m_Roll = roll; SplineChanged(); } } void ezSplineNodeComponent::SetTangentModeIn(ezEnum<ezSplineTangentMode> mode) { if (m_TangentModeIn != mode) { m_TangentModeIn = mode; SplineChanged(); } } void ezSplineNodeComponent::SetTangentModeOut(ezEnum<ezSplineTangentMode> mode) { if (m_TangentModeOut != mode) { m_TangentModeOut = mode; SplineChanged(); } } void ezSplineNodeComponent::SetCustomTangentIn(const ezVec3& vTangent) { if (m_vCustomTangentIn != vTangent) { m_vCustomTangentIn = vTangent; SplineChanged(); } } void ezSplineNodeComponent::SetCustomTangentOut(const ezVec3& vTangent) { if (m_vCustomTangentOut != vTangent) { m_vCustomTangentOut = vTangent; SplineChanged(); } } void ezSplineNodeComponent::SetLinkCustomTangents(bool bLink) { if (m_bLinkCustomTangents != bLink) { m_bLinkCustomTangents = bLink; SplineChanged(); } } void ezSplineNodeComponent::OnMsgTransformChanged(ezMsgTransformChanged& msg) { SplineChanged(); } void ezSplineNodeComponent::OnMsgParentChanged(ezMsgParentChanged& msg) { if (msg.m_Type == ezMsgParentChanged::Type::ParentUnlinked) { ezGameObject* pOldParent = nullptr; if (GetWorld()->TryGetObject(msg.m_hParent, pOldParent)) { ezMsgSplineChanged msg2; pOldParent->SendEventMessage(msg2, this); } } else { SplineChanged(); } } void ezSplineNodeComponent::OnMsgExtractRenderData(ezMsgExtractRenderData& msg) const { if (msg.m_OverrideCategory != ezDefaultRenderDataCategories::Selection) return; const ezGameObject* pParent = GetOwner()->GetParent(); const ezSplineComponent* pSplineComponent = nullptr; while (pParent != nullptr && !pParent->TryGetComponentOfBaseType(pSplineComponent)) { pParent = pParent->GetParent(); } if (pSplineComponent != nullptr && pSplineComponent->DrawSplineOnSelection()) { pSplineComponent->DrawDebugTangents(m_uiNodeIndex, m_TangentModeIn, m_TangentModeOut); } } void ezSplineNodeComponent::OnActivated() { SUPER::OnActivated(); GetOwner()->EnableStaticTransformChangesNotifications(); GetOwner()->EnableParentChangesNotifications(); } void ezSplineNodeComponent::SplineChanged() { if (!IsActiveAndInitialized()) return; ezMsgSplineChanged msg; GetOwner()->SendEventMessage(msg, this); } ezSimdVec4f ezSplineNodeComponent::GetFinalCustomTangentIn() const { ezSimdVec4f t = ezSimdConversion::ToVec3(m_vCustomTangentIn); t = GetOwner()->GetLocalRotationSimd() * t; return t; } ezSimdVec4f ezSplineNodeComponent::GetFinalCustomTangentOut() const { ezSimdVec4f t = ezSimdConversion::ToVec3(m_vCustomTangentOut); t = GetOwner()->GetLocalRotationSimd() * t; return t; } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// #include <Foundation/Serialization/AbstractObjectGraph.h> #include <Foundation/Serialization/GraphPatch.h> class ezPathComponentPatch_1_2 : public ezGraphPatch { public: ezPathComponentPatch_1_2() : ezGraphPatch("ezPathComponent", 2) { } virtual void Patch(ezGraphPatchContext& ref_context, ezAbstractObjectGraph* pGraph, ezAbstractObjectNode* pNode) const override { ref_context.RenameClass("ezSplineComponent"); auto* pFlags = pNode->FindProperty("Flags"); if (pFlags && pFlags->m_Value.IsA<ezString>()) { ezStringBuilder sFlags = pFlags->m_Value.Get<ezString>(); sFlags.ReplaceAll("Path", "Spline"); pNode->ChangeProperty("Flags", sFlags.GetView()); } } }; ezPathComponentPatch_1_2 g_ezPathComponentPatch_1_2; class ezPathNodeComponentPatch_1_2 : public ezGraphPatch { public: ezPathNodeComponentPatch_1_2() : ezGraphPatch("ezPathNodeComponent", 2) { } virtual void Patch(ezGraphPatchContext& ref_context, ezAbstractObjectGraph* pGraph, ezAbstractObjectNode* pNode) const override { ref_context.RenameClass("ezSplineNodeComponent"); } }; ezPathNodeComponentPatch_1_2 g_ezPathNodeComponentPatch_1_2; EZ_STATICLINK_FILE(RendererCore, RendererCore_Components_Implementation_SplineComponent);