How to use the splines (Bezier curves) - DarkOceanInteractive/project-wendigo GitHub Wiki
Splines are curves that can be mathematically defined. They can be used for many things, such as smoothing animations up, or moving an object along a predefined curve.
Our implementation of splines uses Bézier curves. They are defined by control points. A Bézier curve defined by 3 control points is called a quadratic Bézier curve, and one defined by 4 control points is called a cubic Bézier curve. For animations, most of the time it is preferable to use (0, 0) and (1, 1) as first and last control points. That is why sometimes cubic Bézier curves are only defined using the middle 2 control points, such as in this tool which helps you fine-tune animations using a cubic Bézier curve: https://cubic-bezier.com/.
Note: Some spline calculations can be cached, which greatly reduces the amount of calculations needed. To achieve that caching, splines must be stored as instances of the spline classes.
Depending on the type of data you want to manipulate, you can use different spline classes:
-
FloatSpline
uses floats to define control points -
Vector2Spline
uses Vector2 to define control points -
Vector3Spline
uses Vector3 to define control points
Each spline class offers those methods:
float GetCurveLength()
Approximates the spline curve length.
float UnsafeGetCurveLength()
Same as GetCurveLength
, without checking cache integrity. This method should be preferred over GetCurveLength
when the spline is constant (not tweaked via inspector or script).
float GetTFromDistance(float distance)
Approximate the t value at which the distance from the beginning of the spline curve is equal to the given distance
.
float UnsafeGetTFromDistance(float distance)
Same as GetTFromDistance
, without checking cache integrity. This method should be preferred over GetTFromDistance
when the spline is constant (not tweaked via inspector or script).
void DrawGizmos(Vector3 origin)
Draw debug gizmos according to the spline gizmo options (editable in inspector). The origin
parameter defines the origin point in the world where to gizmos should be drawn from.
T* Compute(float t)
Compute the position on the spline at time t
.
T* Derivative(float t)
Compute the derivative of the spline at time t
.
U** Interpolate(U start, U end, float t)
Interpolate a point between start
and end
at time t
.
*T
type: can be either float
, Vector2
or Vector3
, depending on the spline class used.
**U
type: can be either float
, Vector2
or Vector3
, arbitrarily chosen (not depending on the spline class used).
A spline can be fully tweaked via the Unity inspector tab.
Spline inspector view
Debug visualization using gizmos
You can add and change control points defining your curve. The type of control points depends on the type of spline you're using.*
Gizmos are used by Unity to render debug information into the scene when enabled. Spline classes expose a DrawGizmos
method. You can call it from a MonoBehaviour
's OnDrawGizmos
or OnDrawGizmosSelected
method.
Spline gizmo options give a way to customize the level of details of the debug information shown:
By default, drawing gizmos will draw a *green sphere for each control point, and smaller green spheres and lines to describe the curve shape.
-
Step
: step oft
between two points on the displayed curve. -
Show coordinates
: when enabled, writes the x, y and z coordinates for each point on the displayed curve. -
Draw X axis
: when enabled, draws a blue curve for the X axis of the spline along the time axis. -
Draw Y axis
: when enabled, draws a red curve for the Y axis of the spline along the time axis. -
Draw derivative X axis
: when enabled, draws a purple curve for the X axis of the derivative of the spline along the time axis. -
Draw derivative Y axis
: when enabled, draws a yellow curve for the Y axis of the derivative of the spline along the time axis.
Spline objects cache some results internally to avoid unnecessary operations. One of the things the cache stores is a cumulative distance lookup table, storing for a certain number of t
values, the distance from the beginning of the curve up to the point on the curve at time t
.
The larger the lookup table is, the more precise some approximations will me, but the more memory and computations it will require.
The cumulative distance lookup table size
option allows you to adjust the size of this lookup table, but most of the time, 10 (which is the default value) is more than enough.
Object moving along a curve
public class MoveObjectExample : MonoBehaviour
{
[SerializeField]
private Vector3Spline _spline = Vector3Spline.CubicBezier(
Vector3.zero,
new Vector3(-2f, 0f, -2f),
new Vector3(1f, 4f, -1f),
new Vector3(2f, 2f, 2f));
[SerializeField] private float _translationDuration = 2f;
private Vector3 _startPosition;
private float _startTime;
private bool _isTranslating = false;
protected void Start()
{
this._startPosition = this.transform.position;
}
private void StartTranslation()
{
this._startTime = Time.time;
this._isTranslating = true;
}
protected void OnDrawGizmos()
{
this._spline.DrawGizmos(this._startPosition);
}
protected void OnGUI()
{
// Start the translation when clicking the button on screen.
if (GUILayout.Button("Start animation"))
this.StartTranslation();
}
protected void Update()
{
if (this._isTranslating)
{
float t = (Time.time - this._startTime) / this._translationDuration;
if (t <= 1f)
{
// Interpolate the object position using the spline.
Vector3 pointOnCurve = this._spline.Compute(t);
this.transform.position = this._startPosition + pointOnCurve;
}
else
{
this._isTranslating = false;
}
}
}
}
Object moving along a direction at a speed defined by a spline
public class MoveObjectAlongDirection : MonoBehaviour
{
[SerializeField]
private Vector2Spline _spline = new Vector2Spline(
new Vector2(0f, 1f),
new Vector2(0.3f, 1f),
new Vector2(0.5f, -0.5f),
new Vector2(0.7f, 1f),
new Vector2(1f, 1f));
[SerializeField] private float _translationDuration = 2f;
private Vector3 _startPosition;
private float _startTime;
protected void Start()
{
this._startTime = Time.time;
this._startPosition = this.transform.position;
}
protected void OnDrawGizmos()
{
this._spline.DrawGizmos(this.transform.position + new Vector3(0f, 1f, 0f));
}
protected void Update()
{
float t = (Time.time - this._startTime) / this._translationDuration;
if (t > 1f)
{
this._startTime += this._translationDuration;
t -= 1f;
}
// Interpolate the object position using the spline.
float yValue = this._spline.Interpolate(0f, 1f, t);
this.transform.position = this._startPosition + new Vector3(0f, yValue, 0f);
}
}
Note: this last example code can be reused to make smooth transitions between values such as colors or intensities.
Resources used during the development of this feature:
- General explanations and formulas for Bézier curves: https://en.wikipedia.org/wiki/B%C3%A9zier_curve
- An approach on Bézier curves derivatives: https://pages.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/Bezier/bezier-der.html
- Cubic Bézier curve visualization tool: https://www.desmos.com/calculator/ebdtbxgbq0
- Video explaining Bézier curves and some of their possible uses: https://youtu.be/aVwxzDHniEw