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.MatBuilder; 012import edu.wpi.first.math.Matrix; 013import edu.wpi.first.math.Nat; 014import edu.wpi.first.math.VecBuilder; 015import edu.wpi.first.math.Vector; 016import edu.wpi.first.math.interpolation.Interpolatable; 017import edu.wpi.first.math.numbers.N1; 018import edu.wpi.first.math.numbers.N3; 019import java.util.Objects; 020 021/** Represents a 3D pose containing translational and rotational elements. */ 022@JsonIgnoreProperties(ignoreUnknown = true) 023@JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE) 024public class Pose3d implements Interpolatable<Pose3d> { 025 private final Translation3d m_translation; 026 private final Rotation3d m_rotation; 027 028 /** Constructs a pose at the origin facing toward the positive X axis. */ 029 public Pose3d() { 030 m_translation = new Translation3d(); 031 m_rotation = new Rotation3d(); 032 } 033 034 /** 035 * Constructs a pose with the specified translation and rotation. 036 * 037 * @param translation The translational component of the pose. 038 * @param rotation The rotational component of the pose. 039 */ 040 @JsonCreator 041 public Pose3d( 042 @JsonProperty(required = true, value = "translation") Translation3d translation, 043 @JsonProperty(required = true, value = "rotation") Rotation3d rotation) { 044 m_translation = translation; 045 m_rotation = rotation; 046 } 047 048 /** 049 * Constructs a pose with x, y, and z translations instead of a separate Translation3d. 050 * 051 * @param x The x component of the translational component of the pose. 052 * @param y The y component of the translational component of the pose. 053 * @param z The z component of the translational component of the pose. 054 * @param rotation The rotational component of the pose. 055 */ 056 public Pose3d(double x, double y, double z, Rotation3d rotation) { 057 m_translation = new Translation3d(x, y, z); 058 m_rotation = rotation; 059 } 060 061 /** 062 * Constructs a 3D pose from a 2D pose in the X-Y plane. 063 * 064 * @param pose The 2D pose. 065 */ 066 public Pose3d(Pose2d pose) { 067 m_translation = new Translation3d(pose.getX(), pose.getY(), 0.0); 068 m_rotation = new Rotation3d(0.0, 0.0, pose.getRotation().getRadians()); 069 } 070 071 /** 072 * Transforms the pose by the given transformation and returns the new transformed pose. 073 * 074 * @param other The transform to transform the pose by. 075 * @return The transformed pose. 076 */ 077 public Pose3d plus(Transform3d other) { 078 return transformBy(other); 079 } 080 081 /** 082 * Returns the Transform3d that maps the one pose to another. 083 * 084 * @param other The initial pose of the transformation. 085 * @return The transform that maps the other pose to the current pose. 086 */ 087 public Transform3d minus(Pose3d other) { 088 final var pose = this.relativeTo(other); 089 return new Transform3d(pose.getTranslation(), pose.getRotation()); 090 } 091 092 /** 093 * Returns the translation component of the transformation. 094 * 095 * @return The translational component of the pose. 096 */ 097 @JsonProperty 098 public Translation3d getTranslation() { 099 return m_translation; 100 } 101 102 /** 103 * Returns the X component of the pose's translation. 104 * 105 * @return The x component of the pose's translation. 106 */ 107 public double getX() { 108 return m_translation.getX(); 109 } 110 111 /** 112 * Returns the Y component of the pose's translation. 113 * 114 * @return The y component of the pose's translation. 115 */ 116 public double getY() { 117 return m_translation.getY(); 118 } 119 120 /** 121 * Returns the Z component of the pose's translation. 122 * 123 * @return The z component of the pose's translation. 124 */ 125 public double getZ() { 126 return m_translation.getZ(); 127 } 128 129 /** 130 * Returns the rotational component of the transformation. 131 * 132 * @return The rotational component of the pose. 133 */ 134 @JsonProperty 135 public Rotation3d getRotation() { 136 return m_rotation; 137 } 138 139 /** 140 * Multiplies the current pose by a scalar. 141 * 142 * @param scalar The scalar. 143 * @return The new scaled Pose3d. 144 */ 145 public Pose3d times(double scalar) { 146 return new Pose3d(m_translation.times(scalar), m_rotation.times(scalar)); 147 } 148 149 /** 150 * Divides the current pose by a scalar. 151 * 152 * @param scalar The scalar. 153 * @return The new scaled Pose3d. 154 */ 155 public Pose3d div(double scalar) { 156 return times(1.0 / scalar); 157 } 158 159 /** 160 * Transforms the pose by the given transformation and returns the new pose. See + operator for 161 * the matrix multiplication performed. 162 * 163 * @param other The transform to transform the pose by. 164 * @return The transformed pose. 165 */ 166 public Pose3d transformBy(Transform3d other) { 167 return new Pose3d( 168 m_translation.plus(other.getTranslation().rotateBy(m_rotation)), 169 other.getRotation().plus(m_rotation)); 170 } 171 172 /** 173 * Returns the current pose relative to the given pose. 174 * 175 * <p>This function can often be used for trajectory tracking or pose stabilization algorithms to 176 * get the error between the reference and the current pose. 177 * 178 * @param other The pose that is the origin of the new coordinate frame that the current pose will 179 * be converted into. 180 * @return The current pose relative to the new origin pose. 181 */ 182 public Pose3d relativeTo(Pose3d other) { 183 var transform = new Transform3d(other, this); 184 return new Pose3d(transform.getTranslation(), transform.getRotation()); 185 } 186 187 /** 188 * Obtain a new Pose3d from a (constant curvature) velocity. 189 * 190 * <p>The twist is a change in pose in the robot's coordinate frame since the previous pose 191 * update. When the user runs exp() on the previous known field-relative pose with the argument 192 * being the twist, the user will receive the new field-relative pose. 193 * 194 * <p>"Exp" represents the pose exponential, which is solving a differential equation moving the 195 * pose forward in time. 196 * 197 * @param twist The change in pose in the robot's coordinate frame since the previous pose update. 198 * For example, if a non-holonomic robot moves forward 0.01 meters and changes angle by 0.5 199 * degrees since the previous pose update, the twist would be Twist3d(0.01, 0.0, 0.0, new new 200 * Rotation3d(0.0, 0.0, Units.degreesToRadians(0.5))). 201 * @return The new pose of the robot. 202 */ 203 public Pose3d exp(Twist3d twist) { 204 // Implementation from Section 3.2 of https://ethaneade.org/lie.pdf 205 final var u = VecBuilder.fill(twist.dx, twist.dy, twist.dz); 206 final var rvec = VecBuilder.fill(twist.rx, twist.ry, twist.rz); 207 final var omega = rotationVectorToMatrix(rvec); 208 final var omegaSq = omega.times(omega); 209 double theta = rvec.norm(); 210 double thetaSq = theta * theta; 211 212 double A; 213 double B; 214 double C; 215 if (Math.abs(theta) < 1E-7) { 216 // Taylor Expansions around θ = 0 217 // A = 1/1! - θ²/3! + θ⁴/5! 218 // B = 1/2! - θ²/4! + θ⁴/6! 219 // C = 1/3! - θ²/5! + θ⁴/7! 220 // sources: 221 // A: 222 // https://www.wolframalpha.com/input?i2d=true&i=series+expansion+of+Divide%5Bsin%5C%2840%29x%5C%2841%29%2Cx%5D+at+x%3D0 223 // B: 224 // https://www.wolframalpha.com/input?i2d=true&i=series+expansion+of+Divide%5B1-cos%5C%2840%29x%5C%2841%29%2CPower%5Bx%2C2%5D%5D+at+x%3D0 225 // C: 226 // https://www.wolframalpha.com/input?i2d=true&i=series+expansion+of+Divide%5B1-Divide%5Bsin%5C%2840%29x%5C%2841%29%2Cx%5D%2CPower%5Bx%2C2%5D%5D+at+x%3D0 227 A = 1 - thetaSq / 6 + thetaSq * thetaSq / 120; 228 B = 1 / 2.0 - thetaSq / 24 + thetaSq * thetaSq / 720; 229 C = 1 / 6.0 - thetaSq / 120 + thetaSq * thetaSq / 5040; 230 } else { 231 // A = sin(θ)/θ 232 // B = (1 - cos(θ)) / θ² 233 // C = (1 - A) / θ² 234 A = Math.sin(theta) / theta; 235 B = (1 - Math.cos(theta)) / thetaSq; 236 C = (1 - A) / thetaSq; 237 } 238 239 Matrix<N3, N3> R = Matrix.eye(Nat.N3()).plus(omega.times(A)).plus(omegaSq.times(B)); 240 Matrix<N3, N3> V = Matrix.eye(Nat.N3()).plus(omega.times(B)).plus(omegaSq.times(C)); 241 Matrix<N3, N1> translation_component = V.times(u); 242 final var transform = 243 new Transform3d( 244 new Translation3d( 245 translation_component.get(0, 0), 246 translation_component.get(1, 0), 247 translation_component.get(2, 0)), 248 new Rotation3d(R)); 249 250 return this.plus(transform); 251 } 252 253 /** 254 * Returns a Twist3d that maps this pose to the end pose. If c is the output of {@code a.Log(b)}, 255 * then {@code a.Exp(c)} would yield b. 256 * 257 * @param end The end pose for the transformation. 258 * @return The twist that maps this to end. 259 */ 260 public Twist3d log(Pose3d end) { 261 // Implementation from Section 3.2 of https://ethaneade.org/lie.pdf 262 final var transform = end.relativeTo(this); 263 264 final var rvec = transform.getRotation().getQuaternion().toRotationVector(); 265 266 final var omega = rotationVectorToMatrix(rvec); 267 final var theta = rvec.norm(); 268 final var thetaSq = theta * theta; 269 270 double C; 271 if (Math.abs(theta) < 1E-7) { 272 // Taylor Expansions around θ = 0 273 // A = 1/1! - θ²/3! + θ⁴/5! 274 // B = 1/2! - θ²/4! + θ⁴/6! 275 // C = 1/6 * (1/2 + θ²/5! + θ⁴/7!) 276 // sources: 277 // A: 278 // https://www.wolframalpha.com/input?i2d=true&i=series+expansion+of+Divide%5Bsin%5C%2840%29x%5C%2841%29%2Cx%5D+at+x%3D0 279 // B: 280 // https://www.wolframalpha.com/input?i2d=true&i=series+expansion+of+Divide%5B1-cos%5C%2840%29x%5C%2841%29%2CPower%5Bx%2C2%5D%5D+at+x%3D0 281 // C: 282 // https://www.wolframalpha.com/input?i2d=true&i=series+expansion+of+Divide%5B1-Divide%5BDivide%5Bsin%5C%2840%29x%5C%2841%29%2Cx%5D%2C2Divide%5B1-cos%5C%2840%29x%5C%2841%29%2CPower%5Bx%2C2%5D%5D%5D%2CPower%5Bx%2C2%5D%5D+at+x%3D0 283 C = 1 / 12.0 + thetaSq / 720 + thetaSq * thetaSq / 30240; 284 } else { 285 // A = sin(θ)/θ 286 // B = (1 - cos(θ)) / θ² 287 // C = (1 - A/(2*B)) / θ² 288 double A = Math.sin(theta) / theta; 289 double B = (1 - Math.cos(theta)) / thetaSq; 290 C = (1 - A / (2 * B)) / thetaSq; 291 } 292 293 final var V_inv = 294 Matrix.eye(Nat.N3()).minus(omega.times(0.5)).plus(omega.times(omega).times(C)); 295 296 final var twist_translation = 297 V_inv.times(VecBuilder.fill(transform.getX(), transform.getY(), transform.getZ())); 298 299 return new Twist3d( 300 twist_translation.get(0, 0), 301 twist_translation.get(1, 0), 302 twist_translation.get(2, 0), 303 rvec.get(0, 0), 304 rvec.get(1, 0), 305 rvec.get(2, 0)); 306 } 307 308 /** 309 * Returns a Pose2d representing this Pose3d projected into the X-Y plane. 310 * 311 * @return A Pose2d representing this Pose3d projected into the X-Y plane. 312 */ 313 public Pose2d toPose2d() { 314 return new Pose2d(m_translation.toTranslation2d(), m_rotation.toRotation2d()); 315 } 316 317 @Override 318 public String toString() { 319 return String.format("Pose3d(%s, %s)", m_translation, m_rotation); 320 } 321 322 /** 323 * Checks equality between this Pose3d and another object. 324 * 325 * @param obj The other object. 326 * @return Whether the two objects are equal or not. 327 */ 328 @Override 329 public boolean equals(Object obj) { 330 if (obj instanceof Pose3d) { 331 return ((Pose3d) obj).m_translation.equals(m_translation) 332 && ((Pose3d) obj).m_rotation.equals(m_rotation); 333 } 334 return false; 335 } 336 337 @Override 338 public int hashCode() { 339 return Objects.hash(m_translation, m_rotation); 340 } 341 342 @Override 343 public Pose3d interpolate(Pose3d endValue, double t) { 344 if (t < 0) { 345 return this; 346 } else if (t >= 1) { 347 return endValue; 348 } else { 349 var twist = this.log(endValue); 350 var scaledTwist = 351 new Twist3d( 352 twist.dx * t, twist.dy * t, twist.dz * t, twist.rx * t, twist.ry * t, twist.rz * t); 353 return this.exp(scaledTwist); 354 } 355 } 356 357 /** 358 * Applies the hat operator to a rotation vector. 359 * 360 * <p>It takes a rotation vector and returns the corresponding matrix representation of the Lie 361 * algebra element (a 3x3 rotation matrix). 362 * 363 * @param rotation The rotation vector. 364 * @return The rotation vector as a 3x3 rotation matrix. 365 */ 366 private Matrix<N3, N3> rotationVectorToMatrix(Vector<N3> rotation) { 367 // Given a rotation vector <a, b, c>, 368 // [ 0 -c b] 369 // Omega = [ c 0 -a] 370 // [-b a 0] 371 return new MatBuilder<>(Nat.N3(), Nat.N3()) 372 .fill( 373 0.0, 374 -rotation.get(2, 0), 375 rotation.get(1, 0), 376 rotation.get(2, 0), 377 0.0, 378 -rotation.get(0, 0), 379 -rotation.get(1, 0), 380 rotation.get(0, 0), 381 0.0); 382 } 383}