001/**
002 * Copyright 2024 The Space Cookies : Girl Scout Troop #62868 and FRC Team #1868
003 * Open Source Software; you may modify and/or share it under the terms of
004 * the 3-Clause BSD License found in the root directory of this project.
005 */
006
007package tagalong.math;
008
009import edu.wpi.first.math.MatBuilder;
010import edu.wpi.first.math.Matrix;
011import edu.wpi.first.math.Nat;
012import edu.wpi.first.math.Pair;
013import edu.wpi.first.math.geometry.Pose2d;
014import edu.wpi.first.math.geometry.Rotation2d;
015import edu.wpi.first.math.geometry.Translation2d;
016import edu.wpi.first.math.geometry.Twist2d;
017import edu.wpi.first.math.numbers.N1;
018import edu.wpi.first.math.numbers.N2;
019import edu.wpi.first.math.numbers.N3;
020import edu.wpi.first.math.numbers.N4;
021
022/**
023 * Algebraic functions
024 */
025public class AlgebraicUtils {
026  /**
027   * @param x Cartesian x coordinate
028   * @param y Cartesian y coordinate
029   * @return pair representing the polar coordinate radius and theta in radians
030   */
031  public static Pair<Double, Double> cart2pol(final double x, final double y) {
032    final double r = Math.sqrt(x * x + y * y);
033    final double theta = Math.atan2(y, x);
034    return new Pair<>(r, theta);
035  }
036
037  /**
038   * Converts polar coordinates to Cartesian coordinates.
039   *
040   * @param r     radius in polar coordinates
041   * @param theta angle in radians
042   * @return pair of Cartesian coordinates x and y
043   */
044  public static Pair<Double, Double> pol2cart(final double r, final double theta) {
045    final double x = r * Math.cos(theta);
046    final double y = r * Math.sin(theta);
047    return new Pair<>(x, y);
048  }
049
050  /**
051   * Constrains an angle to be within [-pi, pi).
052   *
053   * @param angle angle in radians
054   * @return angle in radians that is between [-pi,pi]
055   */
056  public static double constrainAngleNegPiToPi(final double angle) {
057    double x = (angle + Math.PI) % (2.0 * Math.PI);
058    if (x < 0.0) {
059      x += 2.0 * Math.PI;
060    }
061    return x - Math.PI;
062  }
063
064  /**
065   * Scopes the angle to be at most pi radians away from the reference angle
066   *
067   * @param referenceAngleRad Base of the scope to place the angle in
068   * @param desiredAngleRad   Angle that should be placed in radians
069   * @return |angle| placed within within [-pi, pi) of |referenceAngle|.
070   */
071  public static double placeInScopeRad(
072      final double referenceAngleRad, final double desiredAngleRad
073  ) {
074    return referenceAngleRad + constrainAngleNegPiToPi(desiredAngleRad - referenceAngleRad);
075  }
076
077  /**
078   * Scopes the angle to be at most 180 degrees away from the reference angle
079   *
080   * @param referenceAngleDeg Base of the scope to place the angle in
081   * @param desiredAngleDeg   Angle that should be placed in degrees
082   * @return |angle| placed within within [-180, 180) of |referenceAngle|.
083   */
084  public static double placeInScopeDeg(
085      final double referenceAngleDeg, final double desiredAngleDeg
086  ) {
087    return Math.toDegrees(
088        placeInScopeRad(Math.toRadians(referenceAngleDeg), Math.toRadians(desiredAngleDeg))
089    );
090  }
091
092  /**
093   * Returns the desired angle in the closest scope of the current angle
094   *
095   * @param referenceAngleRot Current angle in rotations
096   * @param desiredAngleRot Target angle in rotations
097   * @return Scoped angle in rotations
098   */
099  public static double placeInScopeRot(
100      final double referenceAngleRot, final double desiredAngleRot
101  ) {
102    double delta = (desiredAngleRot - referenceAngleRot) % 1.0;
103    if (delta > 0.5) {
104      delta += -1.0;
105    } else if (delta < -0.5) {
106      delta += 1.0;
107    }
108    return referenceAngleRot + delta;
109  }
110
111  /**
112   * Rotates a vector by a given angle.
113   *
114   * @param vector initial vector
115   * @param theta  desired angle rotation
116   * @return rotated vector as a matrix
117   */
118  public static Matrix<N2, N1> rotateVector(final Matrix<N2, N1> vector, final double theta) {
119    final double x1 = vector.get(0, 0);
120    final double y1 = vector.get(1, 0);
121    final double c = Math.cos(theta);
122    final double s = Math.sin(theta);
123    final double x2 = c * x1 - s * y1;
124    final double y2 = s * x1 + c * y1;
125    return MatBuilder.fill(Nat.N2(), Nat.N1(), x2, y2);
126  }
127
128  /**
129   * Returns double z component from the 4*4 matrix.
130   *
131   * @param matrix 4*4 Matrix
132   * @return double Z component from the matrix
133   */
134  public static double extractTFMatrixZ(final Matrix<N4, N4> matrix) {
135    return matrix.get(2, 3);
136  }
137
138  /**
139   * Returns double Y rotation form the 4*4 matrix.
140   *
141   * @param matrix 4*4 matrix
142   * @return double Y rotation from the matrix
143   */
144  public static double extractTFMatrixYRot(final Matrix<N4, N4> matrix) {
145    return Math.atan2(
146        -matrix.get(2, 0),
147        Math.sqrt(matrix.get(2, 1) * matrix.get(2, 1) + matrix.get(2, 2) * matrix.get(2, 2))
148    );
149  }
150
151  /**
152   * Converts the 2D pose into a matrix with a rotation around the z-axis.
153   *
154   * @param pose 2D position of the robot on the field
155   * @return Transformation matrix from Pose2d
156   */
157  public static Matrix<N4, N4> pose2DToZRotTFMatrix(Pose2d pose) {
158    return makeZRotTFMatrix(pose.getX(), pose.getY(), 0.0, pose.getRotation().getRadians());
159  }
160
161  /**
162   * Converts matrix back into 2D pose.
163   *
164   * @param zRotTFMatrix a 4x4 matrix representing a 2D affine transformation with translation and
165   *     rotation
166   * @return Pose2d
167   */
168  public static Pose2d zRotTFMatrixToPose(final Matrix<N4, N4> zRotTFMatrix) {
169    return new Pose2d(
170        zRotTFMatrix.get(0, 3),
171        zRotTFMatrix.get(1, 3),
172        new Rotation2d(Math.atan2(zRotTFMatrix.get(1, 0), zRotTFMatrix.get(0, 0)))
173    );
174  }
175
176  /**
177   * Transforms a 3D point into a given frame of reference using matrix
178   * operations.
179   *
180   * @param point 3x1 column matrix representing the coordinates of a point.
181   *              ---not sure if this is
182   *              right
183   * @param frame 4x4 matrix representing the pose
184   * @return Transformed point as matrix
185   */
186  public static Matrix<N3, N1> getPointInFrame(Matrix<N3, N1> point, final Matrix<N4, N4> frame) {
187    final Matrix<N4, N1> pointMod =
188        MatBuilder.fill(Nat.N4(), Nat.N1(), point.get(0, 0), point.get(1, 0), point.get(2, 0), 1);
189    return frame.inv().times(pointMod).block(Nat.N3(), Nat.N1(), 0, 0);
190  }
191
192  /**
193   * Creates a matrix about with rotation about the y-axis.
194   *
195   * @param x     translation along x-axis
196   * @param y     translation along y-axis
197   * @param z     translation along z-axis
198   * @param theta angle of rotation about y-axis
199   * @return transformation matrix for Y rotation
200   */
201  public static Matrix<N4, N4> makeYRotTFMatrix(
202      final double x, final double y, final double z, final double theta
203  ) {
204    return MatBuilder.fill(
205        Nat.N4(),
206        Nat.N4(),
207        Math.cos(theta),
208        0,
209        Math.sin(theta),
210        x,
211        0,
212        1,
213        0,
214        y,
215        -Math.sin(theta),
216        0,
217        Math.cos(theta),
218        z,
219        0,
220        0,
221        0,
222        1
223    );
224  }
225
226  /**
227   *
228   * @param x     translation along x-axis
229   * @param y     translation along y-axis
230   * @param z     translation along z-axis
231   * @param theta angle of rotation about Z-axis
232   * @return Transformation matrix for Z rotation
233   */
234  public static Matrix<N4, N4> makeZRotTFMatrix(
235      final double x, final double y, final double z, final double theta
236  ) {
237    return MatBuilder.fill(
238        Nat.N4(),
239        Nat.N4(),
240        Math.cos(theta),
241        -Math.sin(theta),
242        0,
243        x,
244        Math.sin(theta),
245        Math.cos(theta),
246        0,
247        y,
248        0,
249        0,
250        1,
251        z,
252        0,
253        0,
254        0,
255        1
256    );
257  }
258
259  private static final double kEps = 1E-9;
260
261  /**
262   * Obtain a new Pose2d from a (constant curvature) velocity. See:
263   * https://github.com/strasdat/Sophus/blob/master/sophus/se2.hpp. Borrowed from
264   * 254:
265   * https://github.com/Team254/FRC-2022-Public/blob/main/src/main/java/com/team254/lib/geometry/Pose2d.java
266   *
267   * @param delta robot change
268   * @return New Pose2d from velocity
269   */
270  public static Pose2d exp(final Twist2d delta) {
271    final double sin_theta = Math.sin(delta.dtheta);
272    final double cos_theta = Math.cos(delta.dtheta);
273    final double s = Math.abs(delta.dtheta) < kEps ? 1.0 - 1.0 / 6.0 * delta.dtheta * delta.dtheta
274                                                   : sin_theta / delta.dtheta;
275    final double c =
276        Math.abs(delta.dtheta) < kEps ? 0.5 * delta.dtheta : (1.0 - cos_theta) / delta.dtheta;
277    return new Pose2d(
278        new Translation2d(delta.dx * s - delta.dy * c, delta.dx * c + delta.dy * s),
279        new Rotation2d(cos_theta, sin_theta)
280    );
281  }
282
283  /**
284   * Logical inverse of the above. Borrowed from 254:
285   * https://github.com/Team254/FRC-2022-Public/blob/main/src/main/java/com/team254/lib/geometry/Pose2d.java
286   *
287   * @param transform Pose to derive velocity from
288   * @return Twist2d representing transformation
289   */
290  public static Twist2d log(final Pose2d transform) {
291    final double dtheta = transform.getRotation().getRadians();
292    final double half_dtheta = 0.5 * dtheta;
293    final double cos_minus_one = Math.cos(transform.getRotation().getRadians()) - 1.0;
294    final double halftheta_by_tan_of_halfdtheta = Math.abs(cos_minus_one) < kEps
295        ? 1.0 - 1.0 / 12.0 * dtheta * dtheta
296        : -(half_dtheta * Math.sin(transform.getRotation().getRadians())) / cos_minus_one;
297    final Translation2d translation_part = transform.getTranslation().rotateBy(
298        new Rotation2d(halftheta_by_tan_of_halfdtheta, -half_dtheta)
299    );
300    return new Twist2d(translation_part.getX(), translation_part.getY(), dtheta);
301  }
302
303  /**
304   * Returns a modulo result that matches the C++ rather than Java implementation
305   *
306   * @param num   Numerator
307   * @param denom Denominator
308   * @return modulo result
309   */
310  public static double cppMod(double num, double denom) {
311    return ((num % denom) + denom) % denom;
312  }
313
314  /**
315   * Clamps a target value between the minimum and maximum values, ensuring the
316   * value is in the given range.
317   *
318   * @param target target value
319   * @param min    min value
320   * @param max    max value
321   * @return the target value rounded to be in range (min, max)
322   */
323  public static double clamp(double target, double min, double max) {
324    return Math.max(min, Math.min(max, target));
325  }
326
327  /**
328   *
329   * Units must be consistent between all 3 values
330   *
331   * @param position   actual position of the mechanism
332   * @param lowerBound minimum position of the mechanism
333   * @param upperBound maximum position of the mechanism
334   * @return True if position is greater than or equal to the lowerBound and less
335   *         than or equal to the upperBound.
336   *         False otherwise, meaning the position falls out of the range
337   */
338  public static boolean inTolerance(double position, double lowerBound, double upperBound) {
339    return (position >= lowerBound) && (position <= upperBound);
340  }
341}