ios 求三次贝塞尔曲线上一点的切线
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4089443/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Find the tangent of a point on a cubic bezier curve
提问by Fattie
For a cubic Bézier curve, with the usual four points a, b, c and d,
对于三次贝塞尔曲线,通常有四个点 a、b、c 和 d,
for a given value t,
对于给定的值 t,
how to most elegantly find the tangentat that point?
如何最优雅地找到那个点的切线?
采纳答案by Fattie
Here is fully tested code to copy and paste:
这是经过全面测试的复制和粘贴代码:
It draws approxidistantpoints along the curve, andit draws the tangents.
它沿曲线绘制近似点,并绘制切线。
bezierInterpolation
finds the points
bezierInterpolation
找到点
bezierTangent
finds the tangents
bezierTangent
找到切线
There are TWO VERSIONSof bezierInterpolation
supplied below:
有两个版本的bezierInterpolation
下面提供:
bezierInterpolation
works perfectly.
bezierInterpolation
完美地工作。
altBezierInterpolation
is exactly the same, BUT it is written in an expanded, clear, explanatory manner. It makes the arithmetic much easier to understand.
altBezierInterpolation
完全一样,但它是以扩展的、清晰的、解释性的方式编写的。它使算术更容易理解。
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.)
在这两种情况下,都使用bezierTangent
来查找切线。(注意:这里是Michal 的精彩代码库。)
A full example of how to use with drawRect:
is also included.
drawRect:
还包括如何使用 with 的完整示例。
// 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 coefficientsof the bezier. (Recall that a b c d are usually called the four control points.)
四个预先计算的值 C1 C2 C3 C4 有时称为贝塞尔系数。(回想一下 abcd 通常被称为四个控制点。)
Of course, t runs from 0 to 1, for example every 0.05.
当然,t 从 0 到 1,例如每 0.05。
Simply call these routines once for X, and then once separately for Y.
只需为 X调用一次这些例程,然后为 Y 分别调用一次。
Hope it helps someone!
希望它可以帮助某人!
Important facts:
重要事实:
(1) It is an absolute factthat: unfortunately, there is, definitely, NO method, provided by Apple, to extract points from a UIBezierPath. True as of 2019.
(1) 这是一个绝对的事实:不幸的是,Apple 绝对没有提供从 UIBezierPath 中提取点的方法。截至 2019 年为真。
(2) Don't forget it's as easy as pie to animate something alonga UIBezierPath. Google many examples.
(2) 不要忘记沿UIBezierPath设置动画就像馅饼一样简单。谷歌很多例子。
(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.
(3) 很多人问,“CGPathApply 不能用于从 UIBezierPath 中提取点吗?” 不,CGPathApply 是完全不相关的:它只是给你一个“制作任何路径的说明”的列表(所以,“从这里开始”,“画一条直线到这一点”等)名称令人困惑,但 CGPathApply与贝塞尔路径完全无关。
For game programmers - as @Engineer points out you may well want the normal of the tangent, fortunately Apple has vector math built-in:
对于游戏程序员 - 正如@Engineer 指出的,您可能需要切线的法线,幸运的是 Apple 内置了矢量数学:
https://developer.apple.com/documentation/accelerate/simd/working_with_vectors
https://developer.apple.com/documentation/simd/2896658-simd_normalize
https://developer.apple.com/documentation/accelerate/simd/working_with_vectors
https://developer.apple.com/documentation/simd/2896658-simd_normalize
回答by Brad Larson
The tangent of a curve is simply its derivative. The parametric equation that Michal uses:
曲线的切线就是它的导数。Michal 使用的参数方程:
P(t) = (1 - t)^3 * P0 + 3t(1-t)^2 * P1 + 3t^2 (1-t) * P2 + t^3 * P3
should have a derivative of
应该有一个导数
dP(t) / dt = -3(1-t)^2 * P0 + 3(1-t)^2 * P1 - 6t(1-t) * P1 - 3t^2 * P2 + 6t(1-t) * P2 + 3t^2 * P3
Which, by the way, appears to be wrong in your earlier question. I believe you're using the slope for a quadratic Bezier curve there, not cubic.
顺便说一句,这在您之前的问题中似乎是错误的。我相信你在那里使用的是二次贝塞尔曲线的斜率,而不是三次曲线。
From there, it should be trivial to implement a C function that performs this calculation, like Michal has already provided for the curve itself.
从那里开始,实现一个执行此计算的 C 函数应该是微不足道的,就像 Michal 已经为曲线本身提供的那样。
回答by Adam
I found it too error-prone to use the supplied equations. Too easy to miss a subtle t or misplaced bracket.
我发现使用提供的方程太容易出错。太容易错过微妙的 t 或错位的括号。
By contrast, Wikipedia provides a much clearer, cleaner, derivative IMHO:
相比之下,维基百科提供了一个更清晰、更简洁的衍生恕我直言:
...which implements easily in code as:
...在代码中很容易实现:
3f * oneMinusT * oneMinusT * (p1 - p0)
+ 6f * t * oneMinusT * (p2 - p1)
+ 3f * t * t * (p3 - p2)
(assuming you have vector-minus configured in your language of choice; question isn't marked as ObjC specifically, and iOS now has several langs available)
(假设您在您选择的语言中配置了 vector-minus;问题没有特别标记为 ObjC,iOS 现在有几个可用的语言)
回答by Rick
I couldn't get any of this to work until I realized that for parametric equations, (dy/dt)/(dx/dt) = dy/dx
直到我意识到我不能得到任何的这个工作,对于参数方程,(DY / DT)/(DX / DT)= DY / DX
回答by SirEnder
Here goes my Swift implementation.
这是我的 Swift 实现。
Which I tried my best to optimize for speed, by eliminating all redundant math operations. i.e. make the minimal numbers of calls to math operations. And use the least possible number of multiplications (which are much more expensive than sums).
我尽力通过消除所有冗余数学运算来优化速度。即对数学运算进行最少数量的调用。并使用尽可能少的乘法(这比总和贵得多)。
There are 0 multiplications to create the bezier. Then 3 multiplications to get a point of bezier. And 2 multiplications to get a tangent to the bezier.
有 0 次乘法可以创建贝塞尔曲线。然后3次乘法得到一个贝塞尔曲线点。和 2 次乘法得到贝塞尔曲线的切线。
struct CubicBezier {
private typealias Me = CubicBezier
typealias Vector = CGVector
typealias Point = CGPoint
typealias Num = CGFloat
typealias Coeficients = (C: Num, S: Num, M: Num, L: Num)
let xCoeficients: Coeficients
let yCoeficients: Coeficients
static func coeficientsOfCurve(from c0: Num, through c1: Num, andThrough c2: Num, to c3: Num) -> Coeficients
{
let _3c0 = c0 + c0 + c0
let _3c1 = c1 + c1 + c1
let _3c2 = c2 + c2 + c2
let _6c1 = _3c1 + _3c1
let C = c3 - _3c2 + _3c1 - c0
let S = _3c2 - _6c1 + _3c0
let M = _3c1 - _3c0
let L = c0
return (C, S, M, L)
}
static func xOrYofCurveWith(coeficients coefs: Coeficients, at t: Num) -> Num
{
let (C, S, M, L) = coefs
return ((C * t + S) * t + M) * t + L
}
static func xOrYofTangentToCurveWith(coeficients coefs: Coeficients, at t: Num) -> Num
{
let (C, S, M, _) = coefs
return ((C + C + C) * t + S + S) * t + M
}
init(from start: Point, through c1: Point, andThrough c2: Point, to end: Point)
{
xCoeficients = Me.coeficientsOfCurve(from: start.x, through: c1.x, andThrough: c2.x, to: end.x)
yCoeficients = Me.coeficientsOfCurve(from: start.y, through: c1.y, andThrough: c2.y, to: end.y)
}
func x(at t: Num) -> Num {
return Me.xOrYofCurveWith(coeficients: xCoeficients, at: t)
}
func y(at t: Num) -> Num {
return Me.xOrYofCurveWith(coeficients: yCoeficients, at: t)
}
func dx(at t: Num) -> Num {
return Me.xOrYofTangentToCurveWith(coeficients: xCoeficients, at: t)
}
func dy(at t: Num) -> Num {
return Me.xOrYofTangentToCurveWith(coeficients: yCoeficients, at: t)
}
func point(at t: Num) -> Point {
return .init(x: x(at: t), y: y(at: t))
}
func tangent(at t: Num) -> Vector {
return .init(dx: dx(at: t), dy: dy(at: t))
}
}
Use like:
像这样使用:
let bezier = CubicBezier.init(from: .zero, through: .zero, andThrough: .zero, to: .zero)
let point02 = bezier.point(at: 0.2)
let point07 = bezier.point(at: 0.7)
let tangent01 = bezier.tangent(at: 0.1)
let tangent05 = bezier.tangent(at: 0.5)