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.Pose3dProto; 017import edu.wpi.first.math.geometry.struct.Pose3dStruct; 018import edu.wpi.first.math.interpolation.Interpolatable; 019import edu.wpi.first.math.numbers.N4; 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 3D pose containing translational and rotational elements. */ 029@JsonIgnoreProperties(ignoreUnknown = true) 030@JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE) 031public class Pose3d implements Interpolatable<Pose3d>, ProtobufSerializable, StructSerializable { 032 /** 033 * A preallocated Pose3d representing the origin. 034 * 035 * <p>This exists to avoid allocations for common poses. 036 */ 037 public static final Pose3d kZero = new Pose3d(); 038 039 private final Translation3d m_translation; 040 private final Rotation3d m_rotation; 041 042 /** Constructs a pose at the origin facing toward the positive X axis. */ 043 public Pose3d() { 044 m_translation = Translation3d.kZero; 045 m_rotation = Rotation3d.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 Pose3d( 056 @JsonProperty(required = true, value = "translation") Translation3d translation, 057 @JsonProperty(required = true, value = "rotation") Rotation3d rotation) { 058 m_translation = translation; 059 m_rotation = rotation; 060 } 061 062 /** 063 * Constructs a pose with x, y, and z translations instead of a separate Translation3d. 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 z The z component of the translational component of the pose. 068 * @param rotation The rotational component of the pose. 069 */ 070 public Pose3d(double x, double y, double z, Rotation3d rotation) { 071 m_translation = new Translation3d(x, y, z); 072 m_rotation = rotation; 073 } 074 075 /** 076 * Constructs a pose with x, y, and z translations instead of a separate Translation3d. The X, Y, 077 * and Z translations will be converted to and tracked as meters. 078 * 079 * @param x The x component of the translational component of the pose. 080 * @param y The y component of the translational component of the pose. 081 * @param z The z component of the translational component of the pose. 082 * @param rotation The rotational component of the pose. 083 */ 084 public Pose3d(Distance x, Distance y, Distance z, Rotation3d rotation) { 085 this(x.in(Meters), y.in(Meters), z.in(Meters), rotation); 086 } 087 088 /** 089 * Constructs a pose with the specified affine transformation matrix. 090 * 091 * @param matrix The affine transformation matrix. 092 * @throws IllegalArgumentException if the affine transformation matrix is invalid. 093 */ 094 public Pose3d(Matrix<N4, N4> matrix) { 095 m_translation = new Translation3d(matrix.get(0, 3), matrix.get(1, 3), matrix.get(2, 3)); 096 m_rotation = new Rotation3d(matrix.block(3, 3, 0, 0)); 097 if (matrix.get(3, 0) != 0.0 098 || matrix.get(3, 1) != 0.0 099 || matrix.get(3, 2) != 0.0 100 || matrix.get(3, 3) != 1.0) { 101 throw new IllegalArgumentException("Affine transformation matrix is invalid"); 102 } 103 } 104 105 /** 106 * Constructs a 3D pose from a 2D pose in the X-Y plane. 107 * 108 * @param pose The 2D pose. 109 * @see Rotation3d#Rotation3d(Rotation2d) 110 * @see Translation3d#Translation3d(Translation2d) 111 */ 112 public Pose3d(Pose2d pose) { 113 m_translation = new Translation3d(pose.getX(), pose.getY(), 0.0); 114 m_rotation = new Rotation3d(0.0, 0.0, pose.getRotation().getRadians()); 115 } 116 117 /** 118 * Transforms the pose by the given transformation and returns the new transformed pose. The 119 * transform is applied relative to the pose's frame. Note that this differs from {@link 120 * Pose3d#rotateBy(Rotation3d)}, which is applied relative to the global frame and around the 121 * origin. 122 * 123 * @param other The transform to transform the pose by. 124 * @return The transformed pose. 125 */ 126 public Pose3d plus(Transform3d other) { 127 return transformBy(other); 128 } 129 130 /** 131 * Returns the Transform3d that maps the one pose to another. 132 * 133 * @param other The initial pose of the transformation. 134 * @return The transform that maps the other pose to the current pose. 135 */ 136 public Transform3d minus(Pose3d other) { 137 final var pose = this.relativeTo(other); 138 return new Transform3d(pose.getTranslation(), pose.getRotation()); 139 } 140 141 /** 142 * Returns the translation component of the transformation. 143 * 144 * @return The translational component of the pose. 145 */ 146 @JsonProperty 147 public Translation3d getTranslation() { 148 return m_translation; 149 } 150 151 /** 152 * Returns the X component of the pose's translation. 153 * 154 * @return The x component of the pose's translation. 155 */ 156 public double getX() { 157 return m_translation.getX(); 158 } 159 160 /** 161 * Returns the Y component of the pose's translation. 162 * 163 * @return The y component of the pose's translation. 164 */ 165 public double getY() { 166 return m_translation.getY(); 167 } 168 169 /** 170 * Returns the Z component of the pose's translation. 171 * 172 * @return The z component of the pose's translation. 173 */ 174 public double getZ() { 175 return m_translation.getZ(); 176 } 177 178 /** 179 * Returns the X component of the pose's translation in a measure. 180 * 181 * @return The x component of the pose's translation in a measure. 182 */ 183 public Distance getMeasureX() { 184 return m_translation.getMeasureX(); 185 } 186 187 /** 188 * Returns the Y component of the pose's translation in a measure. 189 * 190 * @return The y component of the pose's translation in a measure. 191 */ 192 public Distance getMeasureY() { 193 return m_translation.getMeasureY(); 194 } 195 196 /** 197 * Returns the Z component of the pose's translation in a measure. 198 * 199 * @return The z component of the pose's translation in a measure. 200 */ 201 public Distance getMeasureZ() { 202 return m_translation.getMeasureZ(); 203 } 204 205 /** 206 * Returns the rotational component of the transformation. 207 * 208 * @return The rotational component of the pose. 209 */ 210 @JsonProperty 211 public Rotation3d getRotation() { 212 return m_rotation; 213 } 214 215 /** 216 * Multiplies the current pose by a scalar. 217 * 218 * @param scalar The scalar. 219 * @return The new scaled Pose3d. 220 */ 221 public Pose3d times(double scalar) { 222 return new Pose3d(m_translation.times(scalar), m_rotation.times(scalar)); 223 } 224 225 /** 226 * Divides the current pose by a scalar. 227 * 228 * @param scalar The scalar. 229 * @return The new scaled Pose3d. 230 */ 231 public Pose3d div(double scalar) { 232 return times(1.0 / scalar); 233 } 234 235 /** 236 * Rotates the pose around the origin and returns the new pose. 237 * 238 * @param other The rotation to transform the pose by, which is applied extrinsically (from the 239 * global frame). 240 * @return The rotated pose. 241 */ 242 public Pose3d rotateBy(Rotation3d other) { 243 return new Pose3d(m_translation.rotateBy(other), m_rotation.rotateBy(other)); 244 } 245 246 /** 247 * Transforms the pose by the given transformation and returns the new transformed pose. The 248 * transform is applied relative to the pose's frame. Note that this differs from {@link 249 * Pose3d#rotateBy(Rotation3d)}, which is applied relative to the global frame and around the 250 * origin. 251 * 252 * @param other The transform to transform the pose by. 253 * @return The transformed pose. 254 */ 255 public Pose3d transformBy(Transform3d other) { 256 return new Pose3d( 257 m_translation.plus(other.getTranslation().rotateBy(m_rotation)), 258 other.getRotation().plus(m_rotation)); 259 } 260 261 /** 262 * Returns the current pose relative to the given pose. 263 * 264 * <p>This function can often be used for trajectory tracking or pose stabilization algorithms to 265 * get the error between the reference and the current pose. 266 * 267 * @param other The pose that is the origin of the new coordinate frame that the current pose will 268 * be converted into. 269 * @return The current pose relative to the new origin pose. 270 */ 271 public Pose3d relativeTo(Pose3d other) { 272 var transform = new Transform3d(other, this); 273 return new Pose3d(transform.getTranslation(), transform.getRotation()); 274 } 275 276 /** 277 * Rotates the current pose around a point in 3D space. 278 * 279 * @param point The point in 3D space to rotate around. 280 * @param rot The rotation to rotate the pose by. 281 * @return The new rotated pose. 282 */ 283 public Pose3d rotateAround(Translation3d point, Rotation3d rot) { 284 return new Pose3d(m_translation.rotateAround(point, rot), m_rotation.rotateBy(rot)); 285 } 286 287 /** 288 * Returns an affine transformation matrix representation of this pose. 289 * 290 * @return An affine transformation matrix representation of this pose. 291 */ 292 public Matrix<N4, N4> toMatrix() { 293 var vec = m_translation.toVector(); 294 var mat = m_rotation.toMatrix(); 295 return MatBuilder.fill( 296 Nat.N4(), 297 Nat.N4(), 298 mat.get(0, 0), 299 mat.get(0, 1), 300 mat.get(0, 2), 301 vec.get(0), 302 mat.get(1, 0), 303 mat.get(1, 1), 304 mat.get(1, 2), 305 vec.get(1), 306 mat.get(2, 0), 307 mat.get(2, 1), 308 mat.get(2, 2), 309 vec.get(2), 310 0.0, 311 0.0, 312 0.0, 313 1.0); 314 } 315 316 /** 317 * Returns a Pose2d representing this Pose3d projected into the X-Y plane. 318 * 319 * @return A Pose2d representing this Pose3d projected into the X-Y plane. 320 */ 321 public Pose2d toPose2d() { 322 return new Pose2d(m_translation.toTranslation2d(), m_rotation.toRotation2d()); 323 } 324 325 /** 326 * Returns the nearest Pose3d from a collection of poses. If two or more poses in the collection 327 * have the same distance from this pose, return the one with the closest rotation component. 328 * 329 * @param poses The collection of poses to find the nearest. 330 * @return The nearest Pose3d from the collection. 331 */ 332 public Pose3d nearest(Collection<Pose3d> poses) { 333 return Collections.min( 334 poses, 335 Comparator.comparing( 336 (Pose3d other) -> this.getTranslation().getDistance(other.getTranslation())) 337 .thenComparing( 338 (Pose3d other) -> this.getRotation().minus(other.getRotation()).getAngle())); 339 } 340 341 @Override 342 public String toString() { 343 return String.format("Pose3d(%s, %s)", m_translation, m_rotation); 344 } 345 346 /** 347 * Checks equality between this Pose3d and another object. 348 * 349 * @param obj The other object. 350 * @return Whether the two objects are equal or not. 351 */ 352 @Override 353 public boolean equals(Object obj) { 354 return obj instanceof Pose3d pose 355 && m_translation.equals(pose.m_translation) 356 && m_rotation.equals(pose.m_rotation); 357 } 358 359 @Override 360 public int hashCode() { 361 return Objects.hash(m_translation, m_rotation); 362 } 363 364 @Override 365 public Pose3d interpolate(Pose3d endValue, double t) { 366 if (t < 0) { 367 return this; 368 } else if (t >= 1) { 369 return endValue; 370 } else { 371 var twist = endValue.minus(this).log(); 372 var scaledTwist = 373 new Twist3d( 374 twist.dx * t, twist.dy * t, twist.dz * t, twist.rx * t, twist.ry * t, twist.rz * t); 375 return this.plus(scaledTwist.exp()); 376 } 377 } 378 379 /** Pose3d protobuf for serialization. */ 380 public static final Pose3dProto proto = new Pose3dProto(); 381 382 /** Pose3d struct for serialization. */ 383 public static final Pose3dStruct struct = new Pose3dStruct(); 384}