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 org.wpilib.math.geometry;
006
007import static org.wpilib.units.Units.Meters;
008
009import java.util.Objects;
010import org.wpilib.math.geometry.proto.Transform3dProto;
011import org.wpilib.math.geometry.struct.Transform3dStruct;
012import org.wpilib.math.jni.Transform3dJNI;
013import org.wpilib.math.linalg.MatBuilder;
014import org.wpilib.math.linalg.Matrix;
015import org.wpilib.math.numbers.N4;
016import org.wpilib.math.util.Nat;
017import org.wpilib.units.measure.Distance;
018import org.wpilib.util.protobuf.ProtobufSerializable;
019import org.wpilib.util.struct.StructSerializable;
020
021/**
022 * Represents a transformation for a Pose3d in the pose's frame. Translation is applied before
023 * rotation. (The translation is applied in the pose's original frame, not the transformed frame.)
024 */
025public class Transform3d implements ProtobufSerializable, StructSerializable {
026  /**
027   * A preallocated Transform3d representing no transformation.
028   *
029   * <p>This exists to avoid allocations for common transformations.
030   */
031  public static final Transform3d kZero = new Transform3d();
032
033  private final Translation3d m_translation;
034  private final Rotation3d m_rotation;
035
036  /**
037   * Constructs the transform that maps the initial pose to the final pose.
038   *
039   * @param initial The initial pose for the transformation.
040   * @param last The final pose for the transformation.
041   */
042  public Transform3d(Pose3d initial, Pose3d last) {
043    // To transform the global translation delta to be relative to the initial
044    // pose, rotate by the inverse of the initial pose's orientation.
045    m_translation =
046        last.getTranslation()
047            .minus(initial.getTranslation())
048            .rotateBy(initial.getRotation().inverse());
049
050    m_rotation = last.getRotation().relativeTo(initial.getRotation());
051  }
052
053  /**
054   * Constructs a transform with the given translation and rotation components.
055   *
056   * @param translation Translational component of the transform.
057   * @param rotation Rotational component of the transform.
058   */
059  public Transform3d(Translation3d translation, Rotation3d rotation) {
060    m_translation = translation;
061    m_rotation = rotation;
062  }
063
064  /**
065   * Constructs a transform with x, y, and z translations instead of a separate Translation3d.
066   *
067   * @param x The x component of the translational component of the transform.
068   * @param y The y component of the translational component of the transform.
069   * @param z The z component of the translational component of the transform.
070   * @param rotation The rotational component of the transform.
071   */
072  public Transform3d(double x, double y, double z, Rotation3d rotation) {
073    m_translation = new Translation3d(x, y, z);
074    m_rotation = rotation;
075  }
076
077  /**
078   * Constructs a transform with x, y, and z translations instead of a separate Translation3d. The
079   * X, Y, and Z translations will be converted to and tracked as meters.
080   *
081   * @param x The x component of the translational component of the transform.
082   * @param y The y component of the translational component of the transform.
083   * @param z The z component of the translational component of the transform.
084   * @param rotation The rotational component of the transform.
085   */
086  public Transform3d(Distance x, Distance y, Distance z, Rotation3d rotation) {
087    this(x.in(Meters), y.in(Meters), z.in(Meters), rotation);
088  }
089
090  /**
091   * Constructs a transform with the specified affine transformation matrix.
092   *
093   * @param matrix The affine transformation matrix.
094   * @throws IllegalArgumentException if the affine transformation matrix is invalid.
095   */
096  public Transform3d(Matrix<N4, N4> matrix) {
097    m_translation = new Translation3d(matrix.get(0, 3), matrix.get(1, 3), matrix.get(2, 3));
098    m_rotation = new Rotation3d(matrix.block(3, 3, 0, 0));
099    if (matrix.get(3, 0) != 0.0
100        || matrix.get(3, 1) != 0.0
101        || matrix.get(3, 2) != 0.0
102        || matrix.get(3, 3) != 1.0) {
103      throw new IllegalArgumentException("Affine transformation matrix is invalid");
104    }
105  }
106
107  /** Constructs the identity transform -- maps an initial pose to itself. */
108  public Transform3d() {
109    m_translation = Translation3d.kZero;
110    m_rotation = Rotation3d.kZero;
111  }
112
113  /**
114   * Constructs a 3D transform from a 2D transform in the X-Y plane.
115   *
116   * @param transform The 2D transform.
117   * @see Rotation3d#Rotation3d(Rotation2d)
118   * @see Translation3d#Translation3d(Translation2d)
119   */
120  public Transform3d(Transform2d transform) {
121    m_translation = new Translation3d(transform.getTranslation());
122    m_rotation = new Rotation3d(transform.getRotation());
123  }
124
125  /**
126   * Multiplies the transform by the scalar.
127   *
128   * @param scalar The scalar.
129   * @return The scaled Transform3d.
130   */
131  public Transform3d times(double scalar) {
132    return new Transform3d(m_translation.times(scalar), m_rotation.times(scalar));
133  }
134
135  /**
136   * Divides the transform by the scalar.
137   *
138   * @param scalar The scalar.
139   * @return The scaled Transform3d.
140   */
141  public Transform3d div(double scalar) {
142    return times(1.0 / scalar);
143  }
144
145  /**
146   * Composes two transformations. The second transform is applied relative to the orientation of
147   * the first.
148   *
149   * @param other The transform to compose with this one.
150   * @return The composition of the two transformations.
151   */
152  public Transform3d plus(Transform3d other) {
153    return new Transform3d(Pose3d.kZero, Pose3d.kZero.transformBy(this).transformBy(other));
154  }
155
156  /**
157   * Returns the translation component of the transformation.
158   *
159   * @return The translational component of the transform.
160   */
161  public Translation3d getTranslation() {
162    return m_translation;
163  }
164
165  /**
166   * Returns the X component of the transformation's translation.
167   *
168   * @return The x component of the transformation's translation.
169   */
170  public double getX() {
171    return m_translation.getX();
172  }
173
174  /**
175   * Returns the Y component of the transformation's translation.
176   *
177   * @return The y component of the transformation's translation.
178   */
179  public double getY() {
180    return m_translation.getY();
181  }
182
183  /**
184   * Returns the Z component of the transformation's translation.
185   *
186   * @return The z component of the transformation's translation.
187   */
188  public double getZ() {
189    return m_translation.getZ();
190  }
191
192  /**
193   * Returns the X component of the transformation's translation in a measure.
194   *
195   * @return The x component of the transformation's translation in a measure.
196   */
197  public Distance getMeasureX() {
198    return m_translation.getMeasureX();
199  }
200
201  /**
202   * Returns the Y component of the transformation's translation in a measure.
203   *
204   * @return The y component of the transformation's translation in a measure.
205   */
206  public Distance getMeasureY() {
207    return m_translation.getMeasureY();
208  }
209
210  /**
211   * Returns the Z component of the transformation's translation in a measure.
212   *
213   * @return The z component of the transformation's translation in a measure.
214   */
215  public Distance getMeasureZ() {
216    return m_translation.getMeasureZ();
217  }
218
219  /**
220   * Returns an affine transformation matrix representation of this transformation.
221   *
222   * @return An affine transformation matrix representation of this transformation.
223   */
224  public Matrix<N4, N4> toMatrix() {
225    var vec = m_translation.toVector();
226    var mat = m_rotation.toMatrix();
227    return MatBuilder.fill(
228        Nat.N4(),
229        Nat.N4(),
230        mat.get(0, 0),
231        mat.get(0, 1),
232        mat.get(0, 2),
233        vec.get(0),
234        mat.get(1, 0),
235        mat.get(1, 1),
236        mat.get(1, 2),
237        vec.get(1),
238        mat.get(2, 0),
239        mat.get(2, 1),
240        mat.get(2, 2),
241        vec.get(2),
242        0.0,
243        0.0,
244        0.0,
245        1.0);
246  }
247
248  /**
249   * Returns the rotational component of the transformation.
250   *
251   * @return Reference to the rotational component of the transform.
252   */
253  public Rotation3d getRotation() {
254    return m_rotation;
255  }
256
257  /**
258   * Returns a Twist3d of the current transform (pose delta). If b is the output of {@code a.log()},
259   * then {@code b.exp()} would yield a.
260   *
261   * @return The twist that maps the current transform.
262   */
263  public Twist3d log() {
264    var thisQuaternion = m_rotation.getQuaternion();
265    double[] resultArray =
266        Transform3dJNI.log(
267            this.getX(),
268            this.getY(),
269            this.getZ(),
270            thisQuaternion.getW(),
271            thisQuaternion.getX(),
272            thisQuaternion.getY(),
273            thisQuaternion.getZ());
274    return new Twist3d(
275        resultArray[0],
276        resultArray[1],
277        resultArray[2],
278        resultArray[3],
279        resultArray[4],
280        resultArray[5]);
281  }
282
283  /**
284   * Invert the transformation. This is useful for undoing a transformation.
285   *
286   * @return The inverted transformation.
287   */
288  public Transform3d inverse() {
289    // We are rotating the difference between the translations
290    // using a clockwise rotation matrix. This transforms the global
291    // delta into a local delta (relative to the initial pose).
292    return new Transform3d(
293        getTranslation().unaryMinus().rotateBy(getRotation().inverse()), getRotation().inverse());
294  }
295
296  @Override
297  public String toString() {
298    return String.format("Transform3d(%s, %s)", m_translation, m_rotation);
299  }
300
301  /**
302   * Checks equality between this Transform3d and another object.
303   *
304   * @param obj The other object.
305   * @return Whether the two objects are equal or not.
306   */
307  @Override
308  public boolean equals(Object obj) {
309    return obj instanceof Transform3d other
310        && other.m_translation.equals(m_translation)
311        && other.m_rotation.equals(m_rotation);
312  }
313
314  @Override
315  public int hashCode() {
316    return Objects.hash(m_translation, m_rotation);
317  }
318
319  /** Transform3d protobuf for serialization. */
320  public static final Transform3dProto proto = new Transform3dProto();
321
322  /** Transform3d struct for serialization. */
323  public static final Transform3dStruct struct = new Transform3dStruct();
324}