-
Notifications
You must be signed in to change notification settings - Fork 9
Add ChassisSpeeds #129
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
DeltaDizzy
wants to merge
17
commits into
robotdotnet:main
Choose a base branch
from
DeltaDizzy:kinematics
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Add ChassisSpeeds #129
Changes from 13 commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
16b42c7
add chassisspeeds
DeltaDizzy 7a91390
add docs
DeltaDizzy 268fa85
start tests
DeltaDizzy 524023f
Add ToString overrides for Pose2d and its components
DeltaDizzy 402daed
add chassisspeed double constructor with defaulted units
DeltaDizzy b1d3dd8
complete chassisspeed tests (depends on pose2d fixes to pass)
DeltaDizzy 935800f
Exp/Transform investigation tests
DeltaDizzy 8f52b8d
chassisspeeds to struct
DeltaDizzy ddd7bc1
pose init weirdness
DeltaDizzy 1824e3f
add pose2d default constructor
DeltaDizzy 5cff10c
add equality and hashcode
DeltaDizzy 6a43144
split tests into correct files
DeltaDizzy a5a72f6
Merge commit 'be30eedd65c27275df353cabad5eee0cd5dcd3bd' into kinematics
DeltaDizzy 04cb598
final default ctor fix
DeltaDizzy 1aeddf3
remove property initializers
DeltaDizzy a61946f
fix ctors in chassisspeed tests
DeltaDizzy dd1451a
make parameters lowercase
DeltaDizzy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,263 @@ | ||
using System.Numerics; | ||
using Google.Protobuf.Reflection; | ||
using UnitsNet; | ||
using UnitsNet.NumberExtensions.NumberToRotationalSpeed; | ||
using UnitsNet.NumberExtensions.NumberToSpeed; | ||
using WPIMath.Geometry; | ||
using WPIMath.Proto; | ||
using WPIUtil.Serialization.Protobuf; | ||
using WPIUtil.Serialization.Struct; | ||
|
||
namespace WPIMath; | ||
|
||
public readonly struct ChassisSpeeds : IAdditionOperators<ChassisSpeeds, ChassisSpeeds, ChassisSpeeds>, | ||
ISubtractionOperators<ChassisSpeeds, ChassisSpeeds, ChassisSpeeds>, | ||
IMultiplyOperators<ChassisSpeeds, double, ChassisSpeeds>, | ||
IDivisionOperators<ChassisSpeeds, double, ChassisSpeeds>, | ||
IUnaryNegationOperators<ChassisSpeeds, ChassisSpeeds>, | ||
IEquatable<ChassisSpeeds> | ||
{ | ||
/// <summary> | ||
/// Chassis velocity in the X-axis (Forward is positive). | ||
/// </summary> | ||
public Speed Vx { get; } = Speed.FromMetersPerSecond(0); | ||
|
||
/// <summary> | ||
/// Chassis velocity in the Y-axis (Left is positive). | ||
/// </summary> | ||
public Speed Vy { get; } = Speed.FromMetersPerSecond(0); | ||
|
||
/// <summary> | ||
/// Chassis Angular velocity (Z-axis or theta, Counter-Clockwise is positive). | ||
/// </summary> | ||
public RotationalSpeed Omega { get; } = RotationalSpeed.FromRadiansPerSecond(0); | ||
|
||
public static IProtobuf<ChassisSpeeds, ProtobufChassisSpeeds> Proto { get; } = new ChassisSpeedsProto(); | ||
public static IStruct<ChassisSpeeds> Struct { get; } = new ChassisSpeedsStruct(); | ||
|
||
/// <summary> | ||
/// Constructs a new ChassisSpeeds object with zero velocity on all axes. | ||
/// </summary> | ||
public ChassisSpeeds() { } | ||
|
||
/// <summary> | ||
/// Constructs a new ChassisSpeeds object with the given velocities. | ||
/// </summary> | ||
/// <param name="Vx">Chassis velocity in the X-axis (Forward is positive).</param> | ||
/// <param name="Vy">Chassis velocity in the Y-axis (Left is positive).</param> | ||
/// <param name="Omega">Chassis Angular velocity (Z-axis or theta, Counter-Clockwise is positive).</param> | ||
public ChassisSpeeds(Speed Vx, Speed Vy, RotationalSpeed Omega) | ||
DeltaDizzy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
this.Vx = Vx; | ||
this.Vy = Vy; | ||
this.Omega = Omega; | ||
} | ||
|
||
/// <summary> | ||
/// Constructs a new ChassisSpeeds object with the given velocities. | ||
/// </summary> | ||
/// <param name="vx">Chassis velocity in the X-axis (Forward is positive) in meters per second.</param> | ||
/// <param name="vy">Chassis velocity in the Y-axis (Left is positive) in meters per second.</param> | ||
/// <param name="omega">Chassis Angular velocity (Z-axis or theta, Counter-Clockwise is positive) in radians per second.</param> | ||
public ChassisSpeeds(double vx, double vy, double omega) | ||
{ | ||
Vx = vx.MetersPerSecond(); | ||
Vy = vy.MetersPerSecond(); | ||
Omega = omega.RadiansPerSecond(); | ||
} | ||
|
||
/// <summary> | ||
/// Discretizes a continuous-time chassis speed. | ||
/// | ||
/// <para> | ||
/// This function converts a continuous-time chassis speed into a discrete-time one such that | ||
/// when the discrete-time chassis speed is applied for one timestep, the robot moves as if the | ||
/// velocity components are independent (i.e., the robot moves v_x * dt along the x-axis, v_y * dt | ||
/// along the y-axis, and omega * dt around the z-axis). | ||
/// </para> | ||
/// <para> | ||
/// This is useful for compensating for translational skew when translating and rotating a | ||
/// swerve drivetrain. | ||
/// </para> | ||
/// </summary> | ||
/// <param name="Vx">Forward velocity.</param> | ||
/// <param name="Vy">Sideways velocity (left is positive).</param> | ||
/// <param name="Omega">Angular velocity (couter-clockwise is positive).</param> | ||
/// <param name="Dt">Timestep the speed should be applied for (this should be the same as the robot loop duration for most users).</param> | ||
/// <returns>Discretized ChassisSpeeds.</returns> | ||
public static ChassisSpeeds Discretize(Speed Vx, Speed Vy, RotationalSpeed Omega, TimeSpan Dt) | ||
{ | ||
Pose2d desiredDeltaPose = new Pose2d(Vx * Dt, Vy * Dt, new Rotation2d(Omega * Dt)); | ||
var twist = new Pose2d().Log(desiredDeltaPose); | ||
return new(twist.Dx / Dt, twist.Dy / Dt, twist.Dtheta / Dt); | ||
} | ||
|
||
/// <summary> | ||
/// Discretizes a continuous-time chassis speed. | ||
/// | ||
/// <para> | ||
/// This function converts a continuous-time chassis speed into a discrete-time one such that | ||
/// when the discrete-time chassis speed is applied for one timestep, the robot moves as if the | ||
/// velocity components are independent (i.e., the robot moves v_x * dt along the x-axis, v_y * dt | ||
/// along the y-axis, and omega * dt around the z-axis). | ||
/// </para> | ||
/// <para> | ||
/// This is useful for compensating for translational skew when translating and rotating a | ||
/// swerve drivetrain. | ||
/// </para> | ||
/// </summary> | ||
/// <param name="ContinuousSpeeds">ChassisSpeeds to discretize.</param> | ||
/// <param name="Dt">Timestep the speed should be applied for (this should be the same as the robot loop duration for most users).</param> | ||
/// <returns>Discretized ChassisSpeeds.</returns> | ||
public static ChassisSpeeds Discretize(ChassisSpeeds ContinuousSpeeds, TimeSpan Dt) => | ||
Discretize(ContinuousSpeeds.Vx, ContinuousSpeeds.Vy, ContinuousSpeeds.Omega, Dt); | ||
|
||
/// <summary> | ||
/// Converts a user provided field-relative set of speeds into a robot-relative ChassisSpeeds object. | ||
/// </summary> | ||
/// <param name="Vx">The component of speed in the x direction relative to the field. Positive x is away | ||
/// from your alliance wall.</param> | ||
/// <param name="Vy">The component of speed in the y direction relative to the field. Positive y is to | ||
/// your left when standing behind the alliance wall.</param> | ||
/// <param name="Omega">The angular velocity of the robot.</param> | ||
/// <param name="RobotAngle">The angle of the robot as measured by a gyroscope. The robot's angle is | ||
/// considered to be zero when it is facing directly away from your alliance station wall. | ||
/// Remember that this should be CCW positive.</param> | ||
/// <returns>ChassisSpeeds object representing the speeds in the robot's frame of reference.</returns> | ||
public static ChassisSpeeds FromFieldRelativeSpeeds(Speed Vx, Speed Vy, RotationalSpeed Omega, Rotation2d RobotAngle) | ||
{ | ||
var oneSecond = TimeSpan.FromSeconds(1); | ||
// clockwise rotation into robot frame, so negate the angle | ||
var chassisFrameVelocity = new Translation2d(Vx * oneSecond, Vy * oneSecond).RotateBy(-RobotAngle); | ||
return new(chassisFrameVelocity.X / oneSecond, chassisFrameVelocity.Y / oneSecond, Omega); | ||
} | ||
|
||
/// <summary> | ||
/// Converts a user provided field-relative set of speeds into a robot-relative ChassisSpeeds object. | ||
/// </summary> | ||
/// <param name="FieldRelativeSpeeds">Field-relative ChassisSpeeds.</param> | ||
/// <param name="RobotAngle">The angle of the robot as measured by a gyroscope. The robot's angle is | ||
/// considered to be zero when it is facing directly away from your alliance station wall. | ||
/// Remember that this should be CCW positive.</param> | ||
/// <returns>ChassisSpeeds object representing the speeds in the robot's frame of reference.</returns> | ||
public static ChassisSpeeds FromFieldRelativeSpeeds(ChassisSpeeds FieldRelativeSpeeds, Rotation2d RobotAngle) => | ||
FromFieldRelativeSpeeds(FieldRelativeSpeeds.Vx, FieldRelativeSpeeds.Vy, FieldRelativeSpeeds.Omega, RobotAngle); | ||
|
||
/// <summary> | ||
/// Converts a user provided robot-relative set of speeds into a field-relative ChassisSpeeds object. | ||
/// </summary> | ||
/// <param name="Vx">The component of speed in the x direction relative to the robot. Positive x is towards the | ||
/// robot's front.</param> | ||
/// <param name="Vy">The component of speed in the y direction relative to the robot. Positive y is towards the | ||
/// robot's left.</param> | ||
/// <param name="Omega">The angular velocity of the robot.</param> | ||
/// <param name="RobotAngle">The angle of the robot as measured by a gyroscope. The robot's angle is | ||
/// considered to be zero when it is facing directly away from your alliance station wall. | ||
/// Remember that this should be CCW positive.</param> | ||
/// <returns>ChassisSpeeds object representing the speeds in the field's frame of reference.</returns> | ||
public static ChassisSpeeds FromRobotRelativeSpeeds(Speed Vx, Speed Vy, RotationalSpeed Omega, Rotation2d RobotAngle) | ||
{ | ||
var oneSecond = TimeSpan.FromSeconds(1); | ||
// counter-clockwise rotation out of robot frame | ||
var chassisFrameVelocity = new Translation2d(Vx * oneSecond, Vy * oneSecond).RotateBy(RobotAngle); | ||
return new(chassisFrameVelocity.X / oneSecond, chassisFrameVelocity.Y / oneSecond, Omega); | ||
} | ||
|
||
/// <summary> | ||
/// Converts a user provided robot-relative ChassisSpeeds object into a field-relative ChassisSpeeds object. | ||
/// </summary> | ||
/// <param name="RobotRelativeSpeeds"> The ChassisSpeeds object representing the speeds in the robot frame | ||
/// of reference. Positive x is towards the robot's front. Positive y is towards the robot's left.</param> | ||
/// <param name="RobotAngle">The angle of the robot as measured by a gyroscope. The robot's angle is | ||
/// considered to be zero when it is facing directly away from your alliance station wall. | ||
/// Remember that this should be CCW positive.</param> | ||
/// <returns>ChassisSpeeds object representing the speeds in the field's frame of reference.</returns> | ||
public static ChassisSpeeds FromRobotRelativeSpeeds(ChassisSpeeds RobotRelativeSpeeds, Rotation2d RobotAngle) => | ||
FromRobotRelativeSpeeds(RobotRelativeSpeeds.Vx, RobotRelativeSpeeds.Vy, RobotRelativeSpeeds.Omega, RobotAngle); | ||
|
||
public static ChassisSpeeds operator +(ChassisSpeeds left, ChassisSpeeds right) => | ||
new(left.Vx + right.Vx, left.Vy + right.Vy, left.Omega + right.Omega); | ||
|
||
public static ChassisSpeeds operator -(ChassisSpeeds left, ChassisSpeeds right) => | ||
new(left.Vx - right.Vx, left.Vy - right.Vy, left.Omega - right.Omega); | ||
|
||
public static ChassisSpeeds operator -(ChassisSpeeds value) => | ||
new(-value.Vx, -value.Vy, -value.Omega); | ||
|
||
public static ChassisSpeeds operator *(ChassisSpeeds speed, double scalar) => | ||
new(speed.Vx * scalar, speed.Vy * scalar, speed.Omega * scalar); | ||
|
||
public static ChassisSpeeds operator /(ChassisSpeeds speeds, double scalar) => | ||
new(speeds.Vx / scalar, speeds.Vy / scalar, speeds.Omega / scalar); | ||
|
||
public override string ToString() => | ||
$"ChassisSpeeds(Vx: {Vx:F2} {Vx.Unit}, Vy: {Vy:F2} {Vy.Unit}, Omega: {Omega:F2} {Omega.Unit})"; | ||
|
||
public bool Equals(ChassisSpeeds other) | ||
{ | ||
return | ||
Math.Abs(Vx.MetersPerSecond - other.Vx.MetersPerSecond) < 1E-9 && | ||
Math.Abs(Vy.MetersPerSecond - other.Vy.MetersPerSecond) < 1E-9 && | ||
Math.Abs(Omega.RadiansPerSecond - other.Omega.RadiansPerSecond) < 1E-9; | ||
} | ||
|
||
public override bool Equals(object? obj) | ||
{ | ||
return obj is ChassisSpeeds speeds && Equals(speeds); | ||
} | ||
|
||
public static bool operator ==(ChassisSpeeds left, ChassisSpeeds right) | ||
{ | ||
return left.Equals(right); | ||
} | ||
|
||
public static bool operator !=(ChassisSpeeds left, ChassisSpeeds right) | ||
{ | ||
return !(left == right); | ||
} | ||
|
||
public override int GetHashCode() => HashCode.Combine(Vx, Vy, Omega); | ||
} | ||
|
||
public class ChassisSpeedsProto : IProtobuf<ChassisSpeeds, ProtobufChassisSpeeds> | ||
{ | ||
public MessageDescriptor Descriptor => ProtobufChassisSpeeds.Descriptor; | ||
|
||
public ProtobufChassisSpeeds CreateMessage() => new(); | ||
|
||
public void Pack(ProtobufChassisSpeeds msg, ChassisSpeeds value) | ||
{ | ||
msg.Vx = value.Vx.MetersPerSecond; | ||
msg.Vy = value.Vy.MetersPerSecond; | ||
msg.Omega = value.Omega.RadiansPerSecond; | ||
} | ||
|
||
public ChassisSpeeds Unpack(ProtobufChassisSpeeds msg) | ||
{ | ||
return new(Speed.FromMetersPerSecond(msg.Vx), Speed.FromMetersPerSecond(msg.Vy), RotationalSpeed.FromRadiansPerSecond(msg.Omega)); | ||
} | ||
} | ||
|
||
public class ChassisSpeedsStruct : IStruct<ChassisSpeeds> | ||
{ | ||
public string TypeString => "struct:ChassisSpeeds"; | ||
|
||
public int Size => sizeof(double) * 3; | ||
|
||
public string Schema => "double vx;double vy;double omega"; | ||
|
||
public void Pack(ref StructPacker buffer, ChassisSpeeds value) | ||
{ | ||
buffer.WriteDouble(value.Vx.MetersPerSecond); | ||
buffer.WriteDouble(value.Vy.MetersPerSecond); | ||
buffer.WriteDouble(value.Omega.RadiansPerSecond); | ||
} | ||
|
||
public ChassisSpeeds Unpack(ref StructUnpacker buffer) | ||
{ | ||
var vx = Speed.FromMetersPerSecond(buffer.ReadDouble()); | ||
var vy = Speed.FromMetersPerSecond(buffer.ReadDouble()); | ||
var omega = RotationalSpeed.FromRadiansPerSecond(buffer.ReadDouble()); | ||
return new(vx, vy, omega); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
using UnitsNet.NumberExtensions.NumberToAngle; | ||
using UnitsNet.NumberExtensions.NumberToLength; | ||
using WPIMath.Geometry; | ||
using Xunit; | ||
|
||
namespace WPIMath.Test.Geometry | ||
{ | ||
public class Pose2dTest | ||
{ | ||
[Fact] | ||
public void TestPose2dTransformBy() | ||
{ | ||
Pose2d start = new(); | ||
|
||
Transform2d transform = new(new Translation2d(5.Meters(), 5.Meters()), new Rotation2d(90.Degrees())); | ||
|
||
Pose2d end = new(new Translation2d(5.Meters(), 5.Meters()), new Rotation2d(90.Degrees())); | ||
|
||
Assert.Equal(end, start.TransformBy(transform)); | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
using UnitsNet.NumberExtensions.NumberToAngle; | ||
using UnitsNet.NumberExtensions.NumberToLength; | ||
using WPIMath.Geometry; | ||
using Xunit; | ||
|
||
namespace WPIMath.Test.Geometry | ||
{ | ||
public class Translation2dTest | ||
{ | ||
[Fact] | ||
public void TestTranslation2dRotateBy() | ||
{ | ||
Translation2d start = new(5.Meters(), 0.Meters()); | ||
Rotation2d rotation = new(90.Degrees()); | ||
var end = start.RotateBy(rotation); | ||
|
||
Assert.Equal(new Translation2d(0.Meters(), 5.Meters()), end); | ||
} | ||
|
||
[Fact] | ||
public void TestTranslation2dAddition() | ||
{ | ||
Translation2d start = new(5.Meters(), 0.Meters()); | ||
Translation2d offset = new(2.Meters(), 3.Meters()); | ||
Translation2d end = new(7.Meters(), 3.Meters()); | ||
|
||
Assert.Equal(end, start + offset); | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.