Arc Parameterization
Since Bernstein’s or DeCasteljau’s cubic bezier curve would not give us evenly distributed points along with a curve.
For example, when BezierCurvePointAt(float t) is 0.25, it does not give us the position along with the curve at 25%!
Arc-Length Parameterization comes to rescue! to get evenly distributed point.
This lead artist to use accurate object animation along with a curve using different ease e.g. Linear, EaseIn, EaseOut and etc.
C#, Unity
Left is Bernstein's cubic bezier curve distribution and Right is set of evenly distributed points calculated with Arc Parameterization.
Get position at t which is normalized dist
Get position at t which is normalized dist
public void GetPositionAtDistance(float t, float[] arcLengthSet, float curveLength, ref Vector3 position)
{
// assume that t is [0~1]
float targetLength = t * curveLength;
int index = 0;
int low = 0;
int high = arcLengthSet.Length;
while (low < high)
{
//binary search on our stored lengths to find the largest length that's smaller then targetLength
index = low + ((high - low) / 2 | 0);
if (arcLengthSet[index] < targetLength)
{
low = index + 1;
}
else
{
high = index;
}
}
if (arcLengthSet[index] > targetLength && index > 0 && index < arcLengthSet.Length)
{
index--;
}
float lengthBefore = arcLengthSet[index];
float k = 0f; // just return or do the interpolation and return
if (lengthBefore == targetLength || (index + 1) >= arcLengthSet.Length) // TODO: use .AboutEqual instead?
{
k = index / arcLengthSet.Length;
}
else
{
k = (index + (targetLength - lengthBefore) / (arcLengthSet[index + 1] - lengthBefore)) / arcLengthSet.Length;
}
GetPositionAt(k, ref position);
}
Get arc length set
Get arc length set
public float[] GetArcLengthSet(int resolution, int segmentID = 0)
{
float[] set = new float[resolution + 1];
float lengthSq = 0f;
set[0] = 0f;
Vector3 prev = CurveSegments[segmentID].P0.transform.position; // BezierCurveMath.GetPoint(0f, CurveSegments[segmentID].P0.transform.position, CurveSegments[segmentID].P0_Tangent.transform.position, CurveSegments[segmentID].P1_Tangent.transform.position, CurveSegments[segmentID].P1.transform.position);
for (int i = 1; i <= resolution; i++)
{
float t = (float)i / resolution;
Vector3 curr = BezierCurveMath.GetPoint(t, CurveSegments[segmentID].P0.transform.position, CurveSegments[segmentID].P0_Tangent.transform.position, CurveSegments[segmentID].P1_Tangent.transform.position, CurveSegments[segmentID].P1.transform.position); // TODO: optimize with sq dist? lengthSq += Vector3.Distance(prev, curr);
prev = curr;
set[i] = lengthSq;
}
return set;
}