001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package edu.wpi.first.math.geometry;
006
007import static edu.wpi.first.units.Units.Meters;
008
009import com.fasterxml.jackson.annotation.JsonAutoDetect;
010import com.fasterxml.jackson.annotation.JsonCreator;
011import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
012import com.fasterxml.jackson.annotation.JsonProperty;
013import edu.wpi.first.math.MatBuilder;
014import edu.wpi.first.math.Matrix;
015import edu.wpi.first.math.Nat;
016import edu.wpi.first.math.geometry.proto.Pose2dProto;
017import edu.wpi.first.math.geometry.struct.Pose2dStruct;
018import edu.wpi.first.math.interpolation.Interpolatable;
019import edu.wpi.first.math.numbers.N3;
020import edu.wpi.first.units.measure.Distance;
021import edu.wpi.first.util.protobuf.ProtobufSerializable;
022import edu.wpi.first.util.struct.StructSerializable;
023import java.util.Collection;
024import java.util.Collections;
025import java.util.Comparator;
026import java.util.Objects;
027
028/** Represents a 2D pose containing translational and rotational elements. */
029@JsonIgnoreProperties(ignoreUnknown = true)
030@JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE)
031public class Pose2d implements Interpolatable<Pose2d>, ProtobufSerializable, StructSerializable {
032  /**
033   * A preallocated Pose2d representing the origin.
034   *
035   * <p>This exists to avoid allocations for common poses.
036   */
037  public static final Pose2d kZero = new Pose2d();
038
039  private final Translation2d m_translation;
040  private final Rotation2d m_rotation;
041
042  /** Constructs a pose at the origin facing toward the positive X axis. */
043  public Pose2d() {
044    m_translation = Translation2d.kZero;
045    m_rotation = Rotation2d.kZero;
046  }
047
048  /**
049   * Constructs a pose with the specified translation and rotation.
050   *
051   * @param translation The translational component of the pose.
052   * @param rotation The rotational component of the pose.
053   */
054  @JsonCreator
055  public Pose2d(
056      @JsonProperty(required = true, value = "translation") Translation2d translation,
057      @JsonProperty(required = true, value = "rotation") Rotation2d rotation) {
058    m_translation = translation;
059    m_rotation = rotation;
060  }
061
062  /**
063   * Constructs a pose with x and y translations instead of a separate Translation2d.
064   *
065   * @param x The x component of the translational component of the pose.
066   * @param y The y component of the translational component of the pose.
067   * @param rotation The rotational component of the pose.
068   */
069  public Pose2d(double x, double y, Rotation2d rotation) {
070    m_translation = new Translation2d(x, y);
071    m_rotation = rotation;
072  }
073
074  /**
075   * Constructs a pose with x and y translations instead of a separate Translation2d. The X and Y
076   * translations will be converted to and tracked as meters.
077   *
078   * @param x The x component of the translational component of the pose.
079   * @param y The y component of the translational component of the pose.
080   * @param rotation The rotational component of the pose.
081   */
082  public Pose2d(Distance x, Distance y, Rotation2d rotation) {
083    this(x.in(Meters), y.in(Meters), rotation);
084  }
085
086  /**
087   * Constructs a pose with the specified affine transformation matrix.
088   *
089   * @param matrix The affine transformation matrix.
090   * @throws IllegalArgumentException if the affine transformation matrix is invalid.
091   */
092  public Pose2d(Matrix<N3, N3> matrix) {
093    m_translation = new Translation2d(matrix.get(0, 2), matrix.get(1, 2));
094    m_rotation = new Rotation2d(matrix.block(2, 2, 0, 0));
095    if (matrix.get(2, 0) != 0.0 || matrix.get(2, 1) != 0.0 || matrix.get(2, 2) != 1.0) {
096      throw new IllegalArgumentException("Affine transformation matrix is invalid");
097    }
098  }
099
100  /**
101   * Transforms the pose by the given transformation and returns the new transformed pose.
102   *
103   * <pre>
104   * [x_new]    [cos, -sin, 0][transform.x]
105   * [y_new] += [sin,  cos, 0][transform.y]
106   * [t_new]    [  0,    0, 1][transform.t]
107   * </pre>
108   *
109   * @param other The transform to transform the pose by.
110   * @return The transformed pose.
111   */
112  public Pose2d plus(Transform2d other) {
113    return transformBy(other);
114  }
115
116  /**
117   * Returns the Transform2d that maps the one pose to another.
118   *
119   * @param other The initial pose of the transformation.
120   * @return The transform that maps the other pose to the current pose.
121   */
122  public Transform2d minus(Pose2d other) {
123    final var pose = this.relativeTo(other);
124    return new Transform2d(pose.getTranslation(), pose.getRotation());
125  }
126
127  /**
128   * Returns the translation component of the transformation.
129   *
130   * @return The translational component of the pose.
131   */
132  @JsonProperty
133  public Translation2d getTranslation() {
134    return m_translation;
135  }
136
137  /**
138   * Returns the X component of the pose's translation.
139   *
140   * @return The x component of the pose's translation.
141   */
142  public double getX() {
143    return m_translation.getX();
144  }
145
146  /**
147   * Returns the Y component of the pose's translation.
148   *
149   * @return The y component of the pose's translation.
150   */
151  public double getY() {
152    return m_translation.getY();
153  }
154
155  /**
156   * Returns the X component of the pose's translation in a measure.
157   *
158   * @return The x component of the pose's translation in a measure.
159   */
160  public Distance getMeasureX() {
161    return m_translation.getMeasureX();
162  }
163
164  /**
165   * Returns the Y component of the pose's translation in a measure.
166   *
167   * @return The y component of the pose's translation in a measure.
168   */
169  public Distance getMeasureY() {
170    return m_translation.getMeasureY();
171  }
172
173  /**
174   * Returns the rotational component of the transformation.
175   *
176   * @return The rotational component of the pose.
177   */
178  @JsonProperty
179  public Rotation2d getRotation() {
180    return m_rotation;
181  }
182
183  /**
184   * Multiplies the current pose by a scalar.
185   *
186   * @param scalar The scalar.
187   * @return The new scaled Pose2d.
188   */
189  public Pose2d times(double scalar) {
190    return new Pose2d(m_translation.times(scalar), m_rotation.times(scalar));
191  }
192
193  /**
194   * Divides the current pose by a scalar.
195   *
196   * @param scalar The scalar.
197   * @return The new scaled Pose2d.
198   */
199  public Pose2d div(double scalar) {
200    return times(1.0 / scalar);
201  }
202
203  /**
204   * Rotates the pose around the origin and returns the new pose.
205   *
206   * @param other The rotation to transform the pose by.
207   * @return The transformed pose.
208   */
209  public Pose2d rotateBy(Rotation2d other) {
210    return new Pose2d(m_translation.rotateBy(other), m_rotation.rotateBy(other));
211  }
212
213  /**
214   * Transforms the pose by the given transformation and returns the new pose. See + operator for
215   * the matrix multiplication performed.
216   *
217   * @param other The transform to transform the pose by.
218   * @return The transformed pose.
219   */
220  public Pose2d transformBy(Transform2d other) {
221    return new Pose2d(
222        m_translation.plus(other.getTranslation().rotateBy(m_rotation)),
223        other.getRotation().plus(m_rotation));
224  }
225
226  /**
227   * Returns the current pose relative to the given pose.
228   *
229   * <p>This function can often be used for trajectory tracking or pose stabilization algorithms to
230   * get the error between the reference and the current pose.
231   *
232   * @param other The pose that is the origin of the new coordinate frame that the current pose will
233   *     be converted into.
234   * @return The current pose relative to the new origin pose.
235   */
236  public Pose2d relativeTo(Pose2d other) {
237    var transform = new Transform2d(other, this);
238    return new Pose2d(transform.getTranslation(), transform.getRotation());
239  }
240
241  /**
242   * Rotates the current pose around a point in 2D space.
243   *
244   * @param point The point in 2D space to rotate around.
245   * @param rot The rotation to rotate the pose by.
246   * @return The new rotated pose.
247   */
248  public Pose2d rotateAround(Translation2d point, Rotation2d rot) {
249    return new Pose2d(m_translation.rotateAround(point, rot), m_rotation.rotateBy(rot));
250  }
251
252  /**
253   * Returns an affine transformation matrix representation of this pose.
254   *
255   * @return An affine transformation matrix representation of this pose.
256   */
257  public Matrix<N3, N3> toMatrix() {
258    var vec = m_translation.toVector();
259    var mat = m_rotation.toMatrix();
260    return MatBuilder.fill(
261        Nat.N3(),
262        Nat.N3(),
263        mat.get(0, 0),
264        mat.get(0, 1),
265        vec.get(0),
266        mat.get(1, 0),
267        mat.get(1, 1),
268        vec.get(1),
269        0.0,
270        0.0,
271        1.0);
272  }
273
274  /**
275   * Returns the nearest Pose2d from a collection of poses. If two or more poses in the collection
276   * have the same distance from this pose, return the one with the closest rotation component.
277   *
278   * @param poses The collection of poses to find the nearest.
279   * @return The nearest Pose2d from the collection.
280   */
281  public Pose2d nearest(Collection<Pose2d> poses) {
282    return Collections.min(
283        poses,
284        Comparator.comparing(
285                (Pose2d other) -> this.getTranslation().getDistance(other.getTranslation()))
286            .thenComparing(
287                (Pose2d other) ->
288                    Math.abs(this.getRotation().minus(other.getRotation()).getRadians())));
289  }
290
291  @Override
292  public String toString() {
293    return String.format("Pose2d(%s, %s)", m_translation, m_rotation);
294  }
295
296  /**
297   * Checks equality between this Pose2d and another object.
298   *
299   * @param obj The other object.
300   * @return Whether the two objects are equal or not.
301   */
302  @Override
303  public boolean equals(Object obj) {
304    return obj instanceof Pose2d pose
305        && m_translation.equals(pose.m_translation)
306        && m_rotation.equals(pose.m_rotation);
307  }
308
309  @Override
310  public int hashCode() {
311    return Objects.hash(m_translation, m_rotation);
312  }
313
314  @Override
315  public Pose2d interpolate(Pose2d endValue, double t) {
316    if (t < 0) {
317      return this;
318    } else if (t >= 1) {
319      return endValue;
320    } else {
321      var twist = endValue.minus(this).log();
322      var scaledTwist = new Twist2d(twist.dx * t, twist.dy * t, twist.dtheta * t);
323      return this.plus(scaledTwist.exp());
324    }
325  }
326
327  /** Pose2d protobuf for serialization. */
328  public static final Pose2dProto proto = new Pose2dProto();
329
330  /** Pose2d struct for serialization. */
331  public static final Pose2dStruct struct = new Pose2dStruct();
332}