For a cubic Bézier curve, with the usual four points a, b, c and d,
for a given value t,
how to most elegantly find the tangent at
Here goes my Swift implementation.
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.
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)