SCNQuaternion Multiplication
f#, ios, quaternions, scenekit, swift
Solution
SceneKit doesn't include a library for doing quaternion math. `SCNQuaternion` is just a type alias for `SCNVector4` — in other words, it's a place to hold the four coefficients of a quaternion, but you're left to do your own math to interpret or work with them.
In Xcode 9 / iOS 11 / macOS 10.13 High Sierra / etc, there's a better way to deal with quaternions (and other vector/matrix math) in SceneKit: the SIMD library includes two quaternion types (for double and float), and SceneKit duplicates all the SCNVector/SCNMatrix/SCNQuaternion accessors to provide SIMD versions. There's lots of great reasons to use SIMD types instead of the SCN ones:
- SIMD types are used in multiple iOS/macOS/etc frameworks, so you can use them to pass vector/matrix/quaternion values between (e.g.) SceneKit, ARKit, and Model I/O without casting or converting.
- The Metal shader language uses the same library for its vector/matrix types, so your math code is easily portable between CPU and GPU.
- In Swift, SIMD types appear as structs with initializers, properties and methods, so most(*) of your code can be Swift-friendly.
- In Swift and C++, operator overloading covers all the basic operations, so your math code and read and write like math.
- All of the above is implemented using compiler intrinsics, so they use the native vector instruction set of the CPU your code is running on (x86 SSE, ARM NEON) to go fast.
Unfortunately, CoreMotion hasn't (yet?) adopted the SIMD library for its vector/matrix/quaternion types, so you'll need to convert your input `CMQuaternion` to `simd_quatf` before working with SIMD functions or SceneKit SIMD-based API. If you find yourself doing that often, you might want an extension for it:
extension simd_quatf {
init(_ cmq: CMQuaternion) {
self.init(ix: Float(cmq.x), iy: Float(cmq.y), iz: Float(cmq.z), r: Float(cmq.w))
}
}
Then (assuming I'm reading your F# right), your code becomes something like this in Swift:
let zAxis = float3(x: 0, y: 0, z: 1)
let rotateAroundZ = simd_quatf(angle: .pi/2, axis: zAxis)
manager.startDeviceMotionUpdates(using: .xArbitraryZVertical, to: queue) { motion, error in
guard let motion = motion else { /* handle error and */ return }
let deviceQuaternion = simd_quatf(motion.attitude.quaternion)
cameraNode.simdOrientation = deviceQuaternion * rotateAroundZ
}
(*) Some of the useful SIMD operations are still C-style global functions: Check `simd/quaternion.h` for stuff like slerp and Bézier interpolation.
For earlier Xcode / SDK releases...
Prior to Xcode 9 /iOS 11 / macOS 10.13 / etc, there's no quaternions in SIMD, and no SIMD—specific accessors in SceneKit. For everything but quaternions, it can still be useful to take the SCN types, convert them component-wise to the equivalent SIMD types, and do your math there.
Apple does ship a quaternion library in GLKit. Use `GLKQuaternionMake` to make a `GLKQuaternion` from the four components of your `SCNQuaternion`. Then you can multiply it, slerp it, whatever using `GLKQuaternion` functions. When done, use the components of the result to make another `SCNQuaternion`.
In Swift 1.x (Xcode 6.x), the SIMD library isn't available from Swift. GLKit math is a halfway decent substitute, but you're better off just using a newer Xcode — remember you can target all the way back to iOS 7 / macOS 10.9 Mavericks even with Swift 4 (and the SIMD library is available as far back as iOS 8 / macOS 10.10 Yosemite).
Problem
I'm trying to perform a SCNQuaternion Multiplication in SceneKit with Swift. The code below is in F# (Xamarin iOS Development). I'm trying to translate that code into Swift. I'm stuck at the line: let dqm = SCNQuaternion.Multiply... SceneKit doesn't have a member called multiply. Any idea how I can do this? ``` let rr = CMAttitudeReferenceFrame.XArbitraryCorrectedZVertical this.motion.DeviceMotionUpdateInterval <- float (1.0f / 30.0f) this.motion.StartDeviceMotionUpdates (rr,NSOperationQueue.CurrentQueue, (fun d e -> let a = this.motion.DeviceMotion.Attitude let q = a.Quaternion let dq = new SCNQuaternion(float32 q.x, float32 q.y, float32 q.z, float32 q.w) let dqm = SCNQuaternion.Multiply (dq, SCNQuaternion.FromAxisAngle(SCNVector3.UnitZ, piover2)) camNode.Orientation <- dqm () )) ```