Files
FlaxEngine/Source/Engine/Level/Actors/Spline.cpp
2021-02-11 16:47:43 +01:00

513 lines
14 KiB
C++

// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#include "Spline.h"
#include "Engine/Serialization/Serialization.h"
#include "Engine/Animations/CurveSerialization.h"
#include <ThirdParty/mono-2.0/mono/metadata/object.h>
Spline::Spline(const SpawnParams& params)
: Actor(params)
{
}
bool Spline::GetIsLoop() const
{
return _loop;
}
void Spline::SetIsLoop(bool value)
{
if (_loop != value)
{
_loop = value;
UpdateSpline();
}
}
Vector3 Spline::GetSplinePoint(float time) const
{
Transform t;
Curve.Evaluate(t, time, _loop);
return _transform.LocalToWorld(t.Translation);
}
Vector3 Spline::GetSplineLocalPoint(float time) const
{
Transform t;
Curve.Evaluate(t, time, _loop);
return t.Translation;
}
Quaternion Spline::GetSplineOrientation(float time) const
{
Transform t;
Curve.Evaluate(t, time, _loop);
Quaternion::Multiply(_transform.Orientation, t.Orientation, t.Orientation);
t.Orientation.Normalize();
return t.Orientation;
}
Quaternion Spline::GetSplineLocalOrientation(float time) const
{
Transform t;
Curve.Evaluate(t, time, _loop);
return t.Orientation;
}
Vector3 Spline::GetSplineScale(float time) const
{
Transform t;
Curve.Evaluate(t, time, _loop);
return _transform.Scale * t.Scale;
}
Vector3 Spline::GetSplineLocalScale(float time) const
{
Transform t;
Curve.Evaluate(t, time, _loop);
return t.Scale;
}
Transform Spline::GetSplineTransform(float time) const
{
Transform t;
Curve.Evaluate(t, time, _loop);
return _transform.LocalToWorld(t);
}
Transform Spline::GetSplineLocalTransform(float time) const
{
Transform t;
Curve.Evaluate(t, time, _loop);
return t;
}
Vector3 Spline::GetSplineDirection(float time) const
{
return _transform.LocalToWorldVector(GetSplineLocalDirection(time));
}
Vector3 Spline::GetSplineLocalDirection(float time) const
{
if (Curve.GetKeyframes().Count() == 0)
return Vector3::Forward;
Transform t;
Curve.EvaluateFirstDerivative(t, time, _loop);
t.Translation.Normalize();
return t.Translation;
}
Vector3 Spline::GetSplinePoint(int32 index) const
{
CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), Vector3::Zero)
return _transform.LocalToWorld(Curve[index].Value.Translation);
}
Vector3 Spline::GetSplineLocalPoint(int32 index) const
{
CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), Vector3::Zero)
return Curve[index].Value.Translation;
}
Transform Spline::GetSplineTransform(int32 index) const
{
CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), Transform::Identity)
return _transform.LocalToWorld(Curve[index].Value);
}
Transform Spline::GetSplineLocalTransform(int32 index) const
{
CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), Transform::Identity)
return Curve[index].Value;
}
Transform Spline::GetSplineTangent(int32 index, bool isIn)
{
return _transform.LocalToWorld(GetSplineLocalTangent(index, isIn));
}
Transform Spline::GetSplineLocalTangent(int32 index, bool isIn)
{
CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), Transform::Identity)
const auto& k = Curve[index];
const auto& tangent = isIn ? k.TangentIn : k.TangentOut;
return tangent + k.Value;
}
int32 Spline::GetSplinePointsCount() const
{
return Curve.GetKeyframes().Count();
}
float Spline::GetSplineDuration() const
{
return Curve.GetLength();
}
float Spline::GetSplineLength() const
{
float sum = 0.0f;
const int32 slices = 20;
const float step = 1.0f / (float)slices;
Vector3 prevPoint = Vector3::Zero;
for (int32 i = 1; i < Curve.GetKeyframes().Count(); i++)
{
const auto& a = Curve[i = 1];
const auto& b = Curve[i];
const float length = Math::Abs(b.Time - a.Time);
Vector3 leftTangent, rightTangent;
AnimationUtils::GetTangent(a.Value.Translation, a.TangentOut.Translation, length, leftTangent);
AnimationUtils::GetTangent(b.Value.Translation, b.TangentIn.Translation, length, rightTangent);
// TODO: implement sth more analytical than brute-force solution
for (int32 slice = 0; slice < slices; slice++)
{
const float t = (float)slice * step;
Vector3 pos;
AnimationUtils::Bezier(a.Value.Translation, leftTangent, rightTangent, b.Value.Translation, t, pos);
pos *= _transform.Scale;
sum += Vector3::DistanceSquared(pos, prevPoint);
prevPoint = pos;
}
}
return Math::Sqrt(sum);
}
float Spline::GetSplineTime(int32 index) const
{
CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), 0.0f)
return Curve[index].Time;
}
namespace
{
void FindTimeClosestToPoint(const Vector3& point, const Spline::Keyframe& start, const Spline::Keyframe& end, float& bestDistanceSquared, float& bestTime)
{
// TODO: implement sth more analytical than brute-force solution
const int32 slices = 100;
const float step = 1.0f / (float)slices;
const float length = Math::Abs(end.Time - start.Time);
for (int32 i = 0; i <= slices; i++)
{
const float t = (float)i * step;
Transform result;
Spline::Keyframe::Interpolate(start, end, t, length, result);
const float distanceSquared = Vector3::DistanceSquared(point, result.Translation);
if (distanceSquared < bestDistanceSquared)
{
bestDistanceSquared = distanceSquared;
bestTime = start.Time + t * length;
}
}
}
}
float Spline::GetSplineTimeClosestToPoint(const Vector3& point) const
{
const int32 pointsCount = Curve.GetKeyframes().Count();
if (pointsCount == 0)
return 0.0f;
if (pointsCount == 1)
return Curve[0].Time;
const Vector3 localPoint = _transform.WorldToLocal(point);
float bestDistanceSquared = MAX_float;
float bestTime = 0.0f;
for (int32 i = 1; i < pointsCount; i++)
FindTimeClosestToPoint(localPoint, Curve[i - 1], Curve[i], bestDistanceSquared, bestTime);
return bestTime;
}
Vector3 Spline::GetSplinePointClosestToPoint(const Vector3& point) const
{
return GetSplinePoint(GetSplineTimeClosestToPoint(point));
}
void Spline::GetSplinePoints(Array<Vector3>& points) const
{
for (auto& e : Curve.GetKeyframes())
points.Add(_transform.LocalToWorld(e.Value.Translation));
}
void Spline::GetSplineLocalPoints(Array<Vector3>& points) const
{
for (auto& e : Curve.GetKeyframes())
points.Add(e.Value.Translation);
}
void Spline::GetSplinePoints(Array<Transform>& points) const
{
for (auto& e : Curve.GetKeyframes())
points.Add(_transform.LocalToWorld(e.Value));
}
void Spline::GetSplineLocalPoints(Array<Transform>& points) const
{
for (auto& e : Curve.GetKeyframes())
points.Add(e.Value);
}
void Spline::ClearSpline()
{
if (Curve.IsEmpty())
return;
Curve.Clear();
UpdateSpline();
}
void Spline::RemoveSplinePoint(int32 index, bool updateSpline)
{
CHECK(index >= 0 && index < GetSplinePointsCount());
Curve.GetKeyframes().RemoveAtKeepOrder(index);
if (updateSpline)
UpdateSpline();
}
void Spline::SetSplinePoint(int32 index, const Vector3& point, bool updateSpline)
{
CHECK(index >= 0 && index < GetSplinePointsCount());
Curve[index].Value.Translation = _transform.WorldToLocal(point);
if (updateSpline)
UpdateSpline();
}
void Spline::SetSplineLocalPoint(int32 index, const Vector3& point, bool updateSpline)
{
CHECK(index >= 0 && index < GetSplinePointsCount());
Curve[index].Value.Translation = point;
if (updateSpline)
UpdateSpline();
}
void Spline::SetSplineTransform(int32 index, const Transform& point, bool updateSpline)
{
CHECK(index >= 0 && index < GetSplinePointsCount());
Curve[index].Value = _transform.WorldToLocal(point);
if (updateSpline)
UpdateSpline();
}
void Spline::SetSplineLocalTransform(int32 index, const Transform& point, bool updateSpline)
{
CHECK(index >= 0 && index < GetSplinePointsCount());
Curve[index].Value = point;
if (updateSpline)
UpdateSpline();
}
void Spline::SetSplineTangent(int32 index, const Transform& point, bool isIn, bool updateSpline)
{
SetSplineLocalTangent(index, _transform.WorldToLocal(point), isIn, updateSpline);
}
void Spline::SetSplineLocalTangent(int32 index, const Transform& point, bool isIn, bool updateSpline)
{
CHECK(index >= 0 && index < GetSplinePointsCount());
auto& k = Curve[index];
auto& tangent = isIn ? k.TangentIn : k.TangentOut;
tangent = point - k.Value;
if (updateSpline)
UpdateSpline();
}
void Spline::SetSplinePointTime(int32 index, float time, bool updateSpline)
{
CHECK(index >= 0 && index < GetSplinePointsCount());
Curve[index].Time = time;
if (updateSpline)
UpdateSpline();
}
void Spline::AddSplinePoint(const Vector3& point, bool updateSpline)
{
const Keyframe k(Curve.IsEmpty() ? 0.0f : Curve.GetKeyframes().Last().Time + 1.0f, Transform(_transform.WorldToLocal(point)));
Curve.GetKeyframes().Add(k);
if (updateSpline)
UpdateSpline();
}
void Spline::AddSplineLocalPoint(const Vector3& point, bool updateSpline)
{
const Keyframe k(Curve.IsEmpty() ? 0.0f : Curve.GetKeyframes().Last().Time + 1.0f, Transform(point));
Curve.GetKeyframes().Add(k);
if (updateSpline)
UpdateSpline();
}
void Spline::AddSplinePoint(const Transform& point, bool updateSpline)
{
const Keyframe k(Curve.IsEmpty() ? 0.0f : Curve.GetKeyframes().Last().Time + 1.0f, _transform.WorldToLocal(point));
Curve.GetKeyframes().Add(k);
if (updateSpline)
UpdateSpline();
}
void Spline::AddSplineLocalPoint(const Transform& point, bool updateSpline)
{
const Keyframe k(Curve.IsEmpty() ? 0.0f : Curve.GetKeyframes().Last().Time + 1.0f, point);
Curve.GetKeyframes().Add(k);
if (updateSpline)
UpdateSpline();
}
void Spline::InsertSplinePoint(int32 index, float time, const Transform& point, bool updateSpline)
{
const Keyframe k(time, _transform.WorldToLocal(point));
Curve.GetKeyframes().Insert(index, k);
if (updateSpline)
UpdateSpline();
}
void Spline::InsertSplineLocalPoint(int32 index, float time, const Transform& point, bool updateSpline)
{
const Keyframe k(time, point);
Curve.GetKeyframes().Insert(index, k);
if (updateSpline)
UpdateSpline();
}
void Spline::SetTangentsLinear()
{
const int32 count = Curve.GetKeyframes().Count();
if (count < 2)
return;
if (_loop)
Curve[count - 1].Value = Curve[0].Value;
for (int32 i = 0; i < count; i++)
{
auto& k = Curve[i];
k.TangentIn = k.TangentOut = Transform::Identity;
}
UpdateSpline();
}
void Spline::SetTangentsSmooth()
{
const int32 count = Curve.GetKeyframes().Count();
if (count < 2)
return;
auto& keys = Curve.GetKeyframes();
const int32 last = count - 2;
if (_loop)
Curve[count - 1].Value = Curve[0].Value;
for (int32 i = 0; i <= last; i++)
{
auto& key = keys[i];
const auto& prevKey = keys[i == 0 ? (_loop ? last : 0) : i - 1];
const auto& nextKey = keys[i == last ? (_loop ? 0 : last) : i + 1];
const float prevTime = _loop && i == 0 ? key.Time : prevKey.Time;
const float nextTime = _loop && i == last ? key.Time : nextKey.Time;
const Vector3 slope = key.Value.Translation - prevKey.Value.Translation + nextKey.Value.Translation - key.Value.Translation;
const Vector3 tangent = slope / Math::Max(nextTime - prevTime, ZeroTolerance);
key.TangentIn.Translation = -tangent;
key.TangentOut.Translation = tangent;
}
UpdateSpline();
}
void Spline::UpdateSpline()
{
// Always keep last point in the loop
const int32 count = Curve.GetKeyframes().Count();
if (_loop && count > 1)
{
auto& first = Curve[0];
auto& last = Curve[count - 1];
last.Value = first.Value;
last.TangentIn = first.TangentIn;
last.TangentOut = first.TangentOut;
}
SplineUpdated();
}
void Spline::GetKeyframes(MonoArray* data)
{
Platform::MemoryCopy(mono_array_addr_with_size(data, sizeof(Keyframe), 0), Curve.GetKeyframes().Get(), sizeof(Keyframe) * Curve.GetKeyframes().Count());
}
void Spline::SetKeyframes(MonoArray* data)
{
const auto count = (int32)mono_array_length(data);
Curve.GetKeyframes().Resize(count, false);
Platform::MemoryCopy(Curve.GetKeyframes().Get(), mono_array_addr_with_size(data, sizeof(Keyframe), 0), sizeof(Keyframe) * count);
UpdateSpline();
}
#if USE_EDITOR
#include "Engine/Debug/DebugDraw.h"
namespace
{
void DrawSpline(Spline* spline, const Color& color, const Transform& transform, bool depthTest)
{
const int32 count = spline->Curve.GetKeyframes().Count();
if (count == 0)
return;
Spline::Keyframe* prev = spline->Curve.GetKeyframes().Get();
Vector3 prevPos = transform.LocalToWorld(prev->Value.Translation);
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(prevPos, 5.0f), color, 0.0f, depthTest);
for (int32 i = 1; i < count; i++)
{
Spline::Keyframe* next = prev + 1;
Vector3 nextPos = transform.LocalToWorld(next->Value.Translation);
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(nextPos, 5.0f), color, 0.0f, depthTest);
const float d = (next->Time - prev->Time) / 3.0f;
DEBUG_DRAW_BEZIER(prevPos, prevPos + prev->TangentOut.Translation * d, nextPos + next->TangentIn.Translation * d, nextPos, color, 0.0f, depthTest);
prev = next;
prevPos = nextPos;
}
}
}
void Spline::OnDebugDraw()
{
const Color color = GetSplineColor();
DrawSpline(this, color.AlphaMultiplied(0.7f), _transform, true);
// Base
Actor::OnDebugDraw();
}
void Spline::OnDebugDrawSelected()
{
const Color color = GetSplineColor();
DrawSpline(this, color.AlphaMultiplied(0.3f), _transform, false);
// Base
Actor::OnDebugDrawSelected();
}
#endif
void Spline::Serialize(SerializeStream& stream, const void* otherObj)
{
// Base
Actor::Serialize(stream, otherObj);
SERIALIZE_GET_OTHER_OBJ(Spline);
SERIALIZE_MEMBER(IsLoop, _loop);
SERIALIZE(Curve);
}
void Spline::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
{
// Base
Actor::Deserialize(stream, modifier);
DESERIALIZE_MEMBER(IsLoop, _loop);
DESERIALIZE(Curve);
// Initialize spline when loading data during gameplay
if (IsDuringPlay())
{
UpdateSpline();
}
}