Skip to content

Commit 8a9ad43

Browse files
markuswntrstephentyrone
authored andcommitted
Rename halfAngle to argument and make it public
1 parent 1bab0de commit 8a9ad43

File tree

4 files changed

+81
-85
lines changed

4 files changed

+81
-85
lines changed

Sources/QuaternionModule/Polar.swift

Lines changed: 60 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ extension Quaternion {
4949
/// - If a quaternion is not finite, its `.length` is `infinity`.
5050
///
5151
/// See also `.magnitude`, `.lengthSquared`, `.polar` and
52-
/// `init(length:,phase:,axis:)`.
52+
/// `init(length:argument:axis:)`.
5353
@_transparent
5454
public var length: RealType {
5555
let naive = lengthSquared
@@ -77,124 +77,120 @@ extension Quaternion {
7777
/// This property is more efficient to compute than `length`.
7878
///
7979
/// See also `.magnitude`, `.length`, `.polar` and
80-
/// `init(length:,phase:,axis:)`.
80+
/// `init(length:argument:axis:)`.
8181
@_transparent
8282
public var lengthSquared: RealType {
8383
(components * components).sum()
8484
}
8585

86+
/// The principle argument (half rotation angle) in radians within
87+
/// *[0, π]* range.
88+
///
89+
/// Edge cases:
90+
/// - If the quaternion is zero or non-finite, argument is `nan`.
91+
@inlinable
92+
public var argument: RealType {
93+
guard isFinite else { return .nan }
94+
guard imaginary != .zero else {
95+
// A zero quaternion does not encode transformation properties.
96+
// If imaginary is zero, real must be non-zero or nan is returned.
97+
return real.isZero ? .nan : .zero
98+
}
99+
100+
// If lengthSquared computes without over/underflow, everything is fine
101+
// and the result is correct. If not, we have to do the computation
102+
// carefully and unscale the quaternion first.
103+
let lenSq = imaginary.lengthSquared
104+
guard lenSq.isNormal else { return divided(by: magnitude).argument }
105+
return .atan2(y: .sqrt(lenSq), x: real)
106+
}
107+
86108
/// The [polar decomposition][wiki].
87109
///
88-
/// Returns the length of this quaternion, phase in radians of range *[0, π]*
89-
/// and the rotation axis as SIMD3 vector of unit length.
110+
/// Returns the length of this quaternion, argument in radians of range
111+
/// *[0, π]* and the rotation axis as SIMD3 vector of unit length.
90112
///
91113
/// Edge cases:
92-
/// - If the quaternion is zero, length is `.zero` and angle and axis
114+
/// - If the quaternion is zero, length is `.zero` and argument and axis
93115
/// are `nan`.
94-
/// - If the quaternion is non-finite, length is `.infinity` and angle and
116+
/// - If the quaternion is non-finite, length is `.infinity` and argument and
95117
/// axis are `nan`.
96-
/// - For any length other than `.zero` or `.infinity`, if angle is zero, axis
97-
/// is `nan`.
118+
/// - For any length other than `.zero` or `.infinity`, if argument is zero,
119+
/// axis is `nan`.
98120
///
99121
/// See also `.magnitude`, `.length`, `.lengthSquared` and
100-
/// `init(length:,phase:,axis:)`.
122+
/// `init(length:argument:axis:)`.
101123
///
102124
/// [wiki]: https://en.wikipedia.org/wiki/Polar_decomposition#Quaternion_polar_decomposition
103-
public var polar: (length: RealType, phase: RealType, axis: SIMD3<RealType>) {
104-
(length, halfAngle, axis)
125+
public var polar: (length: RealType, argument: RealType, axis: SIMD3<RealType>) {
126+
(length, argument, axis)
127+
}
128+
129+
/// Creates a new quaternion from given half rotation angle about given
130+
/// rotation axis.
131+
///
132+
/// The angle-axis values are transformed using the following equation:
133+
///
134+
/// Q = (cos(argument), unitAxis * sin(argument))
135+
///
136+
/// - Parameters:
137+
/// - argument: The half rotation angle
138+
/// - unitAxis: The rotation axis of unit length
139+
@usableFromInline @inline(__always)
140+
internal init(argument: RealType, unitAxis: SIMD3<RealType>) {
141+
self.init(real: .cos(argument), imaginary: unitAxis * .sin(argument))
105142
}
106143

107144
/// Creates a quaternion specified with [polar coordinates][wiki].
108145
///
109-
/// This initializer reads given `length`, `phase` and `axis` values and
146+
/// This initializer reads given `length`, `argument` and `axis` values and
110147
/// creates a quaternion of equal rotation properties and specified *length*
111148
/// using the following equation:
112149
///
113-
/// Q = (cos(phase), axis * sin(phase)) * length
114-
///
115-
/// - Note: `axis` must be of unit length, or an assertion failure occurs.
150+
/// Q = (cos(argument), axis * sin(argument)) * length
116151
///
117152
/// Edge cases:
118153
/// - Negative lengths are interpreted as reflecting the point through the
119154
/// origin, i.e.:
120155
/// ```
121-
/// Quaternion(length: -r, phase: θ, axis: axis) == -Quaternion(length: r, phase: θ, axis: axis)
156+
/// Quaternion(length: -r, argument: θ, axis: axis) == -Quaternion(length: r, argument: θ, axis: axis)
122157
/// ```
123158
/// - For any `θ` and any `axis`, even `.infinity` or `.nan`:
124159
/// ```
125-
/// Quaternion(length: .zero, phase: θ, axis: axis) == .zero
160+
/// Quaternion(length: .zero, argument: θ, axis: axis) == .zero
126161
/// ```
127162
/// - For any `θ` and any `axis`, even `.infinity` or `.nan`:
128163
/// ```
129-
/// Quaternion(length: .infinity, phase: θ, axis: axis) == .infinity
164+
/// Quaternion(length: .infinity, argument: θ, axis: axis) == .infinity
130165
/// ```
131-
/// - Otherwise, `θ` must be finite, or a precondition failure occurs.
166+
/// - Otherwise, `θ` must be finite, or a precondition failure occurs and
167+
/// `axis` must be of unit length, or an assertion failure occurs.
132168
///
133169
/// See also `.magnitude`, `.length`, `.lengthSquared` and `.polar`.
134170
///
135171
/// [wiki]: https://en.wikipedia.org/wiki/Polar_decomposition#Quaternion_polar_decomposition
136172
@inlinable
137-
public init(length: RealType, phase: RealType, axis: SIMD3<RealType>) {
173+
public init(length: RealType, argument: RealType, axis: SIMD3<RealType>) {
138174
guard !length.isZero, length.isFinite else {
139175
self = Quaternion(length)
140176
return
141177
}
142178

143179
// Length is finite and non-zero, therefore
144-
// 1. `phase` must be finite or a precondition failure needs to occur; as
180+
// 1. `argument` must be finite or a precondition failure needs to occur; as
145181
// this is not representable.
146182
// 2. `axis` must be of unit length or an assertion failure occurs; while
147183
// "wrong" by definition, it is representable.
148184
precondition(
149-
phase.isFinite,
150-
"Either phase must be finite, or length must be zero or infinite."
185+
argument.isFinite,
186+
"Either argument must be finite, or length must be zero or infinite."
151187
)
152188
assert(
153189
// TODO: Replace with `approximateEquality()`
154190
abs(.sqrt(axis.lengthSquared)-1) < max(.sqrt(axis.lengthSquared), 1)*RealType.ulpOfOne.squareRoot(),
155191
"Given axis must be of unit length."
156192
)
157193

158-
self = Quaternion(halfAngle: phase, unitAxis: axis).multiplied(by: length)
159-
}
160-
}
161-
162-
// MARK: - Operations for working with polar form
163-
164-
extension Quaternion {
165-
/// The half rotation angle in radians within *[0, π]* range.
166-
///
167-
/// Edge cases:
168-
/// - If the quaternion is zero or non-finite, halfAngle is `nan`.
169-
@usableFromInline @inline(__always)
170-
internal var halfAngle: RealType {
171-
guard isFinite else { return .nan }
172-
guard imaginary != .zero else {
173-
// A zero quaternion does not encode transformation properties.
174-
// If imaginary is zero, real must be non-zero or nan is returned.
175-
return real.isZero ? .nan : .zero
176-
}
177-
178-
// If lengthSquared computes without over/underflow, everything is fine
179-
// and the result is correct. If not, we have to do the computation
180-
// carefully and unscale the quaternion first.
181-
let lenSq = imaginary.lengthSquared
182-
guard lenSq.isNormal else { return divided(by: magnitude).halfAngle }
183-
return .atan2(y: .sqrt(lenSq), x: real)
184-
}
185-
186-
/// Creates a new quaternion from given half rotation angle about given
187-
/// rotation axis.
188-
///
189-
/// The angle-axis values are transformed using the following equation:
190-
///
191-
/// Q = (cos(halfAngle), unitAxis * sin(halfAngle))
192-
///
193-
/// - Parameters:
194-
/// - halfAngle: The half rotation angle
195-
/// - unitAxis: The rotation axis of unit length
196-
@usableFromInline @inline(__always)
197-
internal init(halfAngle: RealType, unitAxis: SIMD3<RealType>) {
198-
self.init(real: .cos(halfAngle), imaginary: unitAxis * .sin(halfAngle))
194+
self = Quaternion(argument: argument, unitAxis: axis).multiplied(by: length)
199195
}
200196
}

Sources/QuaternionModule/Quaternion+ElementaryFunctions.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ extension Quaternion/*: ElementaryFunctions */ {
4545
// `exp(r) cos(||v||)` would not be.
4646
guard q.isFinite else { return q }
4747
let (â, θ) = q.imaginary.unitAxisAndLength
48-
let rotation = Quaternion(halfAngle: θ, unitAxis: â)
48+
let rotation = Quaternion(argument: θ, unitAxis: â)
4949
// If real < log(greatestFiniteMagnitude), then exp(real) does not overflow.
5050
// To protect ourselves against sketchy log or exp implementations in
5151
// an unknown host library, or slight rounding disagreements between
@@ -90,7 +90,7 @@ extension Quaternion/*: ElementaryFunctions */ {
9090
// so the -1 term is _always_ negligable).
9191
guard q.real < RealType.log(.greatestFiniteMagnitude) - 1 else {
9292
let halfScale = RealType.exp(q.real/2)
93-
let rotation = Quaternion(halfAngle: θ, unitAxis: â)
93+
let rotation = Quaternion(argument: θ, unitAxis: â)
9494
return rotation.multiplied(by: halfScale).multiplied(by: halfScale)
9595
}
9696
return Quaternion(
@@ -123,7 +123,7 @@ extension Quaternion/*: ElementaryFunctions */ {
123123
guard q.isFinite else { return q }
124124
let (â, θ) = q.imaginary.unitAxisAndLength
125125
guard q.real.magnitude < -RealType.log(.ulpOfOne) else {
126-
let rotation = Quaternion(halfAngle: θ, unitAxis: â)
126+
let rotation = Quaternion(argument: θ, unitAxis: â)
127127
let firstScale = RealType.exp(q.real.magnitude/2)
128128
return rotation.multiplied(by: firstScale).multiplied(by: firstScale/2)
129129
}
@@ -145,7 +145,7 @@ extension Quaternion/*: ElementaryFunctions */ {
145145
guard q.isFinite else { return q }
146146
let (â, θ) = q.imaginary.unitAxisAndLength
147147
guard q.real.magnitude < -RealType.log(.ulpOfOne) else {
148-
let rotation = Quaternion(halfAngle: θ, unitAxis: â)
148+
let rotation = Quaternion(argument: θ, unitAxis: â)
149149
let firstScale = RealType.exp(q.real.magnitude/2)
150150
let secondScale = RealType(signOf: q.real, magnitudeOf: firstScale/2)
151151
return rotation.multiplied(by: firstScale).multiplied(by: secondScale)

Sources/QuaternionModule/Transformation.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ extension Quaternion {
2424
/// [wiki]: https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation#Recovering_the_axis-angle_representation
2525
@inlinable
2626
public var angle: RealType {
27-
2 * halfAngle
27+
2 * argument
2828
}
2929

3030
/// The [rotation axis][wiki] of the Angle-Axis representation.
@@ -155,7 +155,7 @@ extension Quaternion {
155155
"Given axis must be of unit length."
156156
)
157157

158-
self = Quaternion(halfAngle: angle/2, unitAxis: axis).multiplied(by: length)
158+
self = Quaternion(argument: angle/2, unitAxis: axis).multiplied(by: length)
159159
}
160160

161161
/// Creates a unit quaternion specified with given [rotation vector][wiki].
@@ -196,7 +196,7 @@ extension Quaternion {
196196
public init(rotation vector: SIMD3<RealType>) {
197197
let angle: RealType = .sqrt(vector.lengthSquared)
198198
if !angle.isZero, angle.isFinite {
199-
self = Quaternion(halfAngle: angle/2, unitAxis: vector/angle)
199+
self = Quaternion(argument: angle/2, unitAxis: vector/angle)
200200
} else {
201201
self = Quaternion(angle)
202202
}

Tests/QuaternionTests/TransformationTests.swift

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -181,12 +181,12 @@ final class TransformationTests: XCTestCase {
181181
func testPolarDecomposition<T: Real & SIMDScalar>(_ type: T.Type) {
182182
let axis = SIMD3<T>(0,-1,0)
183183

184-
let q = Quaternion<T>(length: 5, phase: .pi, axis: axis)
184+
let q = Quaternion<T>(length: 5, argument: .pi, axis: axis)
185185
XCTAssertEqual(q.axis, axis)
186186
XCTAssertEqual(q.angle, .pi * 2)
187187

188188
XCTAssertEqual(q.polar.length, 5)
189-
XCTAssertEqual(q.polar.phase, .pi)
189+
XCTAssertEqual(q.polar.argument, .pi)
190190
XCTAssertEqual(q.polar.axis, axis)
191191
}
192192

@@ -196,18 +196,18 @@ final class TransformationTests: XCTestCase {
196196
}
197197

198198
func testPolarDecompositionEdgeCases<T: Real & SIMDScalar>(_ type: T.Type) {
199-
XCTAssertEqual(Quaternion<T>(length: .zero, phase: .zero , axis: .zero ), .zero)
200-
XCTAssertEqual(Quaternion<T>(length: .zero, phase: .infinity, axis: .infinity), .zero)
201-
XCTAssertEqual(Quaternion<T>(length: .zero, phase:-.infinity, axis: -.infinity), .zero)
202-
XCTAssertEqual(Quaternion<T>(length: .zero, phase: .nan , axis: .nan ), .zero)
203-
XCTAssertEqual(Quaternion<T>(length: .infinity, phase: .zero , axis: .zero ), .infinity)
204-
XCTAssertEqual(Quaternion<T>(length: .infinity, phase: .infinity, axis: .infinity), .infinity)
205-
XCTAssertEqual(Quaternion<T>(length: .infinity, phase:-.infinity, axis: -.infinity), .infinity)
206-
XCTAssertEqual(Quaternion<T>(length: .infinity, phase: .nan , axis: .infinity), .infinity)
207-
XCTAssertEqual(Quaternion<T>(length:-.infinity, phase: .zero , axis: .zero ), .infinity)
208-
XCTAssertEqual(Quaternion<T>(length:-.infinity, phase: .infinity, axis: .infinity), .infinity)
209-
XCTAssertEqual(Quaternion<T>(length:-.infinity, phase:-.infinity, axis: -.infinity), .infinity)
210-
XCTAssertEqual(Quaternion<T>(length:-.infinity, phase: .nan , axis: .infinity), .infinity)
199+
XCTAssertEqual(Quaternion<T>(length: .zero, argument: .zero, axis: .zero), .zero)
200+
XCTAssertEqual(Quaternion<T>(length: .zero, argument: .infinity, axis: .infinity), .zero)
201+
XCTAssertEqual(Quaternion<T>(length: .zero, argument:-.infinity, axis:-.infinity), .zero)
202+
XCTAssertEqual(Quaternion<T>(length: .zero, argument: .nan, axis: .nan), .zero)
203+
XCTAssertEqual(Quaternion<T>(length: .infinity, argument: .zero, axis: .zero), .infinity)
204+
XCTAssertEqual(Quaternion<T>(length: .infinity, argument: .infinity, axis: .infinity), .infinity)
205+
XCTAssertEqual(Quaternion<T>(length: .infinity, argument:-.infinity, axis:-.infinity), .infinity)
206+
XCTAssertEqual(Quaternion<T>(length: .infinity, argument: .nan, axis: .infinity), .infinity)
207+
XCTAssertEqual(Quaternion<T>(length:-.infinity, argument: .zero, axis: .zero), .infinity)
208+
XCTAssertEqual(Quaternion<T>(length:-.infinity, argument: .infinity, axis: .infinity), .infinity)
209+
XCTAssertEqual(Quaternion<T>(length:-.infinity, argument:-.infinity, axis:-.infinity), .infinity)
210+
XCTAssertEqual(Quaternion<T>(length:-.infinity, argument: .nan, axis: .infinity), .infinity)
211211
}
212212

213213
func testPolarDecompositionEdgeCases() {

0 commit comments

Comments
 (0)