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 com.fasterxml.jackson.annotation.JsonAutoDetect;
008import com.fasterxml.jackson.annotation.JsonCreator;
009import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
010import com.fasterxml.jackson.annotation.JsonProperty;
011import edu.wpi.first.math.WPIMathJNI;
012import edu.wpi.first.math.geometry.proto.Pose3dProto;
013import edu.wpi.first.math.geometry.struct.Pose3dStruct;
014import edu.wpi.first.math.interpolation.Interpolatable;
015import edu.wpi.first.util.protobuf.ProtobufSerializable;
016import edu.wpi.first.util.struct.StructSerializable;
017import java.util.Objects;
018
019/** Represents a 3D pose containing translational and rotational elements. */
020@JsonIgnoreProperties(ignoreUnknown = true)
021@JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE)
022public class Pose3d implements Interpolatable<Pose3d>, ProtobufSerializable, StructSerializable {
023  private final Translation3d m_translation;
024  private final Rotation3d m_rotation;
025
026  /** Constructs a pose at the origin facing toward the positive X axis. */
027  public Pose3d() {
028    m_translation = new Translation3d();
029    m_rotation = new Rotation3d();
030  }
031
032  /**
033   * Constructs a pose with the specified translation and rotation.
034   *
035   * @param translation The translational component of the pose.
036   * @param rotation The rotational component of the pose.
037   */
038  @JsonCreator
039  public Pose3d(
040      @JsonProperty(required = true, value = "translation") Translation3d translation,
041      @JsonProperty(required = true, value = "rotation") Rotation3d rotation) {
042    m_translation = translation;
043    m_rotation = rotation;
044  }
045
046  /**
047   * Constructs a pose with x, y, and z translations instead of a separate Translation3d.
048   *
049   * @param x The x component of the translational component of the pose.
050   * @param y The y component of the translational component of the pose.
051   * @param z The z component of the translational component of the pose.
052   * @param rotation The rotational component of the pose.
053   */
054  public Pose3d(double x, double y, double z, Rotation3d rotation) {
055    m_translation = new Translation3d(x, y, z);
056    m_rotation = rotation;
057  }
058
059  /**
060   * Constructs a 3D pose from a 2D pose in the X-Y plane.
061   *
062   * @param pose The 2D pose.
063   */
064  public Pose3d(Pose2d pose) {
065    m_translation = new Translation3d(pose.getX(), pose.getY(), 0.0);
066    m_rotation = new Rotation3d(0.0, 0.0, pose.getRotation().getRadians());
067  }
068
069  /**
070   * Transforms the pose by the given transformation and returns the new transformed pose. The
071   * transform is applied relative to the pose's frame. Note that this differs from {@link
072   * Pose3d#rotateBy(Rotation3d)}, which is applied relative to the global frame and around the
073   * origin.
074   *
075   * @param other The transform to transform the pose by.
076   * @return The transformed pose.
077   */
078  public Pose3d plus(Transform3d other) {
079    return transformBy(other);
080  }
081
082  /**
083   * Returns the Transform3d that maps the one pose to another.
084   *
085   * @param other The initial pose of the transformation.
086   * @return The transform that maps the other pose to the current pose.
087   */
088  public Transform3d minus(Pose3d other) {
089    final var pose = this.relativeTo(other);
090    return new Transform3d(pose.getTranslation(), pose.getRotation());
091  }
092
093  /**
094   * Returns the translation component of the transformation.
095   *
096   * @return The translational component of the pose.
097   */
098  @JsonProperty
099  public Translation3d getTranslation() {
100    return m_translation;
101  }
102
103  /**
104   * Returns the X component of the pose's translation.
105   *
106   * @return The x component of the pose's translation.
107   */
108  public double getX() {
109    return m_translation.getX();
110  }
111
112  /**
113   * Returns the Y component of the pose's translation.
114   *
115   * @return The y component of the pose's translation.
116   */
117  public double getY() {
118    return m_translation.getY();
119  }
120
121  /**
122   * Returns the Z component of the pose's translation.
123   *
124   * @return The z component of the pose's translation.
125   */
126  public double getZ() {
127    return m_translation.getZ();
128  }
129
130  /**
131   * Returns the rotational component of the transformation.
132   *
133   * @return The rotational component of the pose.
134   */
135  @JsonProperty
136  public Rotation3d getRotation() {
137    return m_rotation;
138  }
139
140  /**
141   * Multiplies the current pose by a scalar.
142   *
143   * @param scalar The scalar.
144   * @return The new scaled Pose3d.
145   */
146  public Pose3d times(double scalar) {
147    return new Pose3d(m_translation.times(scalar), m_rotation.times(scalar));
148  }
149
150  /**
151   * Divides the current pose by a scalar.
152   *
153   * @param scalar The scalar.
154   * @return The new scaled Pose3d.
155   */
156  public Pose3d div(double scalar) {
157    return times(1.0 / scalar);
158  }
159
160  /**
161   * Rotates the pose around the origin and returns the new pose.
162   *
163   * @param other The rotation to transform the pose by, which is applied extrinsically (from the
164   *     global frame).
165   * @return The rotated pose.
166   */
167  public Pose3d rotateBy(Rotation3d other) {
168    return new Pose3d(m_translation.rotateBy(other), m_rotation.rotateBy(other));
169  }
170
171  /**
172   * Transforms the pose by the given transformation and returns the new transformed pose. The
173   * transform is applied relative to the pose's frame. Note that this differs from {@link
174   * Pose3d#rotateBy(Rotation3d)}, which is applied relative to the global frame and around the
175   * origin.
176   *
177   * @param other The transform to transform the pose by.
178   * @return The transformed pose.
179   */
180  public Pose3d transformBy(Transform3d other) {
181    return new Pose3d(
182        m_translation.plus(other.getTranslation().rotateBy(m_rotation)),
183        other.getRotation().plus(m_rotation));
184  }
185
186  /**
187   * Returns the current pose relative to the given pose.
188   *
189   * <p>This function can often be used for trajectory tracking or pose stabilization algorithms to
190   * get the error between the reference and the current pose.
191   *
192   * @param other The pose that is the origin of the new coordinate frame that the current pose will
193   *     be converted into.
194   * @return The current pose relative to the new origin pose.
195   */
196  public Pose3d relativeTo(Pose3d other) {
197    var transform = new Transform3d(other, this);
198    return new Pose3d(transform.getTranslation(), transform.getRotation());
199  }
200
201  /**
202   * Obtain a new Pose3d from a (constant curvature) velocity.
203   *
204   * <p>The twist is a change in pose in the robot's coordinate frame since the previous pose
205   * update. When the user runs exp() on the previous known field-relative pose with the argument
206   * being the twist, the user will receive the new field-relative pose.
207   *
208   * <p>"Exp" represents the pose exponential, which is solving a differential equation moving the
209   * pose forward in time.
210   *
211   * @param twist The change in pose in the robot's coordinate frame since the previous pose update.
212   *     For example, if a non-holonomic robot moves forward 0.01 meters and changes angle by 0.5
213   *     degrees since the previous pose update, the twist would be Twist3d(0.01, 0.0, 0.0, new new
214   *     Rotation3d(0.0, 0.0, Units.degreesToRadians(0.5))).
215   * @return The new pose of the robot.
216   */
217  public Pose3d exp(Twist3d twist) {
218    var quaternion = this.getRotation().getQuaternion();
219    double[] resultArray =
220        WPIMathJNI.expPose3d(
221            this.getX(),
222            this.getY(),
223            this.getZ(),
224            quaternion.getW(),
225            quaternion.getX(),
226            quaternion.getY(),
227            quaternion.getZ(),
228            twist.dx,
229            twist.dy,
230            twist.dz,
231            twist.rx,
232            twist.ry,
233            twist.rz);
234    return new Pose3d(
235        resultArray[0],
236        resultArray[1],
237        resultArray[2],
238        new Rotation3d(
239            new Quaternion(resultArray[3], resultArray[4], resultArray[5], resultArray[6])));
240  }
241
242  /**
243   * Returns a Twist3d that maps this pose to the end pose. If c is the output of {@code a.Log(b)},
244   * then {@code a.Exp(c)} would yield b.
245   *
246   * @param end The end pose for the transformation.
247   * @return The twist that maps this to end.
248   */
249  public Twist3d log(Pose3d end) {
250    var thisQuaternion = this.getRotation().getQuaternion();
251    var endQuaternion = end.getRotation().getQuaternion();
252    double[] resultArray =
253        WPIMathJNI.logPose3d(
254            this.getX(),
255            this.getY(),
256            this.getZ(),
257            thisQuaternion.getW(),
258            thisQuaternion.getX(),
259            thisQuaternion.getY(),
260            thisQuaternion.getZ(),
261            end.getX(),
262            end.getY(),
263            end.getZ(),
264            endQuaternion.getW(),
265            endQuaternion.getX(),
266            endQuaternion.getY(),
267            endQuaternion.getZ());
268    return new Twist3d(
269        resultArray[0],
270        resultArray[1],
271        resultArray[2],
272        resultArray[3],
273        resultArray[4],
274        resultArray[5]);
275  }
276
277  /**
278   * Returns a Pose2d representing this Pose3d projected into the X-Y plane.
279   *
280   * @return A Pose2d representing this Pose3d projected into the X-Y plane.
281   */
282  public Pose2d toPose2d() {
283    return new Pose2d(m_translation.toTranslation2d(), m_rotation.toRotation2d());
284  }
285
286  @Override
287  public String toString() {
288    return String.format("Pose3d(%s, %s)", m_translation, m_rotation);
289  }
290
291  /**
292   * Checks equality between this Pose3d and another object.
293   *
294   * @param obj The other object.
295   * @return Whether the two objects are equal or not.
296   */
297  @Override
298  public boolean equals(Object obj) {
299    if (obj instanceof Pose3d) {
300      return ((Pose3d) obj).m_translation.equals(m_translation)
301          && ((Pose3d) obj).m_rotation.equals(m_rotation);
302    }
303    return false;
304  }
305
306  @Override
307  public int hashCode() {
308    return Objects.hash(m_translation, m_rotation);
309  }
310
311  @Override
312  public Pose3d interpolate(Pose3d endValue, double t) {
313    if (t < 0) {
314      return this;
315    } else if (t >= 1) {
316      return endValue;
317    } else {
318      var twist = this.log(endValue);
319      var scaledTwist =
320          new Twist3d(
321              twist.dx * t, twist.dy * t, twist.dz * t, twist.rx * t, twist.ry * t, twist.rz * t);
322      return this.exp(scaledTwist);
323    }
324  }
325
326  /** Pose3d protobuf for serialization. */
327  public static final Pose3dProto proto = new Pose3dProto();
328
329  /** Pose3d struct for serialization. */
330  public static final Pose3dStruct struct = new Pose3dStruct();
331}