Add Spline Rope Body actor for ropes, chains and cables physics
This commit is contained in:
@@ -314,6 +314,7 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
|
||||
contextMenu.AddButton("Add spline model", OnAddSplineModel);
|
||||
contextMenu.AddButton("Add spline collider", OnAddSplineCollider);
|
||||
contextMenu.AddButton("Add spline rope body", OnAddSplineRopeBody);
|
||||
}
|
||||
|
||||
private void OnAddSplineModel()
|
||||
@@ -337,6 +338,16 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
Editor.Instance.SceneEditing.Spawn(actor, Actor);
|
||||
}
|
||||
|
||||
private void OnAddSplineRopeBody()
|
||||
{
|
||||
var actor = new SplineRopeBody
|
||||
{
|
||||
StaticFlags = StaticFlags.None,
|
||||
Transform = Actor.Transform,
|
||||
};
|
||||
Editor.Instance.SceneEditing.Spawn(actor, Actor);
|
||||
}
|
||||
|
||||
internal static void OnSplineEdited(Spline spline)
|
||||
{
|
||||
var collider = spline.GetChild<SplineCollider>();
|
||||
|
||||
@@ -68,6 +68,7 @@ namespace FlaxEditor.SceneGraph
|
||||
CustomNodesTypes.Add(typeof(Spline), typeof(SplineNode));
|
||||
CustomNodesTypes.Add(typeof(SplineModel), typeof(ActorNode));
|
||||
CustomNodesTypes.Add(typeof(SplineCollider), typeof(ColliderNode));
|
||||
CustomNodesTypes.Add(typeof(SplineRopeBody), typeof(ActorNode));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
213
Source/Engine/Physics/Actors/SplineRopeBody.cpp
Normal file
213
Source/Engine/Physics/Actors/SplineRopeBody.cpp
Normal file
@@ -0,0 +1,213 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "SplineRopeBody.h"
|
||||
#include "Engine/Level/Actors/Spline.h"
|
||||
#include "Engine/Level/Scene/Scene.h"
|
||||
#include "Engine/Physics/Physics.h"
|
||||
#include "Engine/Engine/Time.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
|
||||
SplineRopeBody::SplineRopeBody(const SpawnParams& params)
|
||||
: Actor(params)
|
||||
{
|
||||
}
|
||||
|
||||
void SplineRopeBody::Tick()
|
||||
{
|
||||
if (!_spline || _spline->GetSplinePointsCount() < 2)
|
||||
return;
|
||||
PROFILE_CPU();
|
||||
|
||||
// TODO: add as configs
|
||||
const float SubstepTime = 0.02f;
|
||||
const int32 SolverIterations = 1;
|
||||
|
||||
// Cache data
|
||||
const Vector3 gravity = Physics::GetGravity() * GravityScale;
|
||||
auto& keyframes = _spline->Curve.GetKeyframes();
|
||||
const Transform splineTransform = _spline->GetTransform();
|
||||
const int32 keyframesCount = keyframes.Count();
|
||||
const float substepTime = SubstepTime;
|
||||
const float substepTimeSqr = substepTime * substepTime;
|
||||
bool splineDirty = false;
|
||||
|
||||
// Synchronize spline keyframes with simulated masses
|
||||
if (_masses.Count() > keyframesCount)
|
||||
_masses.Resize(keyframesCount);
|
||||
else
|
||||
{
|
||||
_masses.EnsureCapacity(keyframesCount);
|
||||
while (_masses.Count() < keyframesCount)
|
||||
{
|
||||
const int32 i = _masses.Count();
|
||||
auto& mass = _masses.AddOne();
|
||||
mass.PrevPosition = splineTransform.LocalToWorld(keyframes[i].Value.Translation);
|
||||
if (i != 0)
|
||||
mass.SegmentLength = Vector3::Distance(mass.PrevPosition, _masses[i - 1].PrevPosition);
|
||||
else
|
||||
mass.SegmentLength = 0.0f;
|
||||
}
|
||||
}
|
||||
{
|
||||
// Rope start
|
||||
auto& mass = _masses.First();
|
||||
mass.Position = mass.PrevPosition = GetPosition();
|
||||
mass.Unconstrained = false;
|
||||
if (splineTransform.LocalToWorld(keyframes.First().Value.Translation) != mass.Position)
|
||||
splineDirty = true;
|
||||
}
|
||||
for (int32 i = 1; i < keyframesCount; i++)
|
||||
{
|
||||
auto& mass = _masses[i];
|
||||
mass.Unconstrained = true;
|
||||
mass.Position = splineTransform.LocalToWorld(keyframes[i].Value.Translation);
|
||||
}
|
||||
if (AttachEnd)
|
||||
{
|
||||
// Rope end
|
||||
auto& mass = _masses.Last();
|
||||
mass.Position = mass.PrevPosition = 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();
|
||||
while (_time > substepTime)
|
||||
{
|
||||
// Verlet integration
|
||||
// [Reference: https://en.wikipedia.org/wiki/Verlet_integration]
|
||||
const Vector3 force = gravity + AdditionalForce;
|
||||
for (int32 i = 0; i < keyframesCount; i++)
|
||||
{
|
||||
auto& mass = _masses[i];
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Constraints solving
|
||||
for (int32 iteration = 0; iteration < SolverIterations; iteration++)
|
||||
{
|
||||
// Distance constraint
|
||||
for (int32 i = 1; i < keyframesCount; i++)
|
||||
{
|
||||
auto& massA = _masses[i - 1];
|
||||
auto& massB = _masses[i];
|
||||
Vector3 offset = massB.Position - massA.Position;
|
||||
const float distance = offset.Length();
|
||||
const float scale = (distance - massB.SegmentLength) / Math::Max(distance, ZeroTolerance);
|
||||
if (massA.Unconstrained && massB.Unconstrained)
|
||||
{
|
||||
offset *= scale * 0.5f;
|
||||
massA.Position += offset;
|
||||
massB.Position -= offset;
|
||||
}
|
||||
else if (massA.Unconstrained)
|
||||
{
|
||||
massA.Position += scale * offset;
|
||||
}
|
||||
else if (massB.Unconstrained)
|
||||
{
|
||||
massB.Position -= scale * offset;
|
||||
}
|
||||
}
|
||||
|
||||
// Stiffness constraint
|
||||
if (EnableStiffness)
|
||||
{
|
||||
for (int32 i = 2; i < keyframesCount; i++)
|
||||
{
|
||||
auto& massA = _masses[i - 2];
|
||||
auto& massB = _masses[i];
|
||||
Vector3 offset = massB.Position - massA.Position;
|
||||
const float distance = offset.Length();
|
||||
const float scale = (distance - massB.SegmentLength * 2.0f) / Math::Max(distance, ZeroTolerance);
|
||||
if (massA.Unconstrained && massB.Unconstrained)
|
||||
{
|
||||
offset *= scale * 0.5f;
|
||||
massA.Position += offset;
|
||||
massB.Position -= offset;
|
||||
}
|
||||
else if (massA.Unconstrained)
|
||||
{
|
||||
massA.Position += scale * offset;
|
||||
}
|
||||
else if (massB.Unconstrained)
|
||||
{
|
||||
massB.Position -= scale * offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_time -= substepTime;
|
||||
splineDirty = true;
|
||||
}
|
||||
|
||||
// Update spline and relevant components (eg. spline model)
|
||||
if (splineDirty)
|
||||
{
|
||||
for (int32 i = 0; i < keyframesCount; i++)
|
||||
keyframes[i].Value.Translation = splineTransform.WorldToLocal(_masses[i].Position);
|
||||
|
||||
_spline->UpdateSpline();
|
||||
}
|
||||
}
|
||||
|
||||
void SplineRopeBody::Serialize(SerializeStream& stream, const void* otherObj)
|
||||
{
|
||||
Actor::Serialize(stream, otherObj);
|
||||
|
||||
SERIALIZE_GET_OTHER_OBJ(SplineRopeBody);
|
||||
|
||||
SERIALIZE(AttachEnd);
|
||||
SERIALIZE(GravityScale);
|
||||
SERIALIZE(AdditionalForce);
|
||||
SERIALIZE(EnableStiffness);
|
||||
}
|
||||
|
||||
void SplineRopeBody::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
|
||||
{
|
||||
Actor::Deserialize(stream, modifier);
|
||||
|
||||
DESERIALIZE(AttachEnd);
|
||||
DESERIALIZE(GravityScale);
|
||||
DESERIALIZE(AdditionalForce);
|
||||
DESERIALIZE(EnableStiffness);
|
||||
}
|
||||
|
||||
void SplineRopeBody::OnEnable()
|
||||
{
|
||||
GetScene()->Ticking.Update.AddTick<SplineRopeBody, &SplineRopeBody::Tick>(this);
|
||||
|
||||
Actor::OnEnable();
|
||||
}
|
||||
|
||||
void SplineRopeBody::OnDisable()
|
||||
{
|
||||
Actor::OnDisable();
|
||||
|
||||
GetScene()->Ticking.Update.RemoveTick(this);
|
||||
}
|
||||
|
||||
void SplineRopeBody::OnParentChanged()
|
||||
{
|
||||
Actor::OnParentChanged();
|
||||
|
||||
_spline = Cast<Spline>(_parent);
|
||||
}
|
||||
|
||||
void SplineRopeBody::OnTransformChanged()
|
||||
{
|
||||
Actor::OnTransformChanged();
|
||||
|
||||
_box = BoundingBox(_transform.Translation, _transform.Translation);
|
||||
_sphere = BoundingSphere(_transform.Translation, 0.0f);
|
||||
}
|
||||
70
Source/Engine/Physics/Actors/SplineRopeBody.h
Normal file
70
Source/Engine/Physics/Actors/SplineRopeBody.h
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Level/Actor.h"
|
||||
#include "Engine/Scripting/ScriptingObjectReference.h"
|
||||
|
||||
class Spline;
|
||||
|
||||
/// <summary>
|
||||
/// Physical simulation actor for ropes, chains and cables represented by a spline.
|
||||
/// </summary>
|
||||
/// <seealso cref="Spline" />
|
||||
API_CLASS() class FLAXENGINE_API SplineRopeBody : public Actor
|
||||
{
|
||||
DECLARE_SCENE_OBJECT(SplineRopeBody);
|
||||
private:
|
||||
|
||||
struct Mass
|
||||
{
|
||||
Vector3 Position;
|
||||
float SegmentLength;
|
||||
Vector3 PrevPosition;
|
||||
bool Unconstrained;
|
||||
};
|
||||
|
||||
Spline* _spline = nullptr;
|
||||
float _time = 0.0f;
|
||||
Array<Mass> _masses;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// The target actor too attach the rope end to. If unset the rope end will run freely.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(0), DefaultValue(null), EditorDisplay(\"Rope\")")
|
||||
ScriptingObjectReference<Actor> AttachEnd;
|
||||
|
||||
/// <summary>
|
||||
/// The world gravity scale applied to the rope. Can be used to adjust gravity force or disable it.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(10), EditorDisplay(\"Rope\")")
|
||||
float GravityScale = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The additional, external force applied to rope (world-space). This can be eg. wind force.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(20), EditorDisplay(\"Rope\")")
|
||||
Vector3 AdditionalForce = Vector3::Zero;
|
||||
|
||||
/// <summary>
|
||||
/// If checked, the physics solver will use stiffness constraint for rope. It will be less likely to bend over and will preserve more it's shape.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(30), EditorDisplay(\"Rope\")")
|
||||
bool EnableStiffness = false;
|
||||
|
||||
private:
|
||||
|
||||
void Tick();
|
||||
|
||||
public:
|
||||
|
||||
// [Actor]
|
||||
void Serialize(SerializeStream& stream, const void* otherObj) override;
|
||||
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
|
||||
void OnEnable() override;
|
||||
void OnDisable() override;
|
||||
void OnTransformChanged() override;
|
||||
void OnParentChanged() override;
|
||||
};
|
||||
Reference in New Issue
Block a user