You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/CryEngine/CryCommon/Bezier.h

322 lines
11 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#ifndef __BEZIER_H__
#define __BEZIER_H__
#include <AnimTime.h>
#include <Serialization/IArchive.h>
#include <Serialization/Math.h>
struct SBezierControlPoint
{
SBezierControlPoint()
: m_value(0.0f)
, m_inTangent(ZERO)
, m_outTangent(ZERO)
, m_inTangentType(eTangentType_Auto)
, m_outTangentType(eTangentType_Auto)
, m_bBreakTangents(false)
{
}
enum ETangentType
{
eTangentType_Custom,
eTangentType_Auto,
eTangentType_Zero,
eTangentType_Step,
eTangentType_Linear,
};
void Serialize(Serialization::IArchive& ar)
{
ar(m_value, "value", "Value");
if (ar.IsOutput())
{
bool breakTangents = m_bBreakTangents;
ar(breakTangents, "breakTangents", "Break Tangents");
}
else
{
bool breakTangents = false;
ar(breakTangents, "breakTangents", "Break Tangents");
m_bBreakTangents = breakTangents;
}
if (ar.IsOutput())
{
ETangentType inTangentType = m_inTangentType;
ar(inTangentType, "inTangentType", "Incoming tangent type");
}
else
{
ETangentType inTangentType = eTangentType_Auto;
ar(inTangentType, "inTangentType", "Incoming tangent type");
m_inTangentType = inTangentType;
}
ar(m_inTangent, "inTangent", (m_inTangentType == eTangentType_Custom) ? "Incoming Tangent" : NULL);
if (ar.IsOutput())
{
ETangentType outTangentType = m_outTangentType;
ar(outTangentType, "outTangentType", "Outgoing tangent type");
}
else
{
ETangentType outTangentType = eTangentType_Auto;
ar(outTangentType, "outTangentType", "Outgoing tangent type");
m_outTangentType = outTangentType;
}
ar(m_outTangent, "outTangent", (m_outTangentType == eTangentType_Custom) ? "Outgoing Tangent" : NULL);
}
float m_value;
// For 1D Bezier only the Y component is used
Vec2 m_inTangent;
Vec2 m_outTangent;
ETangentType m_inTangentType : 4;
ETangentType m_outTangentType : 4;
bool m_bBreakTangents : 1;
};
struct SBezierKey
{
SBezierKey()
: m_time(0) {}
void Serialize(Serialization::IArchive& ar)
{
ar(m_time, "time", "Time");
ar(m_controlPoint, "controlPoint", "Control Point");
}
SAnimTime m_time;
SBezierControlPoint m_controlPoint;
};
namespace Bezier
{
inline float Evaluate(float t, float p0, float p1, float p2, float p3)
{
const float a = 1 - t;
const float aSq = a * a;
const float tSq = t * t;
return (aSq * a * p0) + (3.0f * aSq * t * p1) + (3.0f * a * tSq * p2) + (tSq * t * p3);
}
inline float EvaluateDeriv(float t, float p0, float p1, float p2, float p3)
{
const float a = 1 - t;
const float ta = t * a;
const float aSq = a * a;
const float tSq = t * t;
return 3.0f * ((-p2 * tSq) + (p3 * tSq) - (p0 * aSq) + (p1 * aSq) + 2.0f * ((-p1 * ta) + (p2 * ta)));
}
inline float EvaluateX(const float t, const float duration, const SBezierControlPoint& start, const SBezierControlPoint& end)
{
const float p0 = 0.0f;
const float p1 = p0 + start.m_outTangent.x;
const float p3 = duration;
const float p2 = p3 + end.m_inTangent.x;
return Evaluate(t, p0, p1, p2, p3);
}
inline float EvaluateY(const float t, const SBezierControlPoint& start, const SBezierControlPoint& end)
{
const float p0 = start.m_value;
const float p1 = p0 + start.m_outTangent.y;
const float p3 = end.m_value;
const float p2 = p3 + end.m_inTangent.y;
return Evaluate(t, p0, p1, p2, p3);
}
// Duration = (time at end key) - (time at start key)
inline float EvaluateDerivX(const float t, const float duration, const SBezierControlPoint& start, const SBezierControlPoint& end)
{
const float p0 = 0.0f;
const float p1 = p0 + start.m_outTangent.x;
const float p3 = duration;
const float p2 = p3 + end.m_inTangent.x;
return EvaluateDeriv(t, p0, p1, p2, p3);
}
inline float EvaluateDerivY(const float t, const SBezierControlPoint& start, const SBezierControlPoint& end)
{
const float p0 = start.m_value;
const float p1 = p0 + start.m_outTangent.y;
const float p3 = end.m_value;
const float p2 = p3 + end.m_inTangent.y;
return EvaluateDeriv(t, p0, p1, p2, p3);
}
// Find interpolation factor where 2D bezier curve has the given x value. Works only for curves where x is monotonically increasing.
// The passed x must be in range [0, duration]. Uses the Newton-Raphson root finding method. Usually takes 2 or 3 iterations.
//
// Note: This is for "1D" 2D bezier curves as used in TrackView. The curves are restricted by the curve editor to be monotonically increasing.
//
inline float InterpolationFactorFromX(const float x, const float duration, const SBezierControlPoint& start, const SBezierControlPoint& end)
{
float t = (x / duration);
const float epsilon = 0.00001f;
const uint maxSteps = 10;
for (uint i = 0; i < maxSteps; ++i)
{
const float currentX = EvaluateX(t, duration, start, end) - x;
if (fabs(currentX) <= epsilon)
{
break;
}
const float currentXDeriv = EvaluateDerivX(t, duration, start, end);
t -= currentX / currentXDeriv;
}
return t;
}
inline SBezierControlPoint CalculateInTangent(
float time, const SBezierControlPoint& point,
float leftTime, const SBezierControlPoint* pLeftPoint,
float rightTime, const SBezierControlPoint* pRightPoint)
{
SBezierControlPoint newPoint = point;
// In tangent X can never be positive
newPoint.m_inTangent.x = std::min(point.m_inTangent.x, 0.0f);
if (pLeftPoint)
{
switch (point.m_inTangentType)
{
case SBezierControlPoint::eTangentType_Custom:
{
// Need to clamp tangent if it is reaching over last point
const float deltaTime = time - leftTime;
if (deltaTime < -newPoint.m_inTangent.x)
{
if (newPoint.m_inTangent.x == 0)
{
newPoint.m_inTangent = Vec2(ZERO);
}
else
{
float scaleFactor = deltaTime / -newPoint.m_inTangent.x;
newPoint.m_inTangent.x = -deltaTime;
newPoint.m_inTangent.y *= scaleFactor;
}
}
}
break;
case SBezierControlPoint::eTangentType_Zero:
// Fall through. Zero for y is same as Auto, x is set to 0.0f
case SBezierControlPoint::eTangentType_Auto:
{
const SBezierControlPoint& rightPoint = pRightPoint ? *pRightPoint : point;
const float deltaTime = (pRightPoint ? rightTime : time) - leftTime;
if (deltaTime > 0.0f)
{
const float ratio = (time - leftTime) / deltaTime;
const float deltaValue = rightPoint.m_value - pLeftPoint->m_value;
const bool bIsZeroTangent = (point.m_inTangentType == SBezierControlPoint::eTangentType_Zero);
newPoint.m_inTangent = Vec2(-(deltaTime * ratio) / 3.0f, bIsZeroTangent ? 0.0f : -(deltaValue * ratio) / 3.0f);
}
else
{
newPoint.m_inTangent = Vec2(ZERO);
}
}
break;
case SBezierControlPoint::eTangentType_Linear:
newPoint.m_inTangent = Vec2((leftTime - time) / 3.0f,
(pLeftPoint->m_value - point.m_value) / 3.0f);
break;
}
}
return newPoint;
}
inline SBezierControlPoint CalculateOutTangent(
float time, const SBezierControlPoint& point,
float leftTime, const SBezierControlPoint* pLeftPoint,
float rightTime, const SBezierControlPoint* pRightPoint)
{
SBezierControlPoint newPoint = point;
// Out tangent X can never be negative
newPoint.m_outTangent.x = std::max(point.m_outTangent.x, 0.0f);
if (pRightPoint)
{
switch (point.m_outTangentType)
{
case SBezierControlPoint::eTangentType_Custom:
{
// Need to clamp tangent if it is reaching over next point
const float deltaTime = rightTime - time;
if (deltaTime < newPoint.m_outTangent.x)
{
if (newPoint.m_outTangent.x == 0)
{
newPoint.m_outTangent = Vec2(ZERO);
}
else
{
float scaleFactor = deltaTime / newPoint.m_outTangent.x;
newPoint.m_outTangent.x = deltaTime;
newPoint.m_outTangent.y *= scaleFactor;
}
}
}
break;
case SBezierControlPoint::eTangentType_Zero:
// Fall through. Zero for y is same as Auto, x is set to 0.0f
case SBezierControlPoint::eTangentType_Auto:
{
const SBezierControlPoint& leftPoint = pLeftPoint ? *pLeftPoint : point;
const float deltaTime = rightTime - (pLeftPoint ? leftTime : time);
if (deltaTime > 0.0f)
{
const float ratio = (rightTime - time) / deltaTime;
const float deltaValue = pRightPoint->m_value - leftPoint.m_value;
const bool bIsZeroTangent = (point.m_outTangentType == SBezierControlPoint::eTangentType_Zero);
newPoint.m_outTangent = Vec2((deltaTime * ratio) / 3.0f, bIsZeroTangent ? 0.0f : (deltaValue * ratio) / 3.0f);
}
else
{
newPoint.m_outTangent = Vec2(ZERO);
}
}
break;
case SBezierControlPoint::eTangentType_Linear:
newPoint.m_outTangent = Vec2((rightTime - time) / 3.0f,
(pRightPoint->m_value - point.m_value) / 3.0f);
break;
}
}
return newPoint;
}
}
#endif