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.

General concepts

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/.

Spline classes

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).

Inspector

A spline can be fully tweaked via the Unity inspector tab.

spline inspector view

Spline inspector view

Debug visualization using gizmos

Debug visualization using gizmos

Control points

You can add and change control points defining your curve. The type of control points depends on the type of spline you're using.*

Gizmo options

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 of t 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.

Cache options

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.

Usage examples

Move an object along a curve

Object moving along a curve

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;
            }
        }
    }
}

Make an object move in a direction at a speed defined by a spline

Object moving along a curve

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

Resources used during the development of this feature:

⚠️ **GitHub.com Fallback** ⚠️