Find the tangent of a point on a cubic bezier curve
Here is fully tested code to copy and paste:
It draws approxidistant points along the curve, and it draws the tangents.
bezierInterpolation
finds the points
bezierTangent
finds the tangents
There are TWO VERSIONS of bezierInterpolation
supplied below:
bezierInterpolation
works perfectly.
altBezierInterpolation
is exactly the same, BUT it is written in an expanded, clear, explanatory manner. It makes the arithmetic much easier to understand.
Use either of those two routines: the results are identical.
In both cases, use bezierTangent
to find the tangents. (Note: Michal's fabulous code base here.)
A full example of how to use with drawRect:
is also included.
// MBBezierView.m original BY MICHAL stackoverflow #4058979
#import "MBBezierView.h"
CGFloat bezierInterpolation(
CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d) {
// see also below for another way to do this, that follows the 'coefficients'
// idea, and is a little clearer
CGFloat t2 = t * t;
CGFloat t3 = t2 * t;
return a + (-a * 3 + t * (3 * a - a * t)) * t
+ (3 * b + t * (-6 * b + b * 3 * t)) * t
+ (c * 3 - c * 3 * t) * t2
+ d * t3;
}
CGFloat altBezierInterpolation(
CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d)
{
// here's an alternative to Michal's bezierInterpolation above.
// the result is absolutely identical.
// of course, you could calculate the four 'coefficients' only once for
// both this and the slope calculation, if desired.
CGFloat C1 = ( d - (3.0 * c) + (3.0 * b) - a );
CGFloat C2 = ( (3.0 * c) - (6.0 * b) + (3.0 * a) );
CGFloat C3 = ( (3.0 * b) - (3.0 * a) );
CGFloat C4 = ( a );
// it's now easy to calculate the point, using those coefficients:
return ( C1*t*t*t + C2*t*t + C3*t + C4 );
}
CGFloat bezierTangent(CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d)
{
// note that abcd are aka x0 x1 x2 x3
/* the four coefficients ..
A = x3 - 3 * x2 + 3 * x1 - x0
B = 3 * x2 - 6 * x1 + 3 * x0
C = 3 * x1 - 3 * x0
D = x0
and then...
Vx = 3At2 + 2Bt + C */
// first calcuate what are usually know as the coeffients,
// they are trivial based on the four control points:
CGFloat C1 = ( d - (3.0 * c) + (3.0 * b) - a );
CGFloat C2 = ( (3.0 * c) - (6.0 * b) + (3.0 * a) );
CGFloat C3 = ( (3.0 * b) - (3.0 * a) );
CGFloat C4 = ( a ); // (not needed for this calculation)
// finally it is easy to calculate the slope element,
// using those coefficients:
return ( ( 3.0 * C1 * t* t ) + ( 2.0 * C2 * t ) + C3 );
// note that this routine works for both the x and y side;
// simply run this routine twice, once for x once for y
// note that there are sometimes said to be 8 (not 4) coefficients,
// these are simply the four for x and four for y,
// calculated as above in each case.
}
@implementation MBBezierView
- (void)drawRect:(CGRect)rect {
CGPoint p1, p2, p3, p4;
p1 = CGPointMake(30, rect.size.height * 0.33);
p2 = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
p3 = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));
p4 = CGPointMake(-30 + CGRectGetMaxX(rect), rect.size.height * 0.66);
[[UIColor blackColor] set];
[[UIBezierPath bezierPathWithRect:rect] fill];
[[UIColor redColor] setStroke];
UIBezierPath *bezierPath = [[[UIBezierPath alloc] init] autorelease];
[bezierPath moveToPoint:p1];
[bezierPath addCurveToPoint:p4 controlPoint1:p2 controlPoint2:p3];
[bezierPath stroke];
[[UIColor brownColor] setStroke];
// now mark in points along the bezier!
for (CGFloat t = 0.0; t <= 1.00001; t += 0.05) {
[[UIColor brownColor] setStroke];
CGPoint point = CGPointMake(
bezierInterpolation(t, p1.x, p2.x, p3.x, p4.x),
bezierInterpolation(t, p1.y, p2.y, p3.y, p4.y));
// there, use either bezierInterpolation or altBezierInterpolation,
// identical results for the position
// just draw that point to indicate it...
UIBezierPath *pointPath =
[UIBezierPath bezierPathWithArcCenter:point
radius:5 startAngle:0 endAngle:2*M_PI clockwise:YES];
[pointPath stroke];
// now find the tangent if someone on stackoverflow knows how
CGPoint vel = CGPointMake(
bezierTangent(t, p1.x, p2.x, p3.x, p4.x),
bezierTangent(t, p1.y, p2.y, p3.y, p4.y));
// the following code simply draws an indication of the tangent
CGPoint demo = CGPointMake( point.x + (vel.x*0.3),
point.y + (vel.y*0.33) );
// (the only reason for the .3 is to make the pointers shorter)
[[UIColor whiteColor] setStroke];
UIBezierPath *vp = [UIBezierPath bezierPath];
[vp moveToPoint:point];
[vp addLineToPoint:demo];
[vp stroke];
}
}
@end
to draw that class...
MBBezierView *mm = [[MBBezierView alloc]
initWithFrame:CGRectMake(400,20, 600,700)];
[mm setNeedsDisplay];
[self addSubview:mm];
Here are the two routines to calculate approximately equidistant points, and the tangents of those, along a bezier cubic.
For clarity and reliability, these routines are written in the simplest, most explanatory, way possible.
CGFloat bezierPoint(CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d)
{
CGFloat C1 = ( d - (3.0 * c) + (3.0 * b) - a );
CGFloat C2 = ( (3.0 * c) - (6.0 * b) + (3.0 * a) );
CGFloat C3 = ( (3.0 * b) - (3.0 * a) );
CGFloat C4 = ( a );
return ( C1*t*t*t + C2*t*t + C3*t + C4 );
}
CGFloat bezierTangent(CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d)
{
CGFloat C1 = ( d - (3.0 * c) + (3.0 * b) - a );
CGFloat C2 = ( (3.0 * c) - (6.0 * b) + (3.0 * a) );
CGFloat C3 = ( (3.0 * b) - (3.0 * a) );
CGFloat C4 = ( a );
return ( ( 3.0 * C1 * t* t ) + ( 2.0 * C2 * t ) + C3 );
}
The four precalculated values, C1 C2 C3 C4, are sometimes called the coefficients of the bezier. (Recall that a b c d are usually called the four control points.)
Of course, t runs from 0 to 1, for example every 0.05.
Simply call these routines once for X, and then once separately for Y.
Hope it helps someone!
Important facts:
(1) It is an absolute fact that: unfortunately, there is, definitely, NO method, provided by Apple, to extract points from a UIBezierPath. True as of 2019.
(2) Don't forget it's as easy as pie to animate something along a UIBezierPath. Google many examples.
(3) Many ask, "Can't CGPathApply be used to extract the points from a UIBezierPath?" No, CGPathApply is totally unrelated: it simply gives you a list of your "instructions in making any path" (so, "start here", "draw a straight line to this point", etc etc.) The name is confusing but CGPathApply is totally unrelated to bezier paths.
For game programmers - as @Engineer points out you may well want the normal of the tangent, fortunately Apple has vector math built-in:
https://developer.apple.com/documentation/accelerate/simd/working_with_vectors
https://developer.apple.com/documentation/simd/2896658-simd_normalize
Calculate the slope of a tangent of an arbitrary point on a quadratic curve in Javascript
This will return the normalised tangent at the unit position for a 2nd and 3rd order curve.
See this answer for more detailed usage of the object below.
const geom = (()=>{
function Vec(x,y){
this.x = x;
this.y = y;
};
function Bezier(p1,p2,cp1,cp2){
this.p1 = p1;
this.p2 = p2;
this.cp1 = cp1;
this.cp2 = cp2;
}
Bezier.prototype = {
//======================================================================================
// single dimension polynomials for 2nd (a,b,c) and 3rd (a,b,c,d) order bezier
//======================================================================================
// for quadratic f(t) = a(1-t)^2+2b(1-t)t+ct^2
// = a+2(-a+b)t+(a-2b+c)t^2
// The derivative f'(t) = 2(1-t)(b-a)+2(c-b)t
//======================================================================================
// for cubic f(t) = a(1-t)^3 + 3bt(1-t)^2 + 3c(1-t)t^2 + dt^3
// = a+(-2a+3b)t+(2a-6b+3c)t^2+(-a+3b-3c+d)t^3
// The derivative f'(t) = -3a(1-t)^2+b(3(1-t)^2-6(1-t)t)+c(6(1-t)t-3t^2) +3dt^2
// The 2nd derivative f"(t) = 6(1-t)(c-2b+a)+6t(d-2c+b)
//======================================================================================
p1 : undefined,
p2 : undefined,
cp1 : undefined,
cp2 : undefined,
tangentAsVec (position, vec ) {
var a, b, c, u;
if (vec === undefined) { vec = new Vec(); }
if (this.cp2 === undefined) {
a = (1-position) * 2;
b = position * 2;
vec.x = a * (this.cp1.x - this.p1.x) + b * (this.p2.x - this.cp1.x);
vec.y = a * (this.cp1.y - this.p1.y) + b * (this.p2.y - this.cp1.y);
}else{
a = (1-position)
b = 6 * a * position; // (6*(1-t)*t)
a *= 3 * a; // 3 * ( 1 - t) ^ 2
c = 3 * position * position; // 3 * t ^ 2
vec.x = -this.p1.x * a + this.cp1.x * (a - b) + this.cp2.x * (b - c) + this.p2.x * c;
vec.y = -this.p1.y * a + this.cp1.y * (a - b) + this.cp2.y * (b - c) + this.p2.y * c;
}
u = Math.sqrt(vec.x * vec.x + vec.y * vec.y);
vec.x /= u;
vec.y /= u;
return vec;
},
}
return { Vec, Bezier,}
})()
Usage for 2nd order bezier
// ? represents any value
var p1 = new geom.Vec(?,?); // start point
var p2 = new geom.Vec(?,?); // end point
var cp1 = new geom.Vec(?,?); //control point
var bez2 = new geom.Bezier(p1,p2,cp1); // create 2nd order bezier
var t = ?;
var tangent = bez2.tangentAsVec(t);
Usage for 3rd order bezier
// ? represents any value
var p1 = new geom.Vec(?,?); // start point
var p2 = new geom.Vec(?,?); // end point
var cp1 = new geom.Vec(?,?); // 1st control point
var cp2 = new geom.Vec(?,?); // 2nd control point
var bez3 = new geom.Bezier(p1,p2,cp1,cp2); // create 3rd order bezier
var t = ?;
var tangent = bez2.tangentAsVec(t);
Find a point, a given distance, along a simple cubic bezier curve. (On an iPhone!)
There's some simple math behind calculating the positions, you can read about it in every paper discussing Bézier curves, even on wikipedia. Anyway, I can relate to everybody who's in trouble to actually implement it in code, so I wrote this sample UIView as it's probably the easiest way to get you started.
#import "MBBezierView.h"
CGFloat bezierInterpolation(CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d) {
CGFloat t2 = t * t;
CGFloat t3 = t2 * t;
return a + (-a * 3 + t * (3 * a - a * t)) * t
+ (3 * b + t * (-6 * b + b * 3 * t)) * t
+ (c * 3 - c * 3 * t) * t2
+ d * t3;
}
@implementation MBBezierView
- (void)drawRect:(CGRect)rect {
CGPoint p1, p2, p3, p4;
p1 = CGPointMake(30, rect.size.height * 0.33);
p2 = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
p3 = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));
p4 = CGPointMake(-30 + CGRectGetMaxX(rect), rect.size.height * 0.66);
[[UIColor blackColor] set];
[[UIBezierPath bezierPathWithRect:rect] fill];
[[UIColor redColor] setStroke];
UIBezierPath *bezierPath = [[[UIBezierPath alloc] init] autorelease];
[bezierPath moveToPoint:p1];
[bezierPath addCurveToPoint:p4 controlPoint1:p2 controlPoint2:p3];
[bezierPath stroke];
[[UIColor brownColor] setStroke];
for (CGFloat t = 0.0; t <= 1.00001; t += 0.05) {
CGPoint point = CGPointMake(bezierInterpolation(t, p1.x, p2.x, p3.x, p4.x), bezierInterpolation(t, p1.y, p2.y, p3.y, p4.y));
UIBezierPath *pointPath = [UIBezierPath bezierPathWithArcCenter:point radius:5 startAngle:0 endAngle:2*M_PI clockwise:YES];
[pointPath stroke];
}
}
@end
This is what I get:
Calculate cubic bezier T value where tangent is perpendicular to anchor line
Let's assume you got a 1D cubic Bézier curve with P0 = 0
and P3 = 1
then the curve is:
P(t) = b0,3(t)*0 + b1,3(t)*P1 + b2,3(t)*P2 + b3,3(t)*1
Where bi,3(t)
are the Bernstein polynomials of degree 3. Then we're looking for the value of t
where this P(t)
is minimal and maximal, so we derive:
P'(t) = b1,3'(t)*P1 + b2,3'(t)*P2 + b3,3'(t)
= (3 - 12t + 9t^2)*P1 + (6t - 9t^2)*P2 + 3t^2
= 0
This has a closed-form but nontrivial solution. According to WolframAlpha, when 3P1 - 3P2 +1 != 0
it's:
t = [2*P1 - P2 +/- sqrt(P1^2-P1*P2-P1+P2^2)] / (3*P1 - 3*P2 + 1)
Otherwise it's:
t = 3P1 / (6P1 - 2)
For a general n-dimensional cubic Bézier P0*, P1*, P2*, P3* compute:
P1 = proj(P1*, P03*) / |P3* - P0*|
P2 = proj(P2*, P03*) / |P3* - P0*|
Where proj(P, P03*)
is the signed distance from P0*
to the point P
projected on the line passing through P0*
and P3*
.
(I haven't checked this, so please confirm there is nothing wrong in my reasoning.)
Draw tangents and normal to each point in Bezier curve opengl
Though P'(t)
is sometimes called the tangent, it is actually the derivative of the curve, aka the velocity. If the curve is in the 2d space and its points are measured in meters, say, then the units of P'(t)
will be in meters/second. It does not make sense to draw a line between '5 meters' and '6 meters/second' because they are points in different spaces.
What you should do is to draw a line between 'the point on the curve' and 'where the object would be if it was detached from the curve and continued to move at its current velocity for 1 second'. That is between P(t)
and P(t) + dt * P'(t)
.
Scaling tangent lines in a bezier curve
The velocity at any point B(t)
is the derivative vector B'(t)
so you got that part right, but the derivative (remember not to call it the tangent: tangents are true lines without a start or end point) at a point tells you the velocity "over one unit of time". And inconveniently, an entire Bezier curve only covers a single unit of time (with the t
parameter running from 0 to 1)...
So, if we space our t
values using some smaller-than-one value, like your curve does by using 20 points (so a t
interval of 1/20
), we need to scale all those derivatives by 1/(point count)
too.
If we look at the unscaled derivatives, they are unwieldy and huge:
But if we scale them by 1/20 so that they represent the velocity vector for the time interval we're actually using, things look really good:
And we see an error between "where the true next point is" and "where the previous point plus its velocity vector says it'll be" based on curvature: the stronger the curvature, the further away from the "true point" a "previous point + velocity over time interval" will be, which we can mitigate by using more points.
If we use only 8 steps in our curve, with the derivatives scaled by 1/8, things don't look all that great:
If we bump that up to 15, with 1/15 scaling, things start to look better:
And while your curve with 20 points looks alright, let's look at what 50 points with 1/50 scaling gets us:
That's preeeetty good.
How to find a point (if any) on quadratic Bezier with a given tangent direction?
Using the notation given in http://pomax.github.io/bezierinfo/#extremities, a quadratic Bezier curve is given by:
B(t) = P1*(1-t)**2 + 2*P2*(1-t)*t + P3*t**2
Therefore, (by taking the derivative of B
with respect to t
) the tangent to the curve is given by
B'(t) = -2*P1*(1-t) + 2*P2*(1-2*t) + 2*P3*t
= 2*(P1 - 2*P2 + P3)*t + 2*(-P1 + P2)
Given some tangent direction V
, solve
B'(t) = V
for t
. If there is a solution, t = ts
, then the point on the Bezier curve which has tangent direction V
is given by B(ts)
.
We essentially want to know if two vectors (B'(t)
and V
) are parallel or anti-parallel. There is a trick to doing that.
Two vectors, X
and Y
, are perpendicular if their dot product is zero. If X = (a,b)
and Y = (c,d)
then the dot product of X
and Y
is given by
a*c + b*d
So, X
and Y
are parallel if X
and Y_perp
are perpendicular, where Y_perp
is a vector perpendicular to Y
.
In 2-dimensions, if in coordinates Y = (a,b)
then Y_perp = (-b, a)
. (There are two perpendicular vectors possible, but this one will do.) Notice that -- using the formula above -- the dot product of Y
and Y_perp
is
a*(-b) + b*(a) = 0
So indeed, this jibes with the claim that perpendicular vectors have a dot product equal to 0.
Now, to solve our problem: Let
B'(t) = (a*t+b, c*t+d)
V = (e, f)
Then B'(t)
is parallel (or anti-parallel) to V
if or whenB'(t)
is perpendicular to V_perp
, which occurs when
dot product((a*t+b, c*t+d), (-f, e)) = 0
-(a*t+b)*f + (c*t+d)*e = 0
We know a
, b
, c
, d
, e
and f
. Solve for t
. If t
lies between 0 and 1, then B(t)
is part of the Bezier curve segment between P1
and P3
.
Quadratic Bezier Curve: Calculate Tangent
Using your formula for B'(t)
, evaluated at t=1/2
, we get
B'(1/2) = -P0 + P2
From the look of your graph, P0 = (0,0) and P2 = (400,0). So
B'(1/2) = (400,0).
This is the "velocity" of a point traveling along B(t) at t=1/2.
(400,0) is a horizontal vector, with magnitude 400.
So all is as it should be. Since B'(t) is horizontal, it does have "slope" 0.
c++ Bezier curve Tangent direction issues
Since your Bezier curve is on the (x, z) plane, I don't quite understand why do you want to take a cross product between first derivative and the z axis (0, 0, 1). The 'normal' vector found in this way is bound to become zero when the first derivative goes in the (0, 0, 1) direction.
If you want to find the normal direction at any given point on the curve, you should evaluate the first derivative C'(t) and the 2nd derivative C"(t) first, then the normal vector vec(n) can be computed as
vec(n) = C' X C" / |C'||C"|
and your binormal vector vec(b) can be computed as vec(b) = vec(t) X vec(n) where vec(t) is the unit tangent vector vec(t)=C'(t)/|C'(t)|.
Related Topics
Changing Placeholder Text Color with Swift
iPhone Ad Hoc Build Using Xcode 4
How to Color a Uiimage in Swift
Find the Tangent of a Point on a Cubic Bezier Curve
How to Detect the Orientation of the Device on iOS
How to Get a Cgpoint from a Tapped Location
Segue in Skscene to Uiviewcontroller
Detect iOS Application About to Delete
Creating Framework That Requires (Depends On) Another Framework
Getting Time Elapsed in Objective-C
How to Prevent the iPhone Screen from Dimming or Turning Off While My Application Is Running
Best Way to Parse Url String to Get Values for Keys
How to Change Uibutton Image in Swift
How to Hide/Show Tab Bar of a View with a Navigation Bar in iOS
Xcode - Error Creating Lldb Target