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.jni.Pose3dJNI; 020import edu.wpi.first.math.numbers.N4; 021import edu.wpi.first.units.measure.Distance; 022import edu.wpi.first.util.protobuf.ProtobufSerializable; 023import edu.wpi.first.util.struct.StructSerializable; 024import java.util.Collection; 025import java.util.Collections; 026import java.util.Comparator; 027import java.util.Objects; 028 029/** Represents a 3D pose containing translational and rotational elements. */ 030@JsonIgnoreProperties(ignoreUnknown = true) 031@JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE) 032public class Pose3d implements Interpolatable<Pose3d>, ProtobufSerializable, StructSerializable { 033 /** 034 * A preallocated Pose3d representing the origin. 035 * 036 * <p>This exists to avoid allocations for common poses. 037 */ 038 public static final Pose3d kZero = new Pose3d(); 039 040 private final Translation3d m_translation; 041 private final Rotation3d m_rotation; 042 043 /** Constructs a pose at the origin facing toward the positive X axis. */ 044 public Pose3d() { 045 m_translation = Translation3d.kZero; 046 m_rotation = Rotation3d.kZero; 047 } 048 049 /** 050 * Constructs a pose with the specified translation and rotation. 051 * 052 * @param translation The translational component of the pose. 053 * @param rotation The rotational component of the pose. 054 */ 055 @JsonCreator 056 public Pose3d( 057 @JsonProperty(required = true, value = "translation") Translation3d translation, 058 @JsonProperty(required = true, value = "rotation") Rotation3d rotation) { 059 m_translation = translation; 060 m_rotation = rotation; 061 } 062 063 /** 064 * Constructs a pose with x, y, and z translations instead of a separate Translation3d. 065 * 066 * @param x The x component of the translational component of the pose. 067 * @param y The y component of the translational component of the pose. 068 * @param z The z component of the translational component of the pose. 069 * @param rotation The rotational component of the pose. 070 */ 071 public Pose3d(double x, double y, double z, Rotation3d rotation) { 072 m_translation = new Translation3d(x, y, z); 073 m_rotation = rotation; 074 } 075 076 /** 077 * Constructs a pose with x, y, and z translations instead of a separate Translation3d. The X, Y, 078 * and Z translations will be converted to and tracked as meters. 079 * 080 * @param x The x component of the translational component of the pose. 081 * @param y The y component of the translational component of the pose. 082 * @param z The z component of the translational component of the pose. 083 * @param rotation The rotational component of the pose. 084 */ 085 public Pose3d(Distance x, Distance y, Distance z, Rotation3d rotation) { 086 this(x.in(Meters), y.in(Meters), z.in(Meters), rotation); 087 } 088 089 /** 090 * Constructs a pose with the specified affine transformation matrix. 091 * 092 * @param matrix The affine transformation matrix. 093 * @throws IllegalArgumentException if the affine transformation matrix is invalid. 094 */ 095 public Pose3d(Matrix<N4, N4> matrix) { 096 m_translation = new Translation3d(matrix.get(0, 3), matrix.get(1, 3), matrix.get(2, 3)); 097 m_rotation = new Rotation3d(matrix.block(3, 3, 0, 0)); 098 if (matrix.get(3, 0) != 0.0 099 || matrix.get(3, 1) != 0.0 100 || matrix.get(3, 2) != 0.0 101 || matrix.get(3, 3) != 1.0) { 102 throw new IllegalArgumentException("Affine transformation matrix is invalid"); 103 } 104 } 105 106 /** 107 * Constructs a 3D pose from a 2D pose in the X-Y plane. 108 * 109 * @param pose The 2D pose. 110 * @see Rotation3d#Rotation3d(Rotation2d) 111 * @see Translation3d#Translation3d(Translation2d) 112 */ 113 public Pose3d(Pose2d pose) { 114 m_translation = new Translation3d(pose.getX(), pose.getY(), 0.0); 115 m_rotation = new Rotation3d(0.0, 0.0, pose.getRotation().getRadians()); 116 } 117 118 /** 119 * Transforms the pose by the given transformation and returns the new transformed pose. The 120 * transform is applied relative to the pose's frame. Note that this differs from {@link 121 * Pose3d#rotateBy(Rotation3d)}, which is applied relative to the global frame and around the 122 * origin. 123 * 124 * @param other The transform to transform the pose by. 125 * @return The transformed pose. 126 */ 127 public Pose3d plus(Transform3d other) { 128 return transformBy(other); 129 } 130 131 /** 132 * Returns the Transform3d that maps the one pose to another. 133 * 134 * @param other The initial pose of the transformation. 135 * @return The transform that maps the other pose to the current pose. 136 */ 137 public Transform3d minus(Pose3d other) { 138 final var pose = this.relativeTo(other); 139 return new Transform3d(pose.getTranslation(), pose.getRotation()); 140 } 141 142 /** 143 * Returns the translation component of the transformation. 144 * 145 * @return The translational component of the pose. 146 */ 147 @JsonProperty 148 public Translation3d getTranslation() { 149 return m_translation; 150 } 151 152 /** 153 * Returns the X component of the pose's translation. 154 * 155 * @return The x component of the pose's translation. 156 */ 157 public double getX() { 158 return m_translation.getX(); 159 } 160 161 /** 162 * Returns the Y component of the pose's translation. 163 * 164 * @return The y component of the pose's translation. 165 */ 166 public double getY() { 167 return m_translation.getY(); 168 } 169 170 /** 171 * Returns the Z component of the pose's translation. 172 * 173 * @return The z component of the pose's translation. 174 */ 175 public double getZ() { 176 return m_translation.getZ(); 177 } 178 179 /** 180 * Returns the X component of the pose's translation in a measure. 181 * 182 * @return The x component of the pose's translation in a measure. 183 */ 184 public Distance getMeasureX() { 185 return m_translation.getMeasureX(); 186 } 187 188 /** 189 * Returns the Y component of the pose's translation in a measure. 190 * 191 * @return The y component of the pose's translation in a measure. 192 */ 193 public Distance getMeasureY() { 194 return m_translation.getMeasureY(); 195 } 196 197 /** 198 * Returns the Z component of the pose's translation in a measure. 199 * 200 * @return The z component of the pose's translation in a measure. 201 */ 202 public Distance getMeasureZ() { 203 return m_translation.getMeasureZ(); 204 } 205 206 /** 207 * Returns the rotational component of the transformation. 208 * 209 * @return The rotational component of the pose. 210 */ 211 @JsonProperty 212 public Rotation3d getRotation() { 213 return m_rotation; 214 } 215 216 /** 217 * Multiplies the current pose by a scalar. 218 * 219 * @param scalar The scalar. 220 * @return The new scaled Pose3d. 221 */ 222 public Pose3d times(double scalar) { 223 return new Pose3d(m_translation.times(scalar), m_rotation.times(scalar)); 224 } 225 226 /** 227 * Divides the current pose by a scalar. 228 * 229 * @param scalar The scalar. 230 * @return The new scaled Pose3d. 231 */ 232 public Pose3d div(double scalar) { 233 return times(1.0 / scalar); 234 } 235 236 /** 237 * Rotates the pose around the origin and returns the new pose. 238 * 239 * @param other The rotation to transform the pose by, which is applied extrinsically (from the 240 * global frame). 241 * @return The rotated pose. 242 */ 243 public Pose3d rotateBy(Rotation3d other) { 244 return new Pose3d(m_translation.rotateBy(other), m_rotation.rotateBy(other)); 245 } 246 247 /** 248 * Transforms the pose by the given transformation and returns the new transformed pose. The 249 * transform is applied relative to the pose's frame. Note that this differs from {@link 250 * Pose3d#rotateBy(Rotation3d)}, which is applied relative to the global frame and around the 251 * origin. 252 * 253 * @param other The transform to transform the pose by. 254 * @return The transformed pose. 255 */ 256 public Pose3d transformBy(Transform3d other) { 257 // Rotating the transform's rotation by the pose's rotation extrinsically is equivalent to 258 // rotating the pose's rotation by the transform's rotation intrinsically. (We define transforms 259 // as being applied intrinsically.) 260 return new Pose3d( 261 m_translation.plus(other.getTranslation().rotateBy(m_rotation)), 262 other.getRotation().rotateBy(m_rotation)); 263 } 264 265 /** 266 * Returns the current pose relative to the given pose. 267 * 268 * <p>This function can often be used for trajectory tracking or pose stabilization algorithms to 269 * get the error between the reference and the current pose. 270 * 271 * @param other The pose that is the origin of the new coordinate frame that the current pose will 272 * be converted into. 273 * @return The current pose relative to the new origin pose. 274 */ 275 public Pose3d relativeTo(Pose3d other) { 276 var transform = new Transform3d(other, this); 277 return new Pose3d(transform.getTranslation(), transform.getRotation()); 278 } 279 280 /** 281 * Rotates the current pose around a point in 3D space. 282 * 283 * @param point The point in 3D space to rotate around. 284 * @param rot The rotation to rotate the pose by. 285 * @return The new rotated pose. 286 */ 287 public Pose3d rotateAround(Translation3d point, Rotation3d rot) { 288 return new Pose3d(m_translation.rotateAround(point, rot), m_rotation.rotateBy(rot)); 289 } 290 291 /** 292 * Obtain a new Pose3d from a (constant curvature) velocity. 293 * 294 * <p>The twist is a change in pose in the robot's coordinate frame since the previous pose 295 * update. When the user runs exp() on the previous known field-relative pose with the argument 296 * being the twist, the user will receive the new field-relative pose. 297 * 298 * <p>"Exp" represents the pose exponential, which is solving a differential equation moving the 299 * pose forward in time. 300 * 301 * @param twist The change in pose in the robot's coordinate frame since the previous pose update. 302 * For example, if a non-holonomic robot moves forward 0.01 meters and changes angle by 0.5 303 * degrees since the previous pose update, the twist would be Twist3d(0.01, 0.0, 0.0, new new 304 * Rotation3d(0.0, 0.0, Units.degreesToRadians(0.5))). 305 * @return The new pose of the robot. 306 */ 307 public Pose3d exp(Twist3d twist) { 308 var quaternion = this.getRotation().getQuaternion(); 309 double[] resultArray = 310 Pose3dJNI.exp( 311 this.getX(), 312 this.getY(), 313 this.getZ(), 314 quaternion.getW(), 315 quaternion.getX(), 316 quaternion.getY(), 317 quaternion.getZ(), 318 twist.dx, 319 twist.dy, 320 twist.dz, 321 twist.rx, 322 twist.ry, 323 twist.rz); 324 return new Pose3d( 325 resultArray[0], 326 resultArray[1], 327 resultArray[2], 328 new Rotation3d( 329 new Quaternion(resultArray[3], resultArray[4], resultArray[5], resultArray[6]))); 330 } 331 332 /** 333 * Returns a Twist3d that maps this pose to the end pose. If c is the output of {@code a.Log(b)}, 334 * then {@code a.Exp(c)} would yield b. 335 * 336 * @param end The end pose for the transformation. 337 * @return The twist that maps this to end. 338 */ 339 public Twist3d log(Pose3d end) { 340 var thisQuaternion = this.getRotation().getQuaternion(); 341 var endQuaternion = end.getRotation().getQuaternion(); 342 double[] resultArray = 343 Pose3dJNI.log( 344 this.getX(), 345 this.getY(), 346 this.getZ(), 347 thisQuaternion.getW(), 348 thisQuaternion.getX(), 349 thisQuaternion.getY(), 350 thisQuaternion.getZ(), 351 end.getX(), 352 end.getY(), 353 end.getZ(), 354 endQuaternion.getW(), 355 endQuaternion.getX(), 356 endQuaternion.getY(), 357 endQuaternion.getZ()); 358 return new Twist3d( 359 resultArray[0], 360 resultArray[1], 361 resultArray[2], 362 resultArray[3], 363 resultArray[4], 364 resultArray[5]); 365 } 366 367 /** 368 * Returns an affine transformation matrix representation of this pose. 369 * 370 * @return An affine transformation matrix representation of this pose. 371 */ 372 public Matrix<N4, N4> toMatrix() { 373 var vec = m_translation.toVector(); 374 var mat = m_rotation.toMatrix(); 375 return MatBuilder.fill( 376 Nat.N4(), 377 Nat.N4(), 378 mat.get(0, 0), 379 mat.get(0, 1), 380 mat.get(0, 2), 381 vec.get(0), 382 mat.get(1, 0), 383 mat.get(1, 1), 384 mat.get(1, 2), 385 vec.get(1), 386 mat.get(2, 0), 387 mat.get(2, 1), 388 mat.get(2, 2), 389 vec.get(2), 390 0.0, 391 0.0, 392 0.0, 393 1.0); 394 } 395 396 /** 397 * Returns a Pose2d representing this Pose3d projected into the X-Y plane. 398 * 399 * @return A Pose2d representing this Pose3d projected into the X-Y plane. 400 */ 401 public Pose2d toPose2d() { 402 return new Pose2d(m_translation.toTranslation2d(), m_rotation.toRotation2d()); 403 } 404 405 /** 406 * Returns the nearest Pose3d from a collection of poses. If two or more poses in the collection 407 * have the same distance from this pose, return the one with the closest rotation component. 408 * 409 * @param poses The collection of poses to find the nearest. 410 * @return The nearest Pose3d from the collection. 411 */ 412 public Pose3d nearest(Collection<Pose3d> poses) { 413 return Collections.min( 414 poses, 415 Comparator.comparing( 416 (Pose3d other) -> this.getTranslation().getDistance(other.getTranslation())) 417 .thenComparing( 418 (Pose3d other) -> this.getRotation().minus(other.getRotation()).getAngle())); 419 } 420 421 @Override 422 public String toString() { 423 return String.format("Pose3d(%s, %s)", m_translation, m_rotation); 424 } 425 426 /** 427 * Checks equality between this Pose3d and another object. 428 * 429 * @param obj The other object. 430 * @return Whether the two objects are equal or not. 431 */ 432 @Override 433 public boolean equals(Object obj) { 434 return obj instanceof Pose3d pose 435 && m_translation.equals(pose.m_translation) 436 && m_rotation.equals(pose.m_rotation); 437 } 438 439 @Override 440 public int hashCode() { 441 return Objects.hash(m_translation, m_rotation); 442 } 443 444 @Override 445 public Pose3d interpolate(Pose3d endValue, double t) { 446 if (t < 0) { 447 return this; 448 } else if (t >= 1) { 449 return endValue; 450 } else { 451 var twist = this.log(endValue); 452 var scaledTwist = 453 new Twist3d( 454 twist.dx * t, twist.dy * t, twist.dz * t, twist.rx * t, twist.ry * t, twist.rz * t); 455 return this.exp(scaledTwist); 456 } 457 } 458 459 /** Pose3d protobuf for serialization. */ 460 public static final Pose3dProto proto = new Pose3dProto(); 461 462 /** Pose3d struct for serialization. */ 463 public static final Pose3dStruct struct = new Pose3dStruct(); 464}