RobotCore 1.0
WPILib port to raspberry Pi
Loading...
Searching...
No Matches
Quaternion.java
1// Copyright (c) FIRST and other WPILib contributors.
2// Open Source Software; you can modify and/or share it under the terms of
3// the WPILib BSD license file in the root directory of this project.
4
5package org.wpilib.math.geometry;
6
7import io.avaje.jsonb.Json;
8import java.util.Objects;
9import org.wpilib.math.geometry.proto.QuaternionProto;
10import org.wpilib.math.geometry.struct.QuaternionStruct;
11import org.wpilib.math.linalg.VecBuilder;
12import org.wpilib.math.linalg.Vector;
13import org.wpilib.math.numbers.N3;
14import org.wpilib.util.protobuf.ProtobufSerializable;
15import org.wpilib.util.struct.StructSerializable;
16
18@Json
19public final class Quaternion implements ProtobufSerializable, StructSerializable {
20 // Scalar r in versor form
21 @Json.Ignore private final double m_w;
22
23 // Vector v in versor form
24 @Json.Ignore private final double m_x;
25 @Json.Ignore private final double m_y;
26 @Json.Ignore private final double m_z;
27
29 public Quaternion() {
30 m_w = 1.0;
31 m_x = 0.0;
32 m_y = 0.0;
33 m_z = 0.0;
34 }
35
44 @Json.Creator
45 public Quaternion(
46 @Json.Alias("W") double w,
47 @Json.Alias("X") double x,
48 @Json.Alias("Y") double y,
49 @Json.Alias("Z") double z) {
50 m_w = w;
51 m_x = x;
52 m_y = y;
53 m_z = z;
54 }
55
62 public Quaternion plus(Quaternion other) {
63 return new Quaternion(
64 getW() + other.getW(), getX() + other.getX(), getY() + other.getY(), getZ() + other.getZ());
65 }
66
73 public Quaternion minus(Quaternion other) {
74 return new Quaternion(
75 getW() - other.getW(), getX() - other.getX(), getY() - other.getY(), getZ() - other.getZ());
76 }
77
84 public Quaternion divide(double scalar) {
85 return new Quaternion(getW() / scalar, getX() / scalar, getY() / scalar, getZ() / scalar);
86 }
87
94 public Quaternion times(double scalar) {
95 return new Quaternion(getW() * scalar, getX() * scalar, getY() * scalar, getZ() * scalar);
96 }
97
105 // https://en.wikipedia.org/wiki/Quaternion#Scalar_and_vector_parts
106 final var r1 = m_w;
107 final var r2 = other.m_w;
108
109 // v₁ ⋅ v₂
110 double dot = m_x * other.m_x + m_y * other.m_y + m_z * other.m_z;
111
112 // v₁ x v₂
113 double cross_x = m_y * other.m_z - other.m_y * m_z;
114 double cross_y = other.m_x * m_z - m_x * other.m_z;
115 double cross_z = m_x * other.m_y - other.m_x * m_y;
116
117 return new Quaternion(
118 // r = r₁r₂ − v₁ ⋅ v₂
119 r1 * r2 - dot,
120 // v = r₁v₂ + r₂v₁ + v₁ x v₂
121 r1 * other.m_x + r2 * m_x + cross_x,
122 r1 * other.m_y + r2 * m_y + cross_y,
123 r1 * other.m_z + r2 * m_z + cross_z);
124 }
125
126 @Override
127 public String toString() {
128 return String.format("Quaternion(%s, %s, %s, %s)", getW(), getX(), getY(), getZ());
129 }
130
137 @Override
138 public boolean equals(Object obj) {
139 return obj instanceof Quaternion other
140 && Math.abs(dot(other) - norm() * other.norm()) < 1e-9
141 && Math.abs(norm() - other.norm()) < 1e-9;
142 }
143
144 @Override
145 public int hashCode() {
146 return Objects.hash(m_w, m_x, m_y, m_z);
147 }
148
155 return new Quaternion(getW(), -getX(), -getY(), -getZ());
156 }
157
164 public double dot(final Quaternion other) {
165 return getW() * other.getW()
166 + getX() * other.getX()
167 + getY() * other.getY()
168 + getZ() * other.getZ();
169 }
170
177 var norm = norm();
178 return conjugate().divide(norm * norm);
179 }
180
186 public double norm() {
187 return Math.sqrt(dot(this));
188 }
189
196 double norm = norm();
197 if (norm == 0.0) {
198 return new Quaternion();
199 } else {
200 return new Quaternion(getW() / norm, getX() / norm, getY() / norm, getZ() / norm);
201 }
202 }
203
210 public Quaternion pow(double t) {
211 // q^t = e^(ln(q^t)) = e^(t * ln(q))
212 return this.log().times(t).exp();
213 }
214
225 public Quaternion exp() {
226 var scalar = Math.exp(getW());
227
228 var axial_magnitude = Math.sqrt(getX() * getX() + getY() * getY() + getZ() * getZ());
229 var cosine = Math.cos(axial_magnitude);
230
231 double axial_scalar;
232
233 if (axial_magnitude < 1e-9) {
234 // Taylor series of sin(θ) / θ near θ = 0: 1 − θ²/6 + θ⁴/120 + O(n⁶)
235 var axial_magnitude_sq = axial_magnitude * axial_magnitude;
236 var axial_magnitude_sq_sq = axial_magnitude_sq * axial_magnitude_sq;
237 axial_scalar = 1.0 - axial_magnitude_sq / 6.0 + axial_magnitude_sq_sq / 120.0;
238 } else {
239 axial_scalar = Math.sin(axial_magnitude) / axial_magnitude;
240 }
241
242 return new Quaternion(
243 cosine * scalar,
244 getX() * axial_scalar * scalar,
245 getY() * axial_scalar * scalar,
246 getZ() * axial_scalar * scalar);
247 }
248
259 public Quaternion log() {
260 var norm = norm();
261 if (norm == 0.0) {
262 return new Quaternion(0.0, 0.0, 0.0, 0.0);
263 }
264
265 var scalar = Math.log(norm);
266
267 var v_norm = Math.sqrt(getX() * getX() + getY() * getY() + getZ() * getZ());
268
269 var w = getW();
270 var s_norm = w / norm;
271
272 if (Math.abs(s_norm + 1) < 1e-9) {
273 return new Quaternion(scalar, -Math.PI, 0, 0);
274 }
275
276 double v_scalar;
277
278 if (v_norm < 1e-9 && w != 0.0) {
279 // Taylor series expansion of atan2(y/x)/y at y = 0:
280 //
281 // 1/x - 1/3 y²/x³ + O(y⁴)
282 v_scalar = 1.0 / w - 1.0 / 3.0 * v_norm * v_norm / (w * w * w);
283 } else if (v_norm == 0.0) {
284 v_scalar = 0.0;
285 } else {
286 v_scalar = Math.atan2(v_norm, w) / v_norm;
287 }
288
289 return new Quaternion(scalar, v_scalar * getX(), v_scalar * getY(), v_scalar * getZ());
290 }
291
297 @Json.Property(value = "W")
298 public double getW() {
299 return m_w;
300 }
301
307 @Json.Property(value = "X")
308 public double getX() {
309 return m_x;
310 }
311
317 @Json.Property(value = "Y")
318 public double getY() {
319 return m_y;
320 }
321
327 @Json.Property(value = "Z")
328 public double getZ() {
329 return m_z;
330 }
331
343 double theta = rvec.norm();
344
345 double cos = Math.cos(theta / 2);
346
347 double axial_scalar;
348
349 if (theta < 1e-9) {
350 // taylor series expansion of sin(θ/2) / θ = 1/2 - θ²/48 + O(θ⁴)
351 axial_scalar = 1.0 / 2.0 - theta * theta / 48.0;
352 } else {
353 axial_scalar = Math.sin(theta / 2) / theta;
354 }
355
356 return new Quaternion(
357 cos,
358 axial_scalar * rvec.get(0, 0),
359 axial_scalar * rvec.get(1, 0),
360 axial_scalar * rvec.get(2, 0));
361 }
362
371 // See equation (31) in "Integrating Generic Sensor Fusion Algorithms with
372 // Sound State Representation through Encapsulation of Manifolds"
373 //
374 // https://arxiv.org/pdf/1107.1119.pdf
375 double norm = Math.sqrt(getX() * getX() + getY() * getY() + getZ() * getZ());
376
377 double coeff;
378 if (norm < 1e-9) {
379 coeff = 2.0 / getW() - 2.0 / 3.0 * norm * norm / (getW() * getW() * getW());
380 } else {
381 if (getW() < 0.0) {
382 coeff = 2.0 * Math.atan2(-norm, -getW()) / norm;
383 } else {
384 coeff = 2.0 * Math.atan2(norm, getW()) / norm;
385 }
386 }
387
388 return VecBuilder.fill(coeff * getX(), coeff * getY(), coeff * getZ());
389 }
390
392 public static final QuaternionProto proto = new QuaternionProto();
393
395 public static final QuaternionStruct struct = new QuaternionStruct();
396}
static final QuaternionProto proto
Quaternion times(Quaternion other)
Quaternion times(double scalar)
static Quaternion fromRotationVector(Vector< N3 > rvec)
Quaternion minus(Quaternion other)
Quaternion divide(double scalar)
Quaternion plus(Quaternion other)
double dot(final Quaternion other)
Quaternion( @Json.Alias("W") double w, @Json.Alias("X") double x, @Json.Alias("Y") double y, @Json.Alias("Z") double z)
static Vector< N1 > fill(double n1)