001// Copyright (c) Choreo contributors 002 003package choreo.trajectory; 004 005import choreo.util.ChoreoAllianceFlipUtil; 006import edu.wpi.first.math.MathUtil; 007import edu.wpi.first.math.Matrix; 008import edu.wpi.first.math.VecBuilder; 009import edu.wpi.first.math.geometry.Pose2d; 010import edu.wpi.first.math.geometry.Rotation2d; 011import edu.wpi.first.math.kinematics.ChassisSpeeds; 012import edu.wpi.first.math.numbers.N1; 013import edu.wpi.first.math.numbers.N2; 014import edu.wpi.first.math.numbers.N6; 015import edu.wpi.first.math.system.NumericalIntegration; 016import edu.wpi.first.util.struct.Struct; 017import java.nio.ByteBuffer; 018import java.util.function.BiFunction; 019 020/** A single differential drive robot sample in a Trajectory. */ 021public class DifferentialSample implements TrajectorySample<DifferentialSample> { 022 /** The timestamp of this sample relative to the beginning of the trajectory. */ 023 public final double t; 024 025 /** The X position of the sample relative to the blue alliance wall origin in meters. */ 026 public final double x; 027 028 /** The Y position of the sample relative to the blue alliance wall origin in meters. */ 029 public final double y; 030 031 /** The heading of the sample in radians, with 0 being in the +X direction. */ 032 public final double heading; 033 034 /** The velocity of the left side in m/s. */ 035 public final double vl; 036 037 /** The velocity of the right side in m/s. */ 038 public final double vr; 039 040 /** The chassis angular velocity in rad/s. */ 041 public final double omega; 042 043 /** The acceleration of the left side in m/s². */ 044 public final double al; 045 046 /** The acceleration of the right side in m/s². */ 047 public final double ar; 048 049 /** The force of the left side in Newtons. */ 050 public final double fl; 051 052 /** The force of the right side in Newtons. */ 053 public final double fr; 054 055 /** 056 * Constructs a DifferentialSample with the specified parameters. 057 * 058 * @param timestamp The timestamp of this sample. 059 * @param x The X position of the sample in meters. 060 * @param y The Y position of the sample in meters. 061 * @param heading The heading of the sample in radians, with 0 being in the +X direction. 062 * @param vl The velocity of the left side in m/s. 063 * @param vr The velocity of the right side in m/s. 064 * @param omega The chassis angular velocity in rad/s. 065 * @param al The acceleration of the left side in m/s². 066 * @param ar The acceleration of the right side in m/s². 067 * @param fl The force of the left side in Newtons. 068 * @param fr The force of the right side in Newtons. 069 */ 070 public DifferentialSample( 071 double timestamp, 072 double x, 073 double y, 074 double heading, 075 double vl, 076 double vr, 077 double omega, 078 double al, 079 double ar, 080 double fl, 081 double fr) { 082 this.t = timestamp; 083 this.x = x; 084 this.y = y; 085 this.heading = heading; 086 this.vl = vl; 087 this.vr = vr; 088 this.omega = omega; 089 this.al = al; 090 this.ar = ar; 091 this.fl = fl; 092 this.fr = fr; 093 } 094 095 @Override 096 public double getTimestamp() { 097 return t; 098 } 099 100 @Override 101 public Pose2d getPose() { 102 return new Pose2d(x, y, Rotation2d.fromRadians(heading)); 103 } 104 105 /** 106 * Returns the field-relative chassis speeds of this sample. 107 * 108 * @return the field-relative chassis speeds of this sample. 109 * @see edu.wpi.first.math.kinematics.DifferentialDriveKinematics#toChassisSpeeds 110 */ 111 @Override 112 public ChassisSpeeds getChassisSpeeds() { 113 return new ChassisSpeeds((vl + vr) / 2, 0, omega); 114 } 115 116 @Override 117 public DifferentialSample interpolate(DifferentialSample endValue, double timestamp) { 118 double scale = (timestamp - this.t) / (endValue.t - this.t); 119 120 // Integrate the acceleration to get the rest of the state, since linearly 121 // interpolating the state gives an inaccurate result if the accelerations are changing between 122 // states 123 Matrix<N6, N1> initialState = VecBuilder.fill(x, y, heading, vl, vr, omega); 124 125 double width = (vr - vl) / omega; 126 BiFunction<Matrix<N6, N1>, Matrix<N2, N1>, Matrix<N6, N1>> f = 127 (state, input) -> { 128 // state = [x, y, θ, vₗ, vᵣ, ω] 129 // input = [aₗ, aᵣ] 130 // 131 // v = (vₗ + vᵣ)/2 132 // ω = (vᵣ − vₗ)/width 133 // α = (aᵣ − aₗ)/width 134 // 135 // ẋ = v cosθ 136 // ẏ = v sinθ 137 // θ̇ = ω 138 // v̇ₗ = aₗ 139 // v̇ᵣ = aᵣ 140 // ω̇ = α 141 var θ = state.get(2, 0); 142 var vl = state.get(3, 0); 143 var vr = state.get(4, 0); 144 var ω = state.get(5, 0); 145 var al = input.get(0, 0); 146 var ar = input.get(1, 0); 147 var v = (vl + vr) / 2; 148 var α = (ar - al) / width; 149 return VecBuilder.fill(v * Math.cos(θ), v * Math.sin(θ), ω, al, ar, α); 150 }; 151 152 double τ = timestamp - this.t; 153 var sample = NumericalIntegration.rkdp(f, initialState, VecBuilder.fill(al, ar), τ); 154 155 double dt = endValue.t - this.t; 156 double jl = (endValue.al - this.al) / dt; 157 double jr = (endValue.ar - this.ar) / dt; 158 159 return new DifferentialSample( 160 MathUtil.interpolate(this.t, endValue.t, scale), 161 sample.get(0, 0), 162 sample.get(1, 0), 163 sample.get(2, 0), 164 sample.get(3, 0), 165 sample.get(4, 0), 166 sample.get(5, 0), 167 this.al + jl * τ, 168 this.ar + jr * τ, 169 MathUtil.interpolate(this.fl, endValue.fl, scale), 170 MathUtil.interpolate(this.fr, endValue.fr, scale)); 171 } 172 173 public DifferentialSample flipped() { 174 return switch (ChoreoAllianceFlipUtil.getFlipper()) { 175 case MIRRORED -> 176 new DifferentialSample( 177 t, 178 ChoreoAllianceFlipUtil.flipX(x), 179 ChoreoAllianceFlipUtil.flipY(y), // No-op for mirroring 180 ChoreoAllianceFlipUtil.flipHeading(heading), 181 vr, 182 vl, 183 -omega, 184 ar, 185 al, 186 fr, 187 fl); 188 case ROTATE_AROUND -> 189 new DifferentialSample( 190 t, 191 ChoreoAllianceFlipUtil.flipX(x), 192 ChoreoAllianceFlipUtil.flipY(y), 193 ChoreoAllianceFlipUtil.flipHeading(heading), 194 vl, 195 vr, 196 omega, 197 al, 198 ar, 199 fl, 200 fr); 201 }; 202 } 203 204 public DifferentialSample offsetBy(double timestampOffset) { 205 return new DifferentialSample( 206 t + timestampOffset, x, y, heading, vl, vr, omega, al, ar, fl, fr); 207 } 208 209 /** The struct for the DifferentialSample class. */ 210 public static final Struct<DifferentialSample> struct = new DifferentialSampleStruct(); 211 212 private static final class DifferentialSampleStruct implements Struct<DifferentialSample> { 213 @Override 214 public Class<DifferentialSample> getTypeClass() { 215 return DifferentialSample.class; 216 } 217 218 @Override 219 public String getTypeName() { 220 return "DifferentialSample"; 221 } 222 223 @Override 224 public int getSize() { 225 return Struct.kSizeDouble * 10; 226 } 227 228 @Override 229 public String getSchema() { 230 return "double timestamp;" 231 + "Pose2d pose;" 232 + "double vl;" 233 + "double vr;" 234 + "double omega;" 235 + "double al;" 236 + "double ar;" 237 + "double fl;" 238 + "double fr;"; 239 } 240 241 @Override 242 public Struct<?>[] getNested() { 243 return new Struct<?>[] {Pose2d.struct}; 244 } 245 246 @Override 247 public DifferentialSample unpack(ByteBuffer bb) { 248 return new DifferentialSample( 249 bb.getDouble(), 250 bb.getDouble(), 251 bb.getDouble(), 252 bb.getDouble(), 253 bb.getDouble(), 254 bb.getDouble(), 255 bb.getDouble(), 256 bb.getDouble(), 257 bb.getDouble(), 258 bb.getDouble(), 259 bb.getDouble()); 260 } 261 262 @Override 263 public void pack(ByteBuffer bb, DifferentialSample value) { 264 bb.putDouble(value.t); 265 bb.putDouble(value.x); 266 bb.putDouble(value.y); 267 bb.putDouble(value.heading); 268 bb.putDouble(value.vl); 269 bb.putDouble(value.vr); 270 bb.putDouble(value.omega); 271 bb.putDouble(value.al); 272 bb.putDouble(value.ar); 273 bb.putDouble(value.fl); 274 bb.putDouble(value.fr); 275 } 276 } 277 278 @Override 279 public boolean equals(Object obj) { 280 if (!(obj instanceof DifferentialSample)) { 281 return false; 282 } 283 284 var other = (DifferentialSample) obj; 285 return this.t == other.t 286 && this.x == other.x 287 && this.y == other.y 288 && this.heading == other.heading 289 && this.vl == other.vl 290 && this.vr == other.vr 291 && this.omega == other.omega 292 && this.al == other.al 293 && this.ar == other.ar 294 && this.fl == other.fl 295 && this.fr == other.fr; 296 } 297}