# How to achieve uniform speed of movement on a bezier curve?

| | August 6, 2015

I’m trying to move an image along Bezier curve. This is how I do it:

``````- (void)startFly
{
[self runAction:[CCSequence actions:
[CCBezierBy actionWithDuration:timeFlying bezier:[self getPathWithDirection:currentDirection]],
[CCCallFuncN actionWithTarget:self selector:@selector(endFly)],
nil]];

}
``````

My issue is that the image moves not uniformly. In the beginning it’s moving slowly and then it accelerates gradually and at the end it’s moving really fast. What should I do to get rid of this acceleration?

## 3 Responses to “How to achieve uniform speed of movement on a bezier curve?”

1. It is possible to approximate a solution to this problem for most parametric trajectories. The idea is the following: if you zoom deep enough on a curve, you cannot tell the curve itself from its tangent at that point.

By making this assumption, there is no need to precompute anything more than two vectors (three for cubic Bezier curves, etc.).

So for a curve `M(t)` we compute its tangent vector `dM/dt` at point `t`. The norm of this vector is `||dM/dt||` and thus the distance traveled for a duration `Δt` can be approximated as `||dM/dt||Δt`. It follows that a distance `L` is traveled for a duration `L/||dM/dt||`.

### Application: quadratic Bezier curve

If the control points of the Bezier curve are `A`, `B` and `C`, the trajectory can be expressed as:

``````M(t) = (1-t)²A + 2t(1-t)B + t²C
= t²(A - 2B + C) + t(-2A + 2B) + A
``````

So the derivative is:

``````dM/dt = t(2A - 4B + 2C) + (-2A + 2B)
``````

You just need to store vectors `v1 = 2A - 4B + 2C` and `v2 = -2A + 2B` somewhere. Then, for a given `t`, if you want to advance of a length `L`, you do:

``````t = t + L / length(t * v1 + v2)
``````

### Cubic Bezier curves

The same reasoning applies to a curve with four control points `A`, `B`, `C` and `D`:

``````M(t) = (1-t)³A + 3t(1-t)²B + 3t²(1-t)C + t³D
= t³(-A + 3B - 3C + D) + t²(3A - 6B + 3C) + t(-3A + 3B) + A
``````

The derivative is:

``````dM/dt = t²(-3A + 9B - 9C + 3D) + t(6A - 12B + 6C) + (-3A + 3B)
``````

We precompute `v1 = -3A + 9B - 9C + 3D`, `v2 = 6A - 12B + 6C` and `v3 = -3A + 3B` and the final formula is:

``````t = t + L / length(t * t * v1 + t * v2 + v3);
``````

### Accuracy issues

If you are running at a reasonable framerate, `L` (which should be computed according to the frame duration) will be sufficiently small for the approximation to work.

However, you may experience inaccuracies in extreme cases. If `L` is too large, you can do the computation piecewise, for instance using 10 parts:

``````for (int i = 0; i < 10; i++)
t = t + (L / 10) / length(t * v1 + v2);
``````
2. You need to reparamaterize the curve. The easiest way to do this is to calculate the arc lengths of several segments of the curve and use these to figure out where you should sample from. For example, maybe at t=0.5 (halfway through), you should pass s=0.7 to the curve to get the “halfway” position. You need to store a list of arc lengths of various curve segments to do this.

There are probably better ways, but here’s some very simple C# code I wrote to do this in my game. It should be easy to port to Objective C:

``````public sealed class CurveMap<TCurve> where TCurve : struct, ICurve
{
private readonly float[] _arcLengths;
private readonly float _ratio;
public float length { get; private set; }
public TCurve curve { get; private set; }
public bool isSet { get { return !length.isNaN(); } }
public int resolution { get { return _arcLengths.Length; } }

public CurveMap(int resolution)
{
_arcLengths = new float[resolution];
_ratio = 1f / resolution;
length = float.NaN;
}

public void set(TCurve c)
{
curve = c;
Vector2 o = c.sample(0);
float ox = o.X;
float oy = o.Y;
float clen = 0;
int nSamples = _arcLengths.Length;
for(int i = 0; i < nSamples; i++)
{
float t = (i + 1) * _ratio;
Vector2 p = c.sample(t);
float dx = ox - p.X;
float dy = oy - p.Y;
clen += (dx * dx + dy * dy).sqrt();
_arcLengths[i] = clen;
ox = p.X;
oy = p.Y;
}
length = clen;
}

public Vector2 sample(float u)
{
if(u <= 0) return curve.sample(0);
if(u >= 1) return curve.sample(1);

int index = 0;
int low = 0;
int high = resolution - 1;
float target = u * length;
float found = float.NaN;

// Binary search to find largest value <= target
while(low < high)
{
index = (low + high) / 2;
found = _arcLengths[index];
if (found < target)
low = index + 1;
else
high = index;
}

// If the value we found is greater than the target value, retreat
if (found > target)
index--;

if(index < 0) return curve.sample(0);
if(index >= resolution - 1) return curve.sample(1);

// Linear interpolation for index
float min = _arcLengths[index];
float max = _arcLengths[index + 1];
Debug.Assert(min <= target && max >= target);
float interp = (target - min) / (max - min);
Debug.Assert(interp >= 0 && interp <= 1);
return curve.sample((index + interp) * _ratio);
}
}
``````

Edit: It’s worth noting that this won’t give you the exact arc length, since it’s impossible to get the arc length of a cubic curve. All this does is estimate the length of the various segments. Depending on how long the curve is, you may need to increase the resolution to prevent it from changing speeds when it reaches a new segment. I usually use ~100, which I’ve never had a problem with.

3. I don’t know anything about cocos2, but a bezier curve is a kind of parametric equation, so you should be able to get your x and y values in terms of time.

• ### How To Submit a iPhone App to the Appstore

by on July 5, 2012 - 0 Comments

The process to develop an iPhone app is not as hard as one might think. The entry into the app store has been opened up to many new types of develope...

• ### Develop iphone Apps on Windows

by on March 7, 2011 - 13 Comments

Top 10 Ways to Develop an iphone/ipad app without a Mac #1. Code in Java For Java developers, there is a workaround: XMLVM. XMLVM cross-compiles byte...

• ### Iphone Android App Ideas: Getting Started

by on March 19, 2011 - 3 Comments

Smartphone Application Ideas: Getting Started Smartphone application developers are constantly on the move. And since overzealous developers stick ...