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 org.wpilib.math.linalg.Matrix; 008import org.wpilib.math.util.MathSharedStore; 009import org.wpilib.math.util.Nat; 010 011/** A helper class that converts Pose3d objects between different standard coordinate frames. */ 012public final class CoordinateSystem { 013 private static final CoordinateSystem m_nwu = 014 new CoordinateSystem(CoordinateAxis.N(), CoordinateAxis.W(), CoordinateAxis.U()); 015 private static final CoordinateSystem m_edn = 016 new CoordinateSystem(CoordinateAxis.E(), CoordinateAxis.D(), CoordinateAxis.N()); 017 private static final CoordinateSystem m_ned = 018 new CoordinateSystem(CoordinateAxis.N(), CoordinateAxis.E(), CoordinateAxis.D()); 019 020 // Rotation from this coordinate system to NWU coordinate system when applied extrinsically 021 private final Rotation3d m_rotation; 022 023 /** 024 * Constructs a coordinate system with the given cardinal directions for each axis. 025 * 026 * @param positiveX The cardinal direction of the positive x-axis. 027 * @param positiveY The cardinal direction of the positive y-axis. 028 * @param positiveZ The cardinal direction of the positive z-axis. 029 * @throws IllegalArgumentException if the coordinate system isn't special orthogonal 030 */ 031 public CoordinateSystem( 032 CoordinateAxis positiveX, CoordinateAxis positiveY, CoordinateAxis positiveZ) { 033 // Construct a change of basis matrix from the source coordinate system to the 034 // NWU coordinate system. Each column vector in the change of basis matrix is 035 // one of the old basis vectors mapped to its representation in the new basis. 036 var R = new Matrix<>(Nat.N3(), Nat.N3()); 037 R.assignBlock(0, 0, positiveX.m_axis); 038 R.assignBlock(0, 1, positiveY.m_axis); 039 R.assignBlock(0, 2, positiveZ.m_axis); 040 041 // If determinant is -1, coordinate system is left-handed 042 if (Math.abs(R.det() + 1.0) < 1e-9) { 043 var msg = 044 "CoordinateSystem requires a right-handed system, but a left-handed one was provided"; 045 MathSharedStore.reportError(msg, Thread.currentThread().getStackTrace()); 046 throw new IllegalArgumentException(msg); 047 } 048 049 // The change of basis matrix should be a pure rotation. The Rotation3d 050 // constructor will verify this by checking for special orthogonality. 051 m_rotation = new Rotation3d(R); 052 } 053 054 /** 055 * Returns an instance of the North-West-Up (NWU) coordinate system. 056 * 057 * <p>The +X axis is north, the +Y axis is west, and the +Z axis is up. 058 * 059 * @return An instance of the North-West-Up (NWU) coordinate system. 060 */ 061 public static CoordinateSystem NWU() { 062 return m_nwu; 063 } 064 065 /** 066 * Returns an instance of the East-Down-North (EDN) coordinate system. 067 * 068 * <p>The +X axis is east, the +Y axis is down, and the +Z axis is north. 069 * 070 * @return An instance of the East-Down-North (EDN) coordinate system. 071 */ 072 public static CoordinateSystem EDN() { 073 return m_edn; 074 } 075 076 /** 077 * Returns an instance of the North-East-Down (NED) coordinate system. 078 * 079 * <p>The +X axis is north, the +Y axis is east, and the +Z axis is down. 080 * 081 * @return An instance of the North-East-Down (NED) coordinate system. 082 */ 083 public static CoordinateSystem NED() { 084 return m_ned; 085 } 086 087 /** 088 * Converts the given translation from one coordinate system to another. 089 * 090 * @param translation The translation to convert. 091 * @param from The coordinate system the pose starts in. 092 * @param to The coordinate system to which to convert. 093 * @return The given translation in the desired coordinate system. 094 */ 095 public static Translation3d convert( 096 Translation3d translation, CoordinateSystem from, CoordinateSystem to) { 097 // Convert to NWU, then convert to the new coordinate system 098 return translation.rotateBy(from.m_rotation).rotateBy(to.m_rotation.inverse()); 099 } 100 101 /** 102 * Converts the given rotation from one coordinate system to another. 103 * 104 * @param rotation The rotation to convert. 105 * @param from The coordinate system the rotation starts in. 106 * @param to The coordinate system to which to convert. 107 * @return The given rotation in the desired coordinate system. 108 */ 109 public static Rotation3d convert( 110 Rotation3d rotation, CoordinateSystem from, CoordinateSystem to) { 111 // Convert to NWU, then convert to the new coordinate system 112 return rotation.rotateBy(from.m_rotation).rotateBy(to.m_rotation.inverse()); 113 } 114 115 /** 116 * Converts the given pose from one coordinate system to another. 117 * 118 * @param pose The pose to convert. 119 * @param from The coordinate system the pose starts in. 120 * @param to The coordinate system to which to convert. 121 * @return The given pose in the desired coordinate system. 122 */ 123 public static Pose3d convert(Pose3d pose, CoordinateSystem from, CoordinateSystem to) { 124 return new Pose3d( 125 convert(pose.getTranslation(), from, to), convert(pose.getRotation(), from, to)); 126 } 127 128 /** 129 * Converts the given transform from one coordinate system to another. 130 * 131 * @param transform The transform to convert. 132 * @param from The coordinate system the transform starts in. 133 * @param to The coordinate system to which to convert. 134 * @return The given transform in the desired coordinate system. 135 */ 136 public static Transform3d convert( 137 Transform3d transform, CoordinateSystem from, CoordinateSystem to) { 138 // coordRot is the rotation that converts between the coordinate systems when applied 139 // extrinsically. It first converts to NWU, then converts to the new coordinate system. 140 var coordRot = from.m_rotation.rotateBy(to.m_rotation.inverse()); 141 // The new rotation is the extrinsic rotation from convert(zero) to 142 // convert(transformRot). That is, applying convertedRot extrinsically to 143 // convert(zero) produces convert(transformRot). In the below snippet, we 144 // use matrix notation, so rotA rotB applies rotA extrinsically to rotB. 145 // 146 // convertedRot convert(zero) = convert(transformRot) 147 // convertedRot = convert(transformRot) convert(zero)⁻¹ 148 // = (coordRot transformRot) (coordRot zero)⁻¹ 149 // = (coordRot transformRot) coordRot⁻¹ 150 // 151 // In code, the equivalent for rotA rotB is rotB.rotateBy(rotA) (note the 152 // change in order). 153 return new Transform3d( 154 convert(transform.getTranslation(), from, to), 155 coordRot.inverse().rotateBy(transform.getRotation().rotateBy(coordRot))); 156 } 157}