Skip to content

Commit 95b9bd8

Browse files
authored
[wpimath] Make geometry classes constexpr (#7222)
1 parent 2054d0f commit 95b9bd8

30 files changed

+1325
-1115
lines changed

wpimath/src/main/java/edu/wpi/first/math/geometry/Quaternion.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,9 @@ public Quaternion log() {
295295
double v_scalar;
296296

297297
if (v_norm < 1e-9) {
298-
// Taylor series expansion of atan2(y / x) / y around y = 0 => 1/x - y²/3*x³ + O(y⁴)
298+
// Taylor series expansion of atan2(y/x)/y at y = 0:
299+
//
300+
// 1/x - 1/3 y²/x³ + O(y⁴)
299301
v_scalar = 1.0 / getW() - 1.0 / 3.0 * v_norm * v_norm / (getW() * getW() * getW());
300302
} else {
301303
v_scalar = Math.atan2(v_norm, getW()) / v_norm;

wpimath/src/main/java/edu/wpi/first/math/geometry/Rotation3d.java

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import edu.wpi.first.util.protobuf.ProtobufSerializable;
2525
import edu.wpi.first.util.struct.StructSerializable;
2626
import java.util.Objects;
27-
import org.ejml.dense.row.factory.DecompositionFactory_DDRM;
2827

2928
/** A rotation in a 3D coordinate frame represented by a quaternion. */
3029
@JsonIgnoreProperties(ignoreUnknown = true)
@@ -230,27 +229,42 @@ public Rotation3d(Vector<N3> initial, Vector<N3> last) {
230229
// there's no rotation. The default initialization of m_q will work.
231230
m_q = new Quaternion();
232231
} else if (dotNorm < -1.0 + 1E-9) {
233-
// If the dot product is -1, the two vectors point in opposite directions
234-
// so a 180 degree rotation is required. Any orthogonal vector can be used
235-
// for it. Q in the QR decomposition is an orthonormal basis, so it
236-
// contains orthogonal unit vectors.
237-
var X = VecBuilder.fill(initial.get(0, 0), initial.get(1, 0), initial.get(2, 0));
238-
final var qr = DecompositionFactory_DDRM.qr(3, 1);
239-
qr.decompose(X.getStorage().getMatrix());
240-
final var Q = qr.getQ(null, false);
241-
242-
// w = cos(θ/2) = cos(90°) = 0
243-
//
244-
// For x, y, and z, we use the second column of Q because the first is
245-
// parallel instead of orthogonal. The third column would also work.
246-
m_q = new Quaternion(0.0, Q.get(0, 1), Q.get(1, 1), Q.get(2, 1));
232+
// If the dot product is -1, the two vectors are antiparallel, so a 180°
233+
// rotation is required. Any other vector can be used to generate an
234+
// orthogonal one.
235+
236+
double x = Math.abs(initial.get(0, 0));
237+
double y = Math.abs(initial.get(1, 0));
238+
double z = Math.abs(initial.get(2, 0));
239+
240+
// Find vector that is most orthogonal to initial vector
241+
Vector<N3> other;
242+
if (x < y) {
243+
if (x < z) {
244+
// Use x-axis
245+
other = VecBuilder.fill(1, 0, 0);
246+
} else {
247+
// Use z-axis
248+
other = VecBuilder.fill(0, 0, 1);
249+
}
250+
} else {
251+
if (y < z) {
252+
// Use y-axis
253+
other = VecBuilder.fill(0, 1, 0);
254+
} else {
255+
// Use z-axis
256+
other = VecBuilder.fill(0, 0, 1);
257+
}
258+
}
259+
260+
var axis = Vector.cross(initial, other);
261+
262+
double axisNorm = axis.norm();
263+
m_q =
264+
new Quaternion(
265+
0.0, axis.get(0, 0) / axisNorm, axis.get(1, 0) / axisNorm, axis.get(2, 0) / axisNorm);
247266
} else {
248-
// initial x last
249-
var axis =
250-
VecBuilder.fill(
251-
initial.get(1, 0) * last.get(2, 0) - last.get(1, 0) * initial.get(2, 0),
252-
last.get(0, 0) * initial.get(2, 0) - initial.get(0, 0) * last.get(2, 0),
253-
initial.get(0, 0) * last.get(1, 0) - last.get(0, 0) * initial.get(1, 0));
267+
var axis = Vector.cross(initial, last);
254268

255269
// https://stackoverflow.com/a/11741520
256270
m_q =

wpimath/src/main/native/cpp/geometry/CoordinateAxis.cpp

Lines changed: 0 additions & 41 deletions
This file was deleted.

wpimath/src/main/native/cpp/geometry/CoordinateSystem.cpp

Lines changed: 0 additions & 76 deletions
This file was deleted.

wpimath/src/main/native/cpp/geometry/Ellipse2d.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,6 @@
88

99
using namespace frc;
1010

11-
units::meter_t Ellipse2d::Distance(const Translation2d& point) const {
12-
return FindNearestPoint(point).Distance(point);
13-
}
14-
1511
Translation2d Ellipse2d::FindNearestPoint(const Translation2d& point) const {
1612
// Check if already in ellipse
1713
if (Contains(point)) {

wpimath/src/main/native/cpp/geometry/Pose2d.cpp

Lines changed: 0 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -4,101 +4,8 @@
44

55
#include "frc/geometry/Pose2d.h"
66

7-
#include <cmath>
8-
97
#include <wpi/json.h>
108

11-
#include "frc/MathUtil.h"
12-
13-
using namespace frc;
14-
15-
Transform2d Pose2d::operator-(const Pose2d& other) const {
16-
const auto pose = this->RelativeTo(other);
17-
return Transform2d{pose.Translation(), pose.Rotation()};
18-
}
19-
20-
Pose2d Pose2d::RelativeTo(const Pose2d& other) const {
21-
const Transform2d transform{other, *this};
22-
return {transform.Translation(), transform.Rotation()};
23-
}
24-
25-
Pose2d Pose2d::Exp(const Twist2d& twist) const {
26-
const auto dx = twist.dx;
27-
const auto dy = twist.dy;
28-
const auto dtheta = twist.dtheta.value();
29-
30-
const auto sinTheta = std::sin(dtheta);
31-
const auto cosTheta = std::cos(dtheta);
32-
33-
double s, c;
34-
if (std::abs(dtheta) < 1E-9) {
35-
s = 1.0 - 1.0 / 6.0 * dtheta * dtheta;
36-
c = 0.5 * dtheta;
37-
} else {
38-
s = sinTheta / dtheta;
39-
c = (1 - cosTheta) / dtheta;
40-
}
41-
42-
const Transform2d transform{Translation2d{dx * s - dy * c, dx * c + dy * s},
43-
Rotation2d{cosTheta, sinTheta}};
44-
45-
return *this + transform;
46-
}
47-
48-
Twist2d Pose2d::Log(const Pose2d& end) const {
49-
const auto transform = end.RelativeTo(*this);
50-
const auto dtheta = transform.Rotation().Radians().value();
51-
const auto halfDtheta = dtheta / 2.0;
52-
53-
const auto cosMinusOne = transform.Rotation().Cos() - 1;
54-
55-
double halfThetaByTanOfHalfDtheta;
56-
57-
if (std::abs(cosMinusOne) < 1E-9) {
58-
halfThetaByTanOfHalfDtheta = 1.0 - 1.0 / 12.0 * dtheta * dtheta;
59-
} else {
60-
halfThetaByTanOfHalfDtheta =
61-
-(halfDtheta * transform.Rotation().Sin()) / cosMinusOne;
62-
}
63-
64-
const Translation2d translationPart =
65-
transform.Translation().RotateBy(
66-
{halfThetaByTanOfHalfDtheta, -halfDtheta}) *
67-
std::hypot(halfThetaByTanOfHalfDtheta, halfDtheta);
68-
69-
return {translationPart.X(), translationPart.Y(), units::radian_t{dtheta}};
70-
}
71-
72-
Pose2d Pose2d::Nearest(std::span<const Pose2d> poses) const {
73-
return *std::min_element(
74-
poses.begin(), poses.end(), [this](const Pose2d& a, const Pose2d& b) {
75-
auto aDistance = this->Translation().Distance(a.Translation());
76-
auto bDistance = this->Translation().Distance(b.Translation());
77-
78-
// If the distances are equal sort by difference in rotation
79-
if (aDistance == bDistance) {
80-
return std::abs((this->Rotation() - a.Rotation()).Radians().value()) <
81-
std::abs((this->Rotation() - b.Rotation()).Radians().value());
82-
}
83-
return aDistance < bDistance;
84-
});
85-
}
86-
87-
Pose2d Pose2d::Nearest(std::initializer_list<Pose2d> poses) const {
88-
return *std::min_element(
89-
poses.begin(), poses.end(), [this](const Pose2d& a, const Pose2d& b) {
90-
auto aDistance = this->Translation().Distance(a.Translation());
91-
auto bDistance = this->Translation().Distance(b.Translation());
92-
93-
// If the distances are equal sort by difference in rotation
94-
if (aDistance == bDistance) {
95-
return std::abs((this->Rotation() - a.Rotation()).Radians().value()) <
96-
std::abs((this->Rotation() - b.Rotation()).Radians().value());
97-
}
98-
return aDistance < bDistance;
99-
});
100-
}
101-
1029
void frc::to_json(wpi::json& json, const Pose2d& pose) {
10310
json = wpi::json{{"translation", pose.Translation()},
10411
{"rotation", pose.Rotation()}};

0 commit comments

Comments
 (0)