fast locking in a parallel concurrentPerform swift loop - swift

I tried many different code to have a parallel loop faster than a sequential one.
My latest attempt :
startTime = CFAbsoluteTimeGetCurrent()
var totaliter = 0
for x in stride(from: -2.0, to: 2.0, by: delta) {
for y in stride(from: -2.0, to: 2.0, by: delta) {
let cx : Double = x
let cy : Double = y
var zx : Double = 0.0
var zy : Double = 0.0
var iteration : Int = 0
while (zx * zx + zy * zy < 4.0 && iteration < maxIter) {
let tmp = zx * zx - zy * zy + cx
zy = 2.0 * zx * zy + cy
zx = tmp
iteration += 1
totaliter += 1
}
}
}
print(totaliter)
print("\tTime : \(CFAbsoluteTimeGetCurrent() - startTime)")
this sequential code take 4s.
The following using swift-atomic code take 29s :
startTime = CFAbsoluteTimeGetCurrent()
let atomicInc = ManagedAtomic<UInt32>(0)
DispatchQueue.concurrentPerform(iterations: Int(4/delta)) { (xIter) in
for y in stride(from: -2.0, to: 2.0, by: delta) {
let x = -2.0 + (Double(xIter) * delta)
let cx : Double = x
let cy : Double = y
var zx : Double = 0.0
var zy : Double = 0.0
var iteration : Int = 0
while (zx * zx + zy * zy < 4.0 && iteration < maxIter) {
let tmp = zx * zx - zy * zy + cx
zy = 2.0 * zx * zy + cy
zx = tmp
iteration += 1
atomicInc.wrappingIncrement(ordering: AtomicUpdateOrdering.relaxed)
}
}
}
print("\n\tMeaningless number (race condition) : ",atomicInc.load(ordering: .relaxed))
print("\tTime : \(CFAbsoluteTimeGetCurrent() - startTime)")
Replacing the atomicInc.wrappingIncrement by a NSLock lock/unlock take forever, I never had the patience to let it finish. my guess is around 10mn and the cpu is 80% busy on "system" load (as opposed to user load)
I simply can't find a way to have a parallel loop that is faster than the sequential version. All my attempts either failed or had a race condition.
For reference, my previous attempt with a different computation was like this (and, yes, the sequential version was faster than the swift-atomic or the NSLock version, I assumed that drand48 was the culprit, but it seems I was wrong)
DispatchQueue.concurrentPerform(iterations: 100) { (_) in
for _ in 1...iteration / 100 {
let rand_x : Float = Float(drand48())
let rand_y : Float = Float(drand48())
let origin_dist : Float = rand_x * rand_x + rand_y * rand_y
if (origin_dist <= 1) {
atomicinside.wrappingIncrement(ordering: AtomicUpdateOrdering.relaxed)
}
}
}
How would you do a parallel loop that require updating a non-thread-local variable ? Anything will do as long as it's fast. I'm using concurrentPerform because I don't know any better, but I'm open to suggestions.
fun fact : the parallel version is slower while using 4~8x more CPU.

You should take the locking increment out of the inner loop and do a locking
totaliter += iteration

Related

Generate random Gaussian noise MTLTexture or MTLBuffer of size (width, height)

I am writing a real-time video filter application and for one of the algorithms I want to try out, I need to generate a random, gaussian univariate distributed buffer (or texture) based on the input source.
Coming from a Python background, the following few lines are running in about 0.15s (which is not real-time worthy but a lot faster than the Swift code I tried below):
h = 1170
w = 2532
with Timer():
noise = np.random.normal(size=w * h * 3)
plt.imshow(noise.reshape(w,h,3))
plt.show()
My Swift code try:
private func generateNoiseTextureBuffer(width: Int, height: Int) -> [Float] {
let w = Float(width)
let h = Float(height)
var noiseData = [Float](repeating: 0, count: width * height * 4)
for xi in (0 ..< width) {
for yi in (0 ..< height) {
let index = yi * width + xi
let x = Float(xi)
let y = Float(yi)
let random = GKRandomSource()
let gaussianGenerator = GKGaussianDistribution(randomSource: random, mean: 0.0, deviation: 1.0)
let randX = gaussianGenerator.nextUniform()
let randY = gaussianGenerator.nextUniform()
let scale = sqrt(2.0 * min(w, h) * (2.0 / Float.pi))
let rx = floor(max(min(x + scale * randX, w - 1.0), 0.0))
let ry = floor(max(min(y + scale * randY, h - 1.0), 0.0))
noiseData[index * 4 + 0] = rx + 0.5
noiseData[index * 4 + 1] = ry + 0.5
noiseData[index * 4 + 2] = 1
noiseData[index * 4 + 3] = 1
}
}
return noiseData
}
...
let noiseData = self.generateNoiseTextureBuffer(width: context.sourceColorTexture.width, height: context.sourceColorTexture.height)
let noiseDataSize = noiseData.count * MemoryLayout.size(ofValue: noiseData[0])
self.noiseBuffer = device.makeBuffer(bytes: noiseData, length: noiseDataSize)
How can I accomplish this fast and easily in Swift?

simd_quatF to euler angle

Im trying to convert my quats to euler, but out of x/y/z components, only my X has accurate value and y/z is incorrect :- ( can any1 have a look/help ?
func quatToEulerAngles(_ quat: simd_quatf) -> SIMD3<Double>{
var angles = SIMD3<Double>();
let qfloat = quat.vector
let q = SIMD4<Double>(Double(qfloat.x),Double(qfloat.y),Double(qfloat.z), Double(qfloat.w))
// roll (x-axis rotation)
let sinr_cosp : Double = 2.0 * (q.w * q.x + q.y * q.z);
let cosr_cosp : Double = 1.0 - 2.0 * (q.x * q.x + q.y * q.y);
angles.x = atan2(sinr_cosp, cosr_cosp);
// pitch (y-axis rotation)
let sinp : Double = 2 * (q.w * q.y - q.z * q.x);
if (abs(sinp) >= 1){
angles.y = copysign(Double.pi / 2, sinp); // use 90 degrees if out of range
}
else{
angles.y = asin(sinp);
}
// yaw (z-axis rotation)
let siny_cosp : Double = 2 * (q.w * q.z + q.x * q.y);
let cosy_cosp : Double = 1 - 2 * (q.y * q.y + q.z * q.z);
angles.z = atan2(siny_cosp, cosy_cosp);
return angles;
}
Wiki example converted to swifht.
TIA
My solution would be to let the (SceneKit) library do it:
func quatToEulerAngles(_ quat: simd_quatf) -> SIMD3<Float>{
let n = SCNNode()
n.simdOrientation = quat
return n.simdEulerAngles
}
I took a look at and converted it to Swift,
https://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/
It works for me.
func quatToEulerAngles(_ quat: simd_quatf) -> SIMD3<Float>{
var angles = SIMD3<Float>();
let qfloat = quat.vector
// heading = x, attitude = y, bank = z
let test = qfloat.x*qfloat.y + qfloat.z*qfloat.w;
if (test > 0.499) { // singularity at north pole
angles.x = 2 * atan2(qfloat.x,qfloat.w)
angles.y = (.pi / 2)
angles.z = 0
return angles
}
if (test < -0.499) { // singularity at south pole
angles.x = -2 * atan2(qfloat.x,qfloat.w)
angles.y = -(.pi / 2)
angles.z = 0
return angles
}
let sqx = qfloat.x*qfloat.x;
let sqy = qfloat.y*qfloat.y;
let sqz = qfloat.z*qfloat.z;
angles.x = atan2(2*qfloat.y*qfloat.w-2*qfloat.x*qfloat.z , 1 - 2*sqy - 2*sqz)
angles.y = asin(2*test)
angles.z = atan2(2*qfloat.x*qfloat.w-2*qfloat.y*qfloat.z , 1 - 2*sqx - 2*sqz)
return angles
}

Splitting method algorithm

(x^3 - 2x^2 - 5) is my equation.First of all I have two values like x = 2 and x = 4. My first two values must be count for equation and them results must be negative and positive each time. And second step is (2 + 4) / 2 = 3 this time x = 3 in equation. And the math operation continue with last one positive value and one negative value. I try this
var x = 2.0
var equation = pow(x, 3) - 2 * pow(x, 2) - 5
switch x {
case x : 2
equation = pow(x, 3) - 2 * pow(x, 2) - 5
case x : 4
equation = pow(x, 3) - 2 * pow(x, 2) - 5
default:
0
}
print(equation)
How can I assign first two values like 2 and 4 for one var x ?
Apparently you want to implement the bisection method to find the (real) solution (“root”) of an equation. The first step is to define that equation as a function, so that it can be evaluated at various points:
func f(_ x: Double) -> Double {
return pow(x, 3) - 2 * pow(x, 2) - 5
}
Then you need two variables for the left and right boundary of the current interval. These must be chosen such that f(x) has opposite signs at the boundaries. In your example:
var xleft = 2.0 // f(xleft) < 0
var xright = 4.0 // f(xright) > 0
Now you can start the iteration: Compute f(x) at the midpoint of the current interval, and replace xleft of xright, depending on whether f(x) is negative or positive. Continue until the approximation is good enough for your purposes:
let eps = 0.0000001 // Desired precision
let leftSign = f(xleft).sign
repeat {
let x = (xleft + xright)/2.0
let y = f(x)
if y == 0 {
xleft = x
break
} else if y.sign == leftSign {
xleft = x
} else {
xright = x
}
// print(xleft, xright)
} while xright - xleft > eps
// Print approximate solution:
print(xleft)
The next step would be to implement the bisection method itself as a function:
func bisect(_ f: ((Double) -> Double), xleft: Double, xright: Double, eps: Double = 1.0e-6) -> Double {
let yleft = f(xleft)
let yright = f(xright)
precondition(yleft * yright <= 0, "f must have opposite sign at the boundaries")
var xleft = xleft
var xright = xright
repeat {
let x = (xleft + xright)/2.0
let y = f(x)
if y == 0 {
return x
} else if y.sign == yleft.sign {
xleft = x
} else {
xright = x
}
} while xright - xleft > eps
return (xleft + xright)/2.0
}
so that it can be used with arbitrary equations:
let sol1 = bisect({ x in pow(x, 3) - 2 * pow(x, 2) - 5 }, xleft: 2.0, xright: 4.0)
print(sol1) // 2.690647602081299
let sol2 = bisect({ x in cos(x/2)}, xleft: 3.0, xright: 4.0, eps: 1.0e-15)
print(sol2) // 3.1415926535897936

Swift CORDIC Algorithm gives constant answer

I tried to translate the MATLAB language on Cordic wikipedia webpage
However when I type those:
print(cordic(beta: Double.pi/9, n: 20))
print(cordic(beta: Double.pi/8, n: 20))
I get
[-0.17163433840184755, 0.98516072489744066]
[-0.17163433840184755, 0.98516072489744066]
It's always giving me a constant answer. Why? I'm sure that the "angle" and "Kvalues" arrays are properly calculated.
Here's the code:
import Foundation
var angles: [Double] = []
for i: Double in stride(from: 0, to: 27, by: 1) {
angles.append(atan(pow(2, -i)))
}
var Kvalues: [Double] = []
for i: Double in stride(from: 0, to: 23, by: 1) {
Kvalues.append(1/sqrt(abs(Double(1) + pow(2,-2 * i))))
if i > 0 {
Kvalues[Kvalues.count - 1] *= Kvalues[Kvalues.count - 2]
}
}
func min(_ a: Int, _ b: Int) -> Int {
return a > b ? b : a
}
func cordic(beta: Double, n: Int) -> [Double] {
var beta1 = beta
let Kn = Kvalues[min(n, Kvalues.count - 1)]
var v: [Double] = [1,0]
var poweroftwo: Double = 1
var angle = angles[0]
for j in 0 ..< n {
let sigma: Double = beta < 0 ? -1 : 1
let factor: Double = sigma * poweroftwo
v = [v[0] - v[1] * factor, v[1] + v[0] * factor]
beta1 -= sigma * angle
poweroftwo /= 2
angle = j + 2 > angles.count ? angle / 2 : angles[j + 2]
}
return [v[0] * Kn, v[1] * Kn]
}
print(cordic(beta: Double.pi/9, n: 20))
print(cordic(beta: Double.pi/8, n: 20))
You get the same result for different input because in
let sigma: Double = beta < 0 ? -1 : 1
beta should be beta1, which is the local variable that is
updated in the loop.
But even after fixing that the results are not correct, and that is
caused by two "off-by-one" index errors. The arrays in the algorithm
description are 1-based and Swift arrays are 0-based. So
let Kn = Kvalues[min(n, Kvalues.count - 1)]
// should be
let Kn = Kvalues[min(n-1, Kvalues.count - 1)]
and
angle = j + 2 > angles.count ? angle / 2 : angles[j + 2]
// should be
angle = j + 1 >= angles.count ? angle / 2 : angles[j + 1]
The angles and Kvalues arrays should be defined for i from 0 up to and including 27 resp. 23.
Finally, there is no need to define your own min function as there is one in the Swift standard library.
Putting it all together your code would be:
var angles: [Double] = []
for i: Double in stride(from: 0, through: 27, by: 1) {
angles.append(atan(pow(2, -i)))
}
var Kvalues: [Double] = []
for i: Double in stride(from: 0, through: 23, by: 1) {
Kvalues.append(1/sqrt(abs(Double(1) + pow(2,-2 * i))))
if i > 0 {
Kvalues[Kvalues.count - 1] *= Kvalues[Kvalues.count - 2]
}
}
func cordic(beta: Double, n: Int) -> [Double] {
var beta1 = beta
let Kn = Kvalues[min(n-1, Kvalues.count - 1)]
var v: [Double] = [1,0]
var poweroftwo: Double = 1
var angle = angles[0]
for j in 0 ..< n {
let sigma: Double = beta1 < 0 ? -1 : 1
let factor: Double = sigma * poweroftwo
v = [v[0] - v[1] * factor, v[1] + v[0] * factor]
beta1 -= sigma * angle
poweroftwo /= 2
angle = j + 1 >= angles.count ? angle / 2 : angles[j + 1]
}
return [v[0] * Kn, v[1] * Kn]
}
And that produces good approximations:
print(cordic(beta: Double.pi/9, n: 20)) // [0.93969210812600046, 0.34202155184390554]
print(cordic(beta: Double.pi/8, n: 20)) // [0.92388022188807306, 0.38268176805806309]
The exact values are
print(cos(Double.pi/9), sin(Double.pi/9)) // 0.939692620785908 0.342020143325669
print(cos(Double.pi/8), sin(Double.pi/8)) // 0.923879532511287 0.38268343236509

Bezier and b-spline arc-length algorithm giving me problems

I'm having a bit of a problem calculating the arc-length of my bezier and b-spline curves. I've been banging my head against this for several days, and I think I'm almost there, but can't seem to get it exactly right. I'm developing in Swift, but I think its syntax is clear enough that anyone who knows C/C++ would be able to read it. If not, please let me know and I'll try to translate it into C/C++.
I've checked my implementations against several sources over and over again, and, as far as the algorithms go, they seem to be correct, although I'm not so sure about the B-spline algorithm. Some tutorials use the degree, and some use the order, of the curve in their calculations, and I get really confused. In addition, in using the Gauss-Legendre quadrature, I understand that I'm supposed to sum the integration of the spans, but I'm not sure I'm understanding how to do that correctly. From what I understand, I should be integrating over each knot span. Is that correct?
When I calculate the length of a Bezier curve with the following control polygon, I get 28.2842712474619, while 3D software (Cinema 4D and Maya) tells me the length should be 30.871.
let bezierControlPoints = [
Vector(-10.0, -10.0),
Vector(0.0, -10.0),
Vector(0.0, 10.0),
Vector(10.0, 10.0)
]
The length of the b-spline is similarly off. My algorithm produces 5.6062782185353, while it should be 7.437.
let splineControlPoints = [
Vector(-2.0, -1.0),
Vector(-1.0, 1.0),
Vector(-0.25, 1.0),
Vector(0.25, -1.0),
Vector(1.0, -1.0),
Vector(2.0, 1.0)
]
I'm not a mathematician, so I'm struggling with the math, but I think I have the gist of it.
The Vector class is pretty straight-forwared, but I've overloaded some operators for convenience/legibility which makes the code quite lengthy, so I'm not posting it here. I'm also not including the Gauss-Legendre weights and abscissae. You can download the source and Xcode project from here (53K).
Here's my bezier curve class:
class Bezier
{
var c0:Vector
var c1:Vector
var c2:Vector
var c3:Vector
init(ic0 _ic0:Vector, ic1 _ic1:Vector, ic2 _ic2:Vector, ic3 _ic3:Vector) {
c0 = _ic0
c1 = _ic1
c2 = _ic2
c3 = _ic3
}
// Calculate curve length using Gauss-Legendre quadrature
func curveLength()->Double {
let gl = GaussLegendre()
gl.order = 3 // Good enough for a quadratic polynomial
let xprime = gl.integrate(a:0.0, b:1.0, closure:{ (t:Double)->Double in return self.dx(atTime:t) })
let yprime = gl.integrate(a:0.0, b:1.0, closure:{ (t:Double)->Double in return self.dy(atTime:t) })
return sqrt(xprime*xprime + yprime*yprime)
}
// I could vectorize this, but correctness > efficiency
// The derivative of the x-component
func dx(atTime t:Double)->Double {
let tc = (1.0-t)
let r0 = (3.0 * tc*tc) * (c1.x - c0.x)
let r1 = (6.0 * tc*t) * (c2.x - c1.x)
let r2 = (3.0 * t*t) * (c3.x - c2.x)
return r0 + r1 + r2
}
// The derivative of the y-component
func dy(atTime t:Double)->Double {
let tc = (1.0-t)
let r0 = (3.0 * tc*tc) * (c1.y - c0.y)
let r1 = (6.0 * tc*t) * (c2.y - c1.y)
let r2 = (3.0 * t*t) * (c3.y - c2.y)
return r0 + r1 + r2
}
}
Here is my b-spline class:
class BSpline
{
var spanLengths:[Double]! = nil
var totalLength:Double = 0.0
var cp:[Vector]
var knots:[Double]! = nil
var o:Int = 4
init(controlPoints:[Vector]) {
cp = controlPoints
calcKnots()
}
// Method to return length of the curve using Gauss-Legendre numerical integration
func cacheSpanLengths() {
spanLengths = [Double]()
totalLength = 0.0
let gl = GaussLegendre()
gl.order = o-1 // The derivative should be quadratic, so o-2 would suffice?
// Am I doing this right? Piece-wise integration?
for i in o-1 ..< knots.count-o {
let t0 = knots[i]
let t1 = knots[i+1]
let xprime = gl.integrate(a:t0, b:t1, closure:self.dx)
let yprime = gl.integrate(a:t0, b:t1, closure:self.dy)
let spanLength = sqrt(xprime*xprime + yprime*yprime)
spanLengths.append(spanLength)
totalLength += spanLength
}
}
// The b-spline basis function
func basis(i:Int, _ k:Int, _ x:Double)->Double {
var r:Double = 0.0
switch k {
case 0:
if (knots[i] <= x) && (x <= knots[i+1]) {
r = 1.0
} else {
r = 0.0
}
default:
var n0 = x - knots[i]
var d0 = knots[i+k]-knots[i]
var b0 = basis(i,k-1,x)
var n1 = knots[i+k+1] - x
var d1 = knots[i+k+1]-knots[i+1]
var b1 = basis(i+1,k-1,x)
var left = Double(0.0)
var right = Double(0.0)
if b0 != 0 && d0 != 0 { left = n0 * b0 / d0 }
if b1 != 0 && d1 != 0 { right = n1 * b1 / d1 }
r = left + right
}
return r
}
// Method to calculate and store the knot vector
func calcKnots() {
// The number of knots in the knot vector = number of control points + order (i.e. degree + 1)
let knotCount = cp.count + o
knots = [Double]()
// For an open b-spline where the ends are incident on the first and last control points,
// the first o knots are the same and the last o knots are the same, where o is the order
// of the curve.
var k = 0
for i in 0 ..< o {
knots.append(0.0)
}
for i in o ..< cp.count {
k++
knots.append(Double(k))
}
k++
for i in cp.count ..< knotCount {
knots.append(Double(k))
}
}
// I could vectorize this, but correctness > efficiency
// Derivative of the x-component
func dx(t:Double)->Double {
var p = Double(0.0)
let n = o
for i in 0 ..< cp.count-1 {
let u0 = knots[i + n + 1]
let u1 = knots[i + 1]
let fn = Double(n) / (u0 - u1)
let thePoint = (cp[i+1].x - cp[i].x) * fn
let b = basis(i+1, n-1, Double(t))
p += thePoint * b
}
return Double(p)
}
// Derivative of the y-component
func dy(t:Double)->Double {
var p = Double(0.0)
let n = o
for i in 0 ..< cp.count-1 {
let u0 = knots[i + n + 1]
let u1 = knots[i + 1]
let fn = Double(n) / (u0 - u1)
let thePoint = (cp[i+1].y - cp[i].y) * fn
let b = basis(i+1, n-1, Double(t))
p += thePoint * b
}
return Double(p)
}
}
And here is my Gauss-Legendre implementation:
class GaussLegendre
{
var order:Int = 5
init() {
}
// Numerical integration of arbitrary function
func integrate(a _a:Double, b _b:Double, closure f:(Double)->Double)->Double {
var result = 0.0
let wgts = gl_weights[order-2]
let absc = gl_abscissae[order-2]
for i in 0..<order {
let a0 = absc[i]
let w0 = wgts[i]
result += w0 * f(0.5 * (_b + _a + a0 * (_b - _a)))
}
return 0.5 * (_b - _a) * result
}
}
And my main logic:
let bezierControlPoints = [
Vector(-10.0, -10.0),
Vector(0.0, -10.0),
Vector(0.0, 10.0),
Vector(10.0, 10.0)
]
let splineControlPoints = [
Vector(-2.0, -1.0),
Vector(-1.0, 1.0),
Vector(-0.25, 1.0),
Vector(0.25, -1.0),
Vector(1.0, -1.0),
Vector(2.0, 1.0)
]
var bezier = Bezier(controlPoints:bezierControlPoints)
println("Bezier curve length: \(bezier.curveLength())\n")
var spline:BSpline = BSpline(controlPoints:splineControlPoints)
spline.cacheSpanLengths()
println("B-Spline curve length: \(spline.totalLength)\n")
UPDATE: PROBLEM (PARTIALLY) SOLVED
Thanks to Mike for his answer!
I verified that I am correctly remapping the numerical integration from the interval a..b to -1..1 for the purposes of Legendre-Gauss quadrature. The math is here (apologies to any real mathematicians out there, it's the best I could do with my long-forgotten calculus).
I've increased the order of the Legendre-Gauss quadrature from 5 to 32 as Mike suggested.
Then after a lot of floundering around in Mathematica, I came back and re-read Mike's code and discovered that my code was NOT equivalent to his.
I was taking the square root of the sums of the squared integrals of the derivative components:
when I should have been taking the integral of the magnitudes of the derivative vectors:
In terms of code, in my Bezier class, instead of this:
// INCORRECT
func curveLength()->Double {
let gl = GaussLegendre()
gl.order = 3 // Good enough for a quadratic polynomial
let xprime = gl.integrate(a:0.0, b:1.0, closure:{ (t:Double)->Double in return self.dx(atTime:t) })
let yprime = gl.integrate(a:0.0, b:1.0, closure:{ (t:Double)->Double in return self.dy(atTime:t) })
return sqrt(xprime*xprime + yprime*yprime)
}
I should have written this:
// CORRECT
func curveLength()->Double {
let gl = GaussLegendre()
gl.order = 32
return = gl.integrate(a:0.0, b:1.0, closure:{ (t:Double)->Double in
let x = self.dx(atTime:t)
let y = self.dy(atTime:t)
return sqrt(x*x + y*y)
})
}
My code calculates the arc length as: 3.59835872777095
Mathematica: 3.598358727834686
So, my result is pretty close. Interestingly, there is a discrepancy between a plot in Mathematica of my test Bezier curve, and the same rendered by Cinema 4D, which would explain why the arc lengths calculated by Mathematica and Cinema 4D are different as well. I think I trust Mathematica to be more correct, though.
In my B-Spline class, instead of this:
// INCORRECT
func cacheSpanLengths() {
spanLengths = [Double]()
totalLength = 0.0
let gl = GaussLegendre()
gl.order = o-1 // The derivative should be quadratic, so o-2 would suffice?
// Am I doing this right? Piece-wise integration?
for i in o-1 ..< knots.count-o {
let t0 = knots[i]
let t1 = knots[i+1]
let xprime = gl.integrate(a:t0, b:t1, closure:self.dx)
let yprime = gl.integrate(a:t0, b:t1, closure:self.dy)
let spanLength = sqrt(xprime*xprime + yprime*yprime)
spanLengths.append(spanLength)
totalLength += spanLength
}
}
I should have written this:
// CORRECT
func cacheSpanLengths() {
spanLengths = [Double]()
totalLength = 0.0
let gl = GaussLegendre()
gl.order = 32
// Am I doing this right? Piece-wise integration?
for i in o-1 ..< knots.count-o {
let t0 = knots[i]
let t1 = knots[i+1]
let spanLength = gl.integrate(a:t0, b:t1, closure:{ (t:Double)->Double in
let x = self.dx(atTime:t)
let y = self.dy(atTime:t)
return sqrt(x*x + y*y)
})
spanLengths.append(spanLength)
totalLength += spanLength
}
}
Unfortunately, the B-Spline math is not as straight-forward, and I haven't been able to test it in Mathematica as easily as the Bezier math, so I'm not entirely sure my code is working, even with the above changes. I will post another update when I verify it.
UPDATE 2: PROBLEM SOLVED
Eureka, I discovered an off-by one error in my code to calculate the B-Spline derivative.
Instead of
// Derivative of the x-component
func dx(t:Double)->Double {
var p = Double(0.0)
let n = o // INCORRECT (should be one less)
for i in 0 ..< cp.count-1 {
let u0 = knots[i + n + 1]
let u1 = knots[i + 1]
let fn = Double(n) / (u0 - u1)
let thePoint = (cp[i+1].x - cp[i].x) * fn
let b = basis(i+1, n-1, Double(t))
p += thePoint * b
}
return Double(p)
}
// Derivative of the y-component
func dy(t:Double)->Double {
var p = Double(0.0)
let n = o // INCORRECT (should be one less_
for i in 0 ..< cp.count-1 {
let u0 = knots[i + n + 1]
let u1 = knots[i + 1]
let fn = Double(n) / (u0 - u1)
let thePoint = (cp[i+1].y - cp[i].y) * fn
let b = basis(i+1, n-1, Double(t))
p += thePoint * b
}
return Double(p)
}
I should have written
// Derivative of the x-component
func dx(t:Double)->Double {
var p = Double(0.0)
let n = o-1 // CORRECT
for i in 0 ..< cp.count-1 {
let u0 = knots[i + n + 1]
let u1 = knots[i + 1]
let fn = Double(n) / (u0 - u1)
let thePoint = (cp[i+1].x - cp[i].x) * fn
let b = basis(i+1, n-1, Double(t))
p += thePoint * b
}
return Double(p)
}
// Derivative of the y-component
func dy(t:Double)->Double {
var p = Double(0.0)
let n = o-1 // CORRECT
for i in 0 ..< cp.count-1 {
let u0 = knots[i + n + 1]
let u1 = knots[i + 1]
let fn = Double(n) / (u0 - u1)
let thePoint = (cp[i+1].y - cp[i].y) * fn
let b = basis(i+1, n-1, Double(t))
p += thePoint * b
}
return Double(p)
}
My code now calculates the length of the B-Spline curve as 6.87309971722132.
Mathematica: 6.87309884638438.
It's probably not scientifically precise, but good enough for me.
The Legendre-Gauss procedure is specifically defined for the interval [-1,1], whereas Beziers and B-Splines are defined over [0,1], so that's a simple conversion and at least while you're trying to make sure your code does the right thing, easy to bake in instead of supplying a dynamic interval (as you say, accuracy over efficiency. Once it works, we can worry about optimising)
So, given weights W and abscissae A (both of same length n), you'd do:
z = 0.5
for i in 1..n
w = W[i]
a = A[i]
t = z * a + z
sum += w * arcfn(t, xpoints, ypoints)
return z * sum
with the pseudo-code assuming list indexing from 1. The arcfn would be defined as:
arcfn(t, xpoints, ypoints):
x = derive(xpoints, t)
y = derive(ypoints, t)
c = x*x + y*y
return sqrt(c)
But that part looks right already.
Your derivatives look correct too, so the main question is: "are you using enough slices in your Legendre-Gauss quadrature?". Your code suggests you're using only 5 slices, which isn't nearly enough to get a good result. Using http://pomax.github.io/bezierinfo/legendre-gauss.html as term data, you generally want a set for n of 16 or higher (for cubic Bezier curves, 24 is generally safe, although still underperformant for curves with cusps or lots of inflections).
I can recommend taking the "unit test" approach here: test your bezier and bspline code (separately) for known base and derivative values. Do those check out? One problem ruled out. On to your LG code: if you perform Legendre-Gauss on a parametric function for a straight line using:
fx(t) = t
fy(t) = t
fx'(t) = 1
fy'(t) = 1
over interval t=[0,1], we know the length should be exactly the square root of 2, and the derivatives are the simplest possible. If those work, do a non-linear test using:
fx(t) = sin(t)
fy(t) = cos(t)
fx'(t) = cos(t)
fy'(t) = -sin(t)
over interval t=[0,1]; we know the length should be exactly 1. Does your LG implementation yield the correct value? Another problem ruled out. If it doesn't, check your weights and abscissae. Do they match the ones from the linked page (generated with a verifiably correct Mathematica program, so pretty much guaranteed to be correct)? Are you using enough slices? Bump the number up to 10, 16, 24, 32; increasing the number of slices will show a stabilising summation, where adding more slices doesn't change digits before the 2nd, 3rd, 4th, 5th, etc decimal point as you increase the count.
Are the curves you're testing with known to be problematic curves? Plot them, do they have cusps or lots of inflections? That's going to be a problem for LG, try simpler curves to see if the values you get back for those, at least, are correct.
Finally, check your types: Are you using the highest precision possible datatype? 32 bit floats are going to run into mysteriously disappearing FPU and wonderful rounding errors at the values we need to use when doing LG with a reasonable number of slices.