RobotCore 1.0
WPILib port to raspberry Pi
Loading...
Searching...
No Matches
Rotation3d.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 static org.wpilib.math.util.UnitConversions.rotationsToRadians;
8import static org.wpilib.units.Units.Radians;
9
10import io.avaje.jsonb.Json;
11import java.util.Objects;
12import org.wpilib.math.geometry.proto.Rotation3dProto;
13import org.wpilib.math.geometry.struct.Rotation3dStruct;
14import org.wpilib.math.interpolation.Interpolatable;
15import org.wpilib.math.linalg.MatBuilder;
16import org.wpilib.math.linalg.Matrix;
17import org.wpilib.math.linalg.VecBuilder;
18import org.wpilib.math.linalg.Vector;
19import org.wpilib.math.numbers.N3;
20import org.wpilib.math.util.MathSharedStore;
21import org.wpilib.math.util.Nat;
22import org.wpilib.units.measure.Angle;
23import org.wpilib.util.protobuf.ProtobufSerializable;
24import org.wpilib.util.struct.StructSerializable;
25
69@Json
70public final class Rotation3d
77 public static final Rotation3d ZERO = new Rotation3d();
78
79 @Json.Property("quaternion")
80 private final Quaternion m_q;
81
83 public Rotation3d() {
84 m_q = new Quaternion();
85 }
86
92 @Json.Creator
93 public Rotation3d(@Json.Alias("quaternion") Quaternion q) {
94 m_q = q.normalize();
95 }
96
124 public Rotation3d(double roll, double pitch, double yaw) {
125 // https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles#Euler_angles_to_quaternion_conversion
126 double cr = Math.cos(roll * 0.5);
127 double sr = Math.sin(roll * 0.5);
128
129 double cp = Math.cos(pitch * 0.5);
130 double sp = Math.sin(pitch * 0.5);
131
132 double cy = Math.cos(yaw * 0.5);
133 double sy = Math.sin(yaw * 0.5);
134
135 m_q =
136 new Quaternion(
137 cr * cp * cy + sr * sp * sy,
138 sr * cp * cy - cr * sp * sy,
139 cr * sp * cy + sr * cp * sy,
140 cr * cp * sy - sr * sp * cy);
141 }
142
170 public Rotation3d(Angle roll, Angle pitch, Angle yaw) {
171 this(roll.in(Radians), pitch.in(Radians), yaw.in(Radians));
172 }
173
181 public Rotation3d(Vector<N3> rvec) {
182 this(rvec, rvec.norm());
183 }
184
192 public Rotation3d(Vector<N3> axis, double angleRadians) {
193 double norm = axis.norm();
194 if (norm == 0.0) {
195 m_q = new Quaternion();
196 return;
197 }
198
199 // https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles#Definition
200 var v = axis.times(1.0 / norm).times(Math.sin(angleRadians / 2.0));
201 m_q = new Quaternion(Math.cos(angleRadians / 2.0), v.get(0, 0), v.get(1, 0), v.get(2, 0));
202 }
203
211 public Rotation3d(Vector<N3> axis, Angle angle) {
212 this(axis, angle.in(Radians));
213 }
214
221 public Rotation3d(Matrix<N3, N3> rotationMatrix) {
222 final var R = rotationMatrix;
223
224 // Require that the rotation matrix is special orthogonal. This is true if
225 // the matrix is orthogonal (RRᵀ = I) and normalized (determinant is 1).
226 if (R.times(R.transpose()).minus(Matrix.eye(Nat.N3())).normF() > 1e-9) {
227 var msg = "Rotation matrix isn't orthogonal\n\nR =\n" + R.getStorage().toString() + '\n';
228 MathSharedStore.reportError(msg, Thread.currentThread().getStackTrace());
229 throw new IllegalArgumentException(msg);
230 }
231 if (Math.abs(R.det() - 1.0) > 1e-9) {
232 var msg =
233 "Rotation matrix is orthogonal but not special orthogonal\n\nR =\n"
234 + R.getStorage().toString()
235 + '\n';
236 MathSharedStore.reportError(msg, Thread.currentThread().getStackTrace());
237 throw new IllegalArgumentException(msg);
238 }
239
240 // Turn rotation matrix into a quaternion
241 // https://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/
242 double trace = R.get(0, 0) + R.get(1, 1) + R.get(2, 2);
243 double w;
244 double x;
245 double y;
246 double z;
247
248 if (trace > 0.0) {
249 double s = 0.5 / Math.sqrt(trace + 1.0);
250 w = 0.25 / s;
251 x = (R.get(2, 1) - R.get(1, 2)) * s;
252 y = (R.get(0, 2) - R.get(2, 0)) * s;
253 z = (R.get(1, 0) - R.get(0, 1)) * s;
254 } else {
255 if (R.get(0, 0) > R.get(1, 1) && R.get(0, 0) > R.get(2, 2)) {
256 double s = 2.0 * Math.sqrt(1.0 + R.get(0, 0) - R.get(1, 1) - R.get(2, 2));
257 w = (R.get(2, 1) - R.get(1, 2)) / s;
258 x = 0.25 * s;
259 y = (R.get(0, 1) + R.get(1, 0)) / s;
260 z = (R.get(0, 2) + R.get(2, 0)) / s;
261 } else if (R.get(1, 1) > R.get(2, 2)) {
262 double s = 2.0 * Math.sqrt(1.0 + R.get(1, 1) - R.get(0, 0) - R.get(2, 2));
263 w = (R.get(0, 2) - R.get(2, 0)) / s;
264 x = (R.get(0, 1) + R.get(1, 0)) / s;
265 y = 0.25 * s;
266 z = (R.get(1, 2) + R.get(2, 1)) / s;
267 } else {
268 double s = 2.0 * Math.sqrt(1.0 + R.get(2, 2) - R.get(0, 0) - R.get(1, 1));
269 w = (R.get(1, 0) - R.get(0, 1)) / s;
270 x = (R.get(0, 2) + R.get(2, 0)) / s;
271 y = (R.get(1, 2) + R.get(2, 1)) / s;
272 z = 0.25 * s;
273 }
274 }
275
276 m_q = new Quaternion(w, x, y, z);
277 }
278
288 public Rotation3d(Vector<N3> initial, Vector<N3> last) {
289 double dot = initial.dot(last);
290 double normProduct = initial.norm() * last.norm();
291 double dotNorm = dot / normProduct;
292
293 if (dotNorm > 1.0 - 1E-9) {
294 // If the dot product is 1, the two vectors point in the same direction so
295 // there's no rotation. The default initialization of m_q will work.
296 m_q = new Quaternion();
297 } else if (dotNorm < -1.0 + 1E-9) {
298 // If the dot product is -1, the two vectors are antiparallel, so a 180°
299 // rotation is required. Any other vector can be used to generate an
300 // orthogonal one.
301
302 double x = Math.abs(initial.get(0, 0));
303 double y = Math.abs(initial.get(1, 0));
304 double z = Math.abs(initial.get(2, 0));
305
306 // Find vector that is most orthogonal to initial vector
307 Vector<N3> other;
308 if (x < y) {
309 if (x < z) {
310 // Use x-axis
311 other = VecBuilder.fill(1, 0, 0);
312 } else {
313 // Use z-axis
314 other = VecBuilder.fill(0, 0, 1);
315 }
316 } else {
317 if (y < z) {
318 // Use y-axis
319 other = VecBuilder.fill(0, 1, 0);
320 } else {
321 // Use z-axis
322 other = VecBuilder.fill(0, 0, 1);
323 }
324 }
325
326 var axis = Vector.cross(initial, other);
327
328 double axisNorm = axis.norm();
329 m_q =
330 new Quaternion(
331 0.0, axis.get(0, 0) / axisNorm, axis.get(1, 0) / axisNorm, axis.get(2, 0) / axisNorm);
332 } else {
333 var axis = Vector.cross(initial, last);
334
335 // https://stackoverflow.com/a/11741520
336 m_q =
337 new Quaternion(normProduct + dot, axis.get(0, 0), axis.get(1, 0), axis.get(2, 0))
338 .normalize();
339 }
340 }
341
349 public Rotation3d(Rotation2d rotation) {
350 this(0.0, 0.0, rotation.getRadians());
351 }
352
362 public static Rotation3d fromRadians(double roll, double pitch, double yaw) {
363 return new Rotation3d(roll, pitch, yaw);
364 }
365
375 public static Rotation3d fromDegrees(double roll, double pitch, double yaw) {
376 return new Rotation3d(Math.toRadians(roll), Math.toRadians(pitch), Math.toRadians(yaw));
377 }
378
388 public static Rotation3d fromRotations(double roll, double pitch, double yaw) {
389 return new Rotation3d(
390 rotationsToRadians(roll), rotationsToRadians(pitch), rotationsToRadians(yaw));
391 }
392
399 return new Rotation3d(m_q.inverse());
400 }
401
408 public Rotation3d times(double scalar) {
409 return Rotation3d.ZERO.interpolate(this, scalar);
410 }
411
418 public Rotation3d div(double scalar) {
419 return times(1.0 / scalar);
420 }
421
433 return new Rotation3d(other.m_q.times(m_q));
434 }
435
446 // To apply a quaternion intrinsically, we must right-multiply by that quaternion.
447 // Therefore, "this_q relative to other_q" is the q such that other_q q = this_q:
448 //
449 // other_q q = this_q
450 // q = other_q⁻¹ this_q
451 return new Rotation3d(other.m_q.inverse().times(m_q));
452 }
453
463 public Rotation3d integrate(double rollRate, double pitchRate, double yawRate, double dt) {
464 // qₖ₊₁ = qₖ exp(1/2 W dt) where W = 0 + ω_x î + ω_y ĵ + ω_z k̂
465 //
466 // https://math.stackexchange.com/a/2099673
467 var W = new Quaternion(0.0, rollRate, pitchRate, yawRate);
468 return new Rotation3d(m_q.times(W.times(dt / 2.0).exp()));
469 }
470
477 return m_q;
478 }
479
485 public double getX() {
486 final var w = m_q.getW();
487 final var x = m_q.getX();
488 final var y = m_q.getY();
489 final var z = m_q.getZ();
490
491 // wpimath/docs/Quaternion.md
492 final var cxcy = 1.0 - 2.0 * (x * x + y * y);
493 final var sxcy = 2.0 * (w * x + y * z);
494 final var cy_sq = cxcy * cxcy + sxcy * sxcy;
495 if (cy_sq > 1e-20) {
496 return Math.atan2(sxcy, cxcy);
497 } else {
498 return 0.0;
499 }
500 }
501
507 public double getY() {
508 final var w = m_q.getW();
509 final var x = m_q.getX();
510 final var y = m_q.getY();
511 final var z = m_q.getZ();
512
513 // https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles#Quaternion_to_Euler_angles_(in_3-2-1_sequence)_conversion
514 double ratio = 2.0 * (w * y - z * x);
515 if (Math.abs(ratio) >= 1.0) {
516 return Math.copySign(Math.PI / 2.0, ratio);
517 } else {
518 return Math.asin(ratio);
519 }
520 }
521
527 public double getZ() {
528 final var w = m_q.getW();
529 final var x = m_q.getX();
530 final var y = m_q.getY();
531 final var z = m_q.getZ();
532
533 // wpimath/docs/Quaternion.md
534 final var cycz = 1.0 - 2.0 * (y * y + z * z);
535 final var cysz = 2.0 * (w * z + x * y);
536 final var cy_sq = cycz * cycz + cysz * cysz;
537 if (cy_sq > 1e-20) {
538 return Math.atan2(cysz, cycz);
539 } else {
540 return Math.atan2(2.0 * w * z, w * w - z * z);
541 }
542 }
543
549 public Angle getMeasureX() {
550 return Radians.of(getX());
551 }
552
558 public Angle getMeasureY() {
559 return Radians.of(getY());
560 }
561
567 public Angle getMeasureZ() {
568 return Radians.of(getZ());
569 }
570
577 double norm =
578 Math.sqrt(m_q.getX() * m_q.getX() + m_q.getY() * m_q.getY() + m_q.getZ() * m_q.getZ());
579 if (norm == 0.0) {
580 return VecBuilder.fill(0.0, 0.0, 0.0);
581 } else {
582 return VecBuilder.fill(m_q.getX() / norm, m_q.getY() / norm, m_q.getZ() / norm);
583 }
584 }
585
591 public double getAngle() {
592 double norm =
593 Math.sqrt(m_q.getX() * m_q.getX() + m_q.getY() * m_q.getY() + m_q.getZ() * m_q.getZ());
594 return 2.0 * Math.atan2(norm, m_q.getW());
595 }
596
603 double w = m_q.getW();
604 double x = m_q.getX();
605 double y = m_q.getY();
606 double z = m_q.getZ();
607
608 // https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation#Quaternion-derived_rotation_matrix
609 return MatBuilder.fill(
610 Nat.N3(),
611 Nat.N3(),
612 1.0 - 2.0 * (y * y + z * z),
613 2.0 * (x * y - w * z),
614 2.0 * (x * z + w * y),
615 2.0 * (x * y + w * z),
616 1.0 - 2.0 * (x * x + z * z),
617 2.0 * (y * z - w * x),
618 2.0 * (x * z - w * y),
619 2.0 * (y * z + w * x),
620 1.0 - 2.0 * (x * x + y * y));
621 }
622
629 return m_q.toRotationVector();
630 }
631
637 public Angle getMeasureAngle() {
638 return Radians.of(getAngle());
639 }
640
647 return new Rotation2d(getZ());
648 }
649
650 @Override
651 public String toString() {
652 return String.format("Rotation3d(%s)", m_q);
653 }
654
661 @Override
662 public boolean equals(Object obj) {
663 return obj instanceof Rotation3d other
664 && Math.abs(Math.abs(m_q.dot(other.m_q)) - m_q.norm() * other.m_q.norm()) < 1e-9;
665 }
666
667 @Override
668 public int hashCode() {
669 return Objects.hash(m_q);
670 }
671
672 @Override
673 public Rotation3d interpolate(Rotation3d endValue, double t) {
674 // https://en.wikipedia.org/wiki/Slerp#Quaternion_Slerp
675 //
676 // slerp(q₀, q₁, t) = (q₁q₀⁻¹)ᵗq₀
677 //
678 // We negate the delta quaternion if necessary to take the shortest path
679 var q0 = m_q;
680 var q1 = endValue.m_q;
681 var delta = q1.times(q0.inverse());
682 if (delta.getW() < 0.0) {
683 delta = new Quaternion(-delta.getW(), -delta.getX(), -delta.getY(), -delta.getZ());
684 }
685 return new Rotation3d(delta.pow(t).times(q0));
686 }
687
689 public static final Rotation3dProto proto = new Rotation3dProto();
690
692 public static final Rotation3dStruct struct = new Rotation3dStruct();
693}
Quaternion times(double scalar)
Rotation3d(@Json.Alias("quaternion") Quaternion q)
Rotation3d div(double scalar)
static Rotation3d fromDegrees(double roll, double pitch, double yaw)
static Rotation3d fromRotations(double roll, double pitch, double yaw)
Rotation3d(Angle roll, Angle pitch, Angle yaw)
Rotation3d(Vector< N3 > axis, double angleRadians)
static final Rotation3d ZERO
Rotation3d(Vector< N3 > axis, Angle angle)
Rotation3d(double roll, double pitch, double yaw)
static final Rotation3dProto proto
Rotation3d(Vector< N3 > initial, Vector< N3 > last)
Rotation3d(Matrix< N3, N3 > rotationMatrix)
Rotation3d relativeTo(Rotation3d other)
static Rotation3d fromRadians(double roll, double pitch, double yaw)
Rotation3d integrate(double rollRate, double pitchRate, double yawRate, double dt)
Rotation3d times(double scalar)
Rotation3d rotateBy(Rotation3d other)
static< R extends Num, C extends Num > Matrix< R, C > fill(Nat< R > rows, Nat< C > cols, double... data)
static< D extends Num > Matrix< D, D > eye(Nat< D > dim)
Definition Matrix.java:613
final double get(int row, int col)
Definition Matrix.java:125
static Vector< N1 > fill(double n1)
double dot(Vector< R > other)
Definition Vector.java:112
static Vector< N3 > cross(Vector< N3 > a, Vector< N3 > b)
Definition Vector.java:157
static void reportError(String error, StackTraceElement[] stackTrace)
static Nat< N3 > N3()
Definition Nat.java:81