diff --git a/Source/Engine/Physics/Actors/SplineRopeBody.cpp b/Source/Engine/Physics/Actors/SplineRopeBody.cpp index 6f5306b25..5f072baef 100644 --- a/Source/Engine/Physics/Actors/SplineRopeBody.cpp +++ b/Source/Engine/Physics/Actors/SplineRopeBody.cpp @@ -3,12 +3,10 @@ #include "SplineRopeBody.h" #include "Engine/Level/Actors/Spline.h" #include "Engine/Level/Scene/Scene.h" -#include "Engine/Physics/Physics.h" #include "Engine/Physics/PhysicsScene.h" #include "Engine/Engine/Time.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Profiler/ProfilerMemory.h" -#include "Engine/Serialization/Serialization.h" SplineRopeBody::SplineRopeBody(const SpawnParams& params) : Actor(params) @@ -28,7 +26,7 @@ void SplineRopeBody::Tick() const Transform splineTransform = _spline->GetTransform(); const int32 keyframesCount = keyframes.Count(); const float substepTime = SubstepTime; - const float substepTimeSqr = substepTime * substepTime; + // TODO: scale substep time based on distance to the camera to have better performance when rope is far away bool splineDirty = false; // Synchronize spline keyframes with simulated masses @@ -41,7 +39,7 @@ void SplineRopeBody::Tick() { const int32 i = _masses.Count(); auto& mass = _masses.AddOne(); - mass.PrevPosition = splineTransform.LocalToWorld(keyframes[i].Value.Translation); + mass.Position = mass.PrevPosition = splineTransform.LocalToWorld(keyframes[i].Value.Translation); if (i != 0) mass.SegmentLength = Vector3::Distance(mass.PrevPosition, _masses[i - 1].PrevPosition); else @@ -51,7 +49,7 @@ void SplineRopeBody::Tick() { // Rope start auto& mass = _masses.First(); - mass.Position = mass.PrevPosition = GetPosition(); + mass.Position = GetPosition(); mass.Unconstrained = false; if (splineTransform.LocalToWorld(keyframes.First().Value.Translation) != mass.Position) splineDirty = true; @@ -76,29 +74,29 @@ void SplineRopeBody::Tick() { // Rope end auto& mass = _masses.Last(); - mass.Position = mass.PrevPosition = AttachEnd->GetPosition(); + mass.Position = AttachEnd->GetPosition(); mass.Unconstrained = false; if (splineTransform.LocalToWorld(keyframes.Last().Value.Translation) != mass.Position) splineDirty = true; } // Perform simulation in substeps to have better stability - _time += Time::Update.DeltaTime.GetTotalSeconds(); + _time += Time::Physics.DeltaTime.GetTotalSeconds(); + float stretchLimit = StretchLimit + 1; while (_time > substepTime) { // Verlet integration - // [Reference: https://en.wikipedia.org/wiki/Verlet_integration] - const Vector3 force = gravity + AdditionalForce; + const Vector3 force = (gravity + AdditionalForce) * (substepTime * substepTime); for (int32 i = 0; i < keyframesCount; i++) { auto& mass = _masses[i]; + Vector3 position = mass.Position; if (mass.Unconstrained) { - const Vector3 velocity = mass.Position - mass.PrevPosition; - mass.PrevPosition = mass.Position; - mass.Position = mass.Position + velocity + (substepTimeSqr * force); - keyframes[i].Value.Translation = splineTransform.WorldToLocal(mass.Position); + const Vector3 velocity = (mass.Position - mass.PrevPosition) * (1 - Drag); + mass.Position += velocity + force; } + mass.PrevPosition = position; } // Distance constraint @@ -108,7 +106,11 @@ void SplineRopeBody::Tick() auto& massB = _masses[i]; Vector3 offset = massB.Position - massA.Position; const Real distance = offset.Length(); - const Real scale = (distance - massB.SegmentLength) / Math::Max(distance, ZeroTolerance); + const Real minDistance = massB.SegmentLength; + const Real maxDistance = massB.SegmentLength * stretchLimit; + if ((distance <= maxDistance && distance >= minDistance) || distance <= ZeroTolerance) + continue; + const Real scale = (distance - maxDistance) / distance; if (massA.Unconstrained && massB.Unconstrained) { offset *= scale * 0.5f; @@ -134,7 +136,10 @@ void SplineRopeBody::Tick() auto& massB = _masses[i]; Vector3 offset = massB.Position - massA.Position; const Real distance = offset.Length(); - const Real scale = (distance - massB.SegmentLength * 2.0f) / Math::Max(distance, ZeroTolerance); + const Real maxDistance = massB.SegmentLength * stretchLimit * 2.0f; + if (distance <= ZeroTolerance) + continue; + const Real scale = (distance - maxDistance) / distance; if (massA.Unconstrained && massB.Unconstrained) { offset *= scale * 0.5f; diff --git a/Source/Engine/Physics/Actors/SplineRopeBody.h b/Source/Engine/Physics/Actors/SplineRopeBody.h index 049348cfe..cf92a8921 100644 --- a/Source/Engine/Physics/Actors/SplineRopeBody.h +++ b/Source/Engine/Physics/Actors/SplineRopeBody.h @@ -31,17 +31,29 @@ private: public: /// - /// The target actor too attach the rope end to. If unset the rope end will run freely. + /// The target actor to attach the rope end to. If unset the rope end will run freely. /// API_FIELD(Attributes="EditorOrder(0), DefaultValue(null), EditorDisplay(\"Rope\")") ScriptingObjectReference AttachEnd; /// - /// The world gravity scale applied to the rope. Can be used to adjust gravity force or disable it. + /// The world gravity scale. Can be used to adjust gravity force or disable it. /// API_FIELD(Attributes="EditorOrder(10), EditorDisplay(\"Rope\")") float GravityScale = 1.0f; + /// + /// The scale of velocity applied to rope. The greater the value, the more suppressed the movement. + /// + API_FIELD(Attributes="EditorOrder(15), EditorDisplay(\"Rope\"), Limit(0, 0.99f, 0.001f)") + float Drag = 0.1f; + + /// + /// The maximum bones stretch scale. The value of 0 means no stretch, 1 - can stretch up to double length. + /// + API_FIELD(Attributes = "EditorOrder(19), EditorDisplay(\"Rope\"), Limit(0)") + float StretchLimit = 0.0f; + /// /// The additional, external force applied to rope (world-space). This can be eg. wind force. ///