diff --git a/wpimath/src/main/java/edu/wpi/first/math/MathUtil.java b/wpimath/src/main/java/edu/wpi/first/math/MathUtil.java index 239a6a31fb..01a4f0337c 100644 --- a/wpimath/src/main/java/edu/wpi/first/math/MathUtil.java +++ b/wpimath/src/main/java/edu/wpi/first/math/MathUtil.java @@ -107,6 +107,39 @@ public final class MathUtil { return applyDeadband(value, deadband, 1); } + /** + * Returns a zero vector if the given vector is within the specified distance from the origin. The + * remaining distance between the deadband and the maximum distance is scaled from the origin to + * the maximum distance. + * + * @param value Value to clip. + * @param deadband Distance from origin. + * @param maxMagnitude The maximum distance from the origin of the input. Can be infinite. + * @param The number of rows in the vector. + * @return The value after the deadband is applied. + */ + public static Vector applyDeadband( + Vector value, double deadband, double maxMagnitude) { + if (value.norm() < 1e-9) { + return value.times(0); + } + return value.unit().times(applyDeadband(value.norm(), deadband, maxMagnitude)); + } + + /** + * Returns a zero vector if the given vector is within the specified distance from the origin. The + * remaining distance between the deadband and a distance of 1.0 is scaled from the origin to a + * distance of 1.0. + * + * @param value Value to clip. + * @param deadband Distance from origin. + * @param The number of rows in the vector. + * @return The value after the deadband is applied. + */ + public static Vector applyDeadband(Vector value, double deadband) { + return applyDeadband(value, deadband, 1); + } + /** * Raises the input to the power of the given exponent while preserving its sign. * @@ -143,6 +176,42 @@ public final class MathUtil { return copySignPow(value, exponent, 1); } + /** + * Raises the norm of the input to the power of the given exponent while preserving its direction. + * + *

The function normalizes the norm of the input to the range [0, 1] based on the maximum + * distance, raises it to the power of the exponent, then scales the result back to the original + * range. This keeps the value in the original max distance and gives consistent curve behavior + * regardless of the input norm's scale. + * + * @param value The input vector to transform. + * @param exponent The exponent to apply (e.g. 1.0 = linear, 2.0 = squared curve). Must be + * positive. + * @param maxMagnitude The maximum expected distance from origin of input. Must be positive. + * @param The number of rows in the vector. + * @return The transformed value with the same direction and norm scaled to the input range. + */ + public static Vector copyDirectionPow( + Vector value, double exponent, double maxMagnitude) { + if (value.norm() < 1e-9) { + return value.times(0); + } + return value.unit().times(copySignPow(value.norm(), exponent, maxMagnitude)); + } + + /** + * Raises the norm of the input to the power of the given exponent while preserving its direction. + * + * @param value The input vector to transform. + * @param exponent The exponent to apply (e.g. 1.0 = linear, 2.0 = squared curve). Must be + * positive. + * @param The number of rows in the vector. + * @return The transformed value with the same direction. + */ + public static Vector copyDirectionPow(Vector value, double exponent) { + return copyDirectionPow(value, exponent, 1); + } + /** * Returns modulus of input. * diff --git a/wpimath/src/main/native/include/frc/MathUtil.h b/wpimath/src/main/native/include/frc/MathUtil.h index 97705811f9..52fa05df86 100644 --- a/wpimath/src/main/native/include/frc/MathUtil.h +++ b/wpimath/src/main/native/include/frc/MathUtil.h @@ -94,15 +94,41 @@ constexpr T ApplyDeadband(T value, T deadband, T maxMagnitude = T{1.0}) { } } +/** + * Returns a zero vector if the given vector is within the specified + * distance from the origin. The remaining distance between the deadband and the + * maximum distance is scaled from the origin to the maximum distance. + * + * @param value Value to clip. + * @param deadband Distance from origin. + * @param maxMagnitude The maximum distance from the origin of the input + * (defaults to 1). Can be infinite. + * @return The value after the deadband is applied. + */ +template + requires std::is_arithmetic_v || units::traits::is_unit_t_v +Eigen::Vector ApplyDeadband(const Eigen::Vector& value, T deadband, + T maxMagnitude = T{1.0}) { + if constexpr (std::is_arithmetic_v) { + if (value.norm() < T{1e-9}) { + return Eigen::Vector::Zero(); + } + return value.normalized() * + ApplyDeadband(value.norm(), deadband, maxMagnitude); + } else { + const Eigen::Vector asDouble = value.template cast(); + const Eigen::Vector processed = + ApplyDeadband(asDouble, deadband.value(), maxMagnitude.value()); + return processed.template cast(); + } +} + /** * Raises the input to the power of the given exponent while preserving its * sign. * * The function normalizes the input value to the range [0, 1] based on the - * maximum magnitude, raises it to the power of the exponent, then scales the - * result back to the original range and copying the sign. This keeps the value - * in the original range and gives consistent curve behavior regardless of the - * input value's scale. + * maximum magnitude so that the output stays in the range. * * This is useful for applying smoother or more aggressive control response * curves (e.g. joystick input shaping). @@ -110,8 +136,8 @@ constexpr T ApplyDeadband(T value, T deadband, T maxMagnitude = T{1.0}) { * @param value The input value to transform. * @param exponent The exponent to apply (e.g. 1.0 = linear, 2.0 = squared * curve). Must be positive. - * @param maxMagnitude The maximum expected absolute value of input. Must be - * positive. + * @param maxMagnitude The maximum expected absolute value of input (defaults to + * 1). Must be positive. * @return The transformed value with the same sign and scaled to the input * range. */ @@ -130,6 +156,42 @@ constexpr T CopySignPow(T value, double exponent, T maxMagnitude = T{1.0}) { } } +/** + * Raises the norm of the input to the power of the given exponent while + * preserving its direction. + * + * The function normalizes the input value to the range [0, 1] based on the + * maximum magnitude so that the output stays in the range. + * + * This is useful for applying smoother or more aggressive control response + * curves (e.g. joystick input shaping). + * + * @param value The input vector to transform. + * @param exponent The exponent to apply (e.g. 1.0 = linear, 2.0 = squared + * curve). Must be positive. + * @param maxMagnitude The maximum expected distance from origin of input + * (defaults to 1). Must be positive. + * @return The transformed value with the same direction and norm scaled to + * the input range. + */ +template + requires std::is_arithmetic_v || units::traits::is_unit_t_v +Eigen::Vector CopyDirectionPow(const Eigen::Vector& value, + double exponent, T maxMagnitude = T{1.0}) { + if constexpr (std::is_arithmetic_v) { + if (value.norm() < T{1e-9}) { + return Eigen::Vector::Zero(); + } + return value.normalized() * + CopySignPow(value.norm(), exponent, maxMagnitude); + } else { + const Eigen::Vector asDouble = value.template cast(); + const Eigen::Vector processed = + CopyDirectionPow(asDouble, exponent, maxMagnitude.value()); + return processed.template cast(); + } +} + /** * Returns modulus of input. * @@ -279,6 +341,7 @@ constexpr Translation2d SlewRateLimit(const Translation2d& current, } if (dist > maxVelocity * dt) { // Move maximum allowed amount in direction of the difference + // NOLINTNEXTLINE(bugprone-integer-division) return current + diff * (maxVelocity * dt / dist); } return next; @@ -309,6 +372,7 @@ constexpr Translation3d SlewRateLimit(const Translation3d& current, } if (dist > maxVelocity * dt) { // Move maximum allowed amount in direction of the difference + // NOLINTNEXTLINE(bugprone-integer-division) return current + diff * (maxVelocity * dt / dist); } return next; diff --git a/wpimath/src/test/java/edu/wpi/first/math/MathUtilTest.java b/wpimath/src/test/java/edu/wpi/first/math/MathUtilTest.java index 1fcef74d1f..1c6d725023 100644 --- a/wpimath/src/test/java/edu/wpi/first/math/MathUtilTest.java +++ b/wpimath/src/test/java/edu/wpi/first/math/MathUtilTest.java @@ -72,6 +72,60 @@ class MathUtilTest extends UtilityClassTest { assertEquals(80.0, MathUtil.applyDeadband(100.0, 20, Double.POSITIVE_INFINITY)); } + @Test + void testApplyDeadband2dUnityScale() { + assertEquals( + VecBuilder.fill(0.0, 1.0), MathUtil.applyDeadband(VecBuilder.fill(0.0, 1.0), 0.02)); + assertEquals( + VecBuilder.fill(0.0, -1.0), MathUtil.applyDeadband(VecBuilder.fill(0.0, -1.0), 0.02)); + assertEquals( + VecBuilder.fill(-1.0, 0.0), MathUtil.applyDeadband(VecBuilder.fill(-1.0, 0.0), 0.02)); + + // == 0 + assertEquals( + VecBuilder.fill(0.0, 0.0), MathUtil.applyDeadband(VecBuilder.fill(0.0, 0.0), 0.02)); + + // > 0 + assertEquals( + VecBuilder.fill(0.0, 0.0), MathUtil.applyDeadband(VecBuilder.fill(0.01, 0.0), 0.02)); + assertEquals( + VecBuilder.fill(0.0, 0.0), MathUtil.applyDeadband(VecBuilder.fill(0.02, 0.0), 0.02)); + assertEquals( + VecBuilder.fill((0.03 - 0.02) / (1.0 - 0.02), 0.0), + MathUtil.applyDeadband(VecBuilder.fill(0.03, 0.0), 0.02)); + assertEquals( + VecBuilder.fill(1.0, 0.0), MathUtil.applyDeadband(VecBuilder.fill(1.0, 0.0), 0.02)); + } + + @Test + void testApplyDeadband2dArbitraryScale() { + assertEquals( + VecBuilder.fill(0.0, 2.5), MathUtil.applyDeadband(VecBuilder.fill(0.0, 2.5), 0.02, 2.5)); + assertEquals( + VecBuilder.fill(0.0, -2.5), MathUtil.applyDeadband(VecBuilder.fill(0.0, -2.5), 0.02, 2.5)); + assertEquals( + VecBuilder.fill(-2.5, 0.0), MathUtil.applyDeadband(VecBuilder.fill(-2.5, 0.0), 0.02, 2.5)); + + // == 0 + assertEquals( + VecBuilder.fill(0.0, 0.0), MathUtil.applyDeadband(VecBuilder.fill(0.0, 0.0), 0.02, 2.5)); + + // > 0 + assertEquals( + VecBuilder.fill(0.0, 0.0), MathUtil.applyDeadband(VecBuilder.fill(0.01, 0.0), 0.02, 2.5)); + assertEquals( + VecBuilder.fill(0.0, 0.0), MathUtil.applyDeadband(VecBuilder.fill(0.02, 0.0), 0.02, 2.5)); + assertEquals( + VecBuilder.fill(2.5, 0.0), MathUtil.applyDeadband(VecBuilder.fill(2.5, 0.0), 0.02, 2.5)); + } + + @Test + void testApplyDeadband2dLargeMaxMagnitude() { + assertEquals( + VecBuilder.fill(80.0, 0.0), + MathUtil.applyDeadband(VecBuilder.fill(100.0, 0.0), 20, Double.POSITIVE_INFINITY)); + } + @Test void testCopySignPow() { assertEquals(0.5, MathUtil.copySignPow(0.5, 1.0)); @@ -110,6 +164,77 @@ class MathUtilTest extends UtilityClassTest { assertEquals(-Math.pow(0.8, 0.3) * 100, MathUtil.copySignPow(-80, 0.3, 100.0)); } + @Test + void testCopyDirectionPow2d() { + assertEquals( + VecBuilder.fill(0.5, 0.0), MathUtil.copyDirectionPow(VecBuilder.fill(0.5, 0.0), 1.0)); + assertEquals( + VecBuilder.fill(-0.5, 0.0), MathUtil.copyDirectionPow(VecBuilder.fill(-0.5, 0.0), 1.0)); + + assertEquals( + VecBuilder.fill(0.25, 0.0), MathUtil.copyDirectionPow(VecBuilder.fill(0.5, 0.0), 2.0)); + assertEquals( + VecBuilder.fill(-0.25, 0.0), MathUtil.copyDirectionPow(VecBuilder.fill(-0.5, 0.0), 2.0)); + + assertEquals( + VecBuilder.fill(Math.sqrt(0.5), 0.0), + MathUtil.copyDirectionPow(VecBuilder.fill(0.5, 0.0), 0.5)); + assertEquals( + VecBuilder.fill(-Math.sqrt(0.5), 0.0), + MathUtil.copyDirectionPow(VecBuilder.fill(-0.5, 0.0), 0.5)); + + assertEquals( + VecBuilder.fill(0.0, 0.0), MathUtil.copyDirectionPow(VecBuilder.fill(0.0, 0.0), 2.0)); + assertEquals( + VecBuilder.fill(1.0, 0.0), MathUtil.copyDirectionPow(VecBuilder.fill(1.0, 0.0), 2.0)); + assertEquals( + VecBuilder.fill(-1.0, 0.0), MathUtil.copyDirectionPow(VecBuilder.fill(-1.0, 0.0), 2.0)); + + assertEquals( + VecBuilder.fill(0.0, Math.pow(0.8, 0.3)), + MathUtil.copyDirectionPow(VecBuilder.fill(0.0, 0.8), 0.3)); + assertEquals( + VecBuilder.fill(0.0, -Math.pow(0.8, 0.3)), + MathUtil.copyDirectionPow(VecBuilder.fill(0.0, -0.8), 0.3)); + } + + @Test + void testCopyDirectionPow2dMaxDistance() { + assertEquals( + VecBuilder.fill(5.0, 0.0), MathUtil.copyDirectionPow(VecBuilder.fill(5.0, 0.0), 1.0, 10.0)); + assertEquals( + VecBuilder.fill(-5.0, 0.0), + MathUtil.copyDirectionPow(VecBuilder.fill(-5.0, 0.0), 1.0, 10.0)); + + assertEquals( + VecBuilder.fill(2.5, 0.0), MathUtil.copyDirectionPow(VecBuilder.fill(5.0, 0.0), 2.0, 10.0)); + assertEquals( + VecBuilder.fill(-2.5, 0.0), + MathUtil.copyDirectionPow(VecBuilder.fill(-5.0, 0.0), 2.0, 10.0)); + + assertEquals( + VecBuilder.fill(Math.sqrt(0.5) * 10, 0.0), + MathUtil.copyDirectionPow(VecBuilder.fill(5.0, 0.0), 0.5, 10.0)); + assertEquals( + VecBuilder.fill(-Math.sqrt(0.5) * 10, 0.0), + MathUtil.copyDirectionPow(VecBuilder.fill(-5.0, 0.0), 0.5, 10.0)); + + assertEquals( + VecBuilder.fill(0.0, 0.0), MathUtil.copyDirectionPow(VecBuilder.fill(0.0, 0.0), 2.0, 5.0)); + assertEquals( + VecBuilder.fill(5.0, 0.0), MathUtil.copyDirectionPow(VecBuilder.fill(5.0, 0.0), 2.0, 5.0)); + assertEquals( + VecBuilder.fill(-5.0, 0.0), + MathUtil.copyDirectionPow(VecBuilder.fill(-5.0, 0.0), 2.0, 5.0)); + + assertEquals( + VecBuilder.fill(0.0, Math.pow(0.8, 0.3) * 100), + MathUtil.copyDirectionPow(VecBuilder.fill(0.0, 80.0), 0.3, 100.0)); + assertEquals( + VecBuilder.fill(0.0, -Math.pow(0.8, 0.3) * 100), + MathUtil.copyDirectionPow(VecBuilder.fill(0.0, -80.0), 0.3, 100.0)); + } + @Test void testInputModulus() { // These tests check error wrapping. That is, the result of wrapping the diff --git a/wpimath/src/test/native/cpp/MathUtilTest.cpp b/wpimath/src/test/native/cpp/MathUtilTest.cpp index 1479c0d18f..6e493dca56 100644 --- a/wpimath/src/test/native/cpp/MathUtilTest.cpp +++ b/wpimath/src/test/native/cpp/MathUtilTest.cpp @@ -55,8 +55,8 @@ TEST(MathUtilTest, ApplyDeadbandArbitraryScale) { TEST(MathUtilTest, ApplyDeadbandUnits) { // < 0 - EXPECT_DOUBLE_EQ( - -20, frc::ApplyDeadband(-20_rad, 1_rad, 20_rad).value()); + EXPECT_UNITS_EQ(-20_rad, + frc::ApplyDeadband(-20_rad, 1_rad, 20_rad)); } TEST(MathUtilTest, ApplyDeadbandLargeMaxMagnitude) { @@ -65,6 +65,72 @@ TEST(MathUtilTest, ApplyDeadbandLargeMaxMagnitude) { frc::ApplyDeadband(100.0, 20.0, std::numeric_limits::infinity())); } +TEST(MathUtilTest, ApplyDeadband2dUnityScale) { + EXPECT_EQ((Eigen::Vector2d{{0.0}, {1.0}}), + frc::ApplyDeadband(Eigen::Vector2d{{0.0}, {1.0}}, 0.02)); + EXPECT_EQ((Eigen::Vector2d{{0.0}, {-1.0}}), + frc::ApplyDeadband(Eigen::Vector2d{{0.0}, {-1.0}}, 0.02)); + EXPECT_EQ((Eigen::Vector2d{{-1.0}, {0.0}}), + frc::ApplyDeadband(Eigen::Vector2d{{-1.0}, {0.0}}, 0.02)); + + // == 0 + EXPECT_EQ((Eigen::Vector2d{{0.0}, {0.0}}), + frc::ApplyDeadband(Eigen::Vector2d{{0.0}, {0.0}}, 0.02)); + + // > 0 + EXPECT_EQ((Eigen::Vector2d{{0.0}, {0.0}}), + frc::ApplyDeadband(Eigen::Vector2d{{0.01}, {0.0}}, 0.02)); + EXPECT_EQ((Eigen::Vector2d{{0.0}, {0.0}}), + frc::ApplyDeadband(Eigen::Vector2d{{0.02}, {0.0}}, 0.02)); + EXPECT_EQ((Eigen::Vector2d{{(0.03 - 0.02) / (1.0 - 0.02)}, {0.0}}), + frc::ApplyDeadband(Eigen::Vector2d{{0.03}, {0.0}}, 0.02)); + EXPECT_EQ((Eigen::Vector2d{{1.0}, {0.0}}), + frc::ApplyDeadband(Eigen::Vector2d{{1.0}, {0.0}}, 0.02)); +} + +TEST(MathUtilTest, ApplyDeadband2dArbitraryScale) { + EXPECT_EQ((Eigen::Vector2d{{0.0}, {2.5}}), + frc::ApplyDeadband(Eigen::Vector2d{{0.0}, {2.5}}, 0.02, 2.5)); + EXPECT_EQ((Eigen::Vector2d{{0.0}, {-2.5}}), + frc::ApplyDeadband(Eigen::Vector2d{{0.0}, {-2.5}}, 0.02, 2.5)); + EXPECT_EQ((Eigen::Vector2d{{-2.5}, {0.0}}), + frc::ApplyDeadband(Eigen::Vector2d{{-2.5}, {0.0}}, 0.02, 2.5)); + + // == 0 + EXPECT_EQ((Eigen::Vector2d{{0.0}, {0.0}}), + frc::ApplyDeadband(Eigen::Vector2d{{0.0}, {0.0}}, 0.02, 2.5)); + + // > 0 + EXPECT_EQ((Eigen::Vector2d{{0.0}, {0.0}}), + frc::ApplyDeadband(Eigen::Vector2d{{0.01}, {0.0}}, 0.02, 2.5)); + EXPECT_EQ((Eigen::Vector2d{{0.0}, {0.0}}), + frc::ApplyDeadband(Eigen::Vector2d{{0.02}, {0.0}}, 0.02, 2.5)); + EXPECT_EQ((Eigen::Vector2d{{2.5}, {0.0}}), + frc::ApplyDeadband(Eigen::Vector2d{{2.5}, {0.0}}, 0.02, 2.5)); +} + +TEST(MathUtilTest, ApplyDeadband2dLargeMaxMagnitude) { + EXPECT_EQ((Eigen::Vector2d{{80.0}, {0.0}}), + (frc::ApplyDeadband(Eigen::Vector2d{{100.0}, {0.0}}, 20.0, + std::numeric_limits::infinity()))); +} + +TEST(MathUtilTest, ApplyDeadband2dUnits) { + EXPECT_EQ((Eigen::Vector{0_mps, 2.5_mps}), + frc::ApplyDeadband( + Eigen::Vector{0_mps, 2.5_mps}, + 0.02_mps, 2.5_mps)); + EXPECT_EQ((Eigen::Vector{1_mps, 0_mps}), + frc::ApplyDeadband( + Eigen::Vector{1_mps, 0_mps}, + 0.02_mps)); + + EXPECT_EQ((Eigen::Vector{0_mps, 0_mps}), + frc::ApplyDeadband( + Eigen::Vector{0_mps, 0_mps}, + 0.02_mps, 2.5_mps)); +} + TEST(MathUtilTest, CopySignPow) { EXPECT_DOUBLE_EQ(0.5, frc::CopySignPow(0.5, 1.0)); EXPECT_DOUBLE_EQ(-0.5, frc::CopySignPow(-0.5, 1.0)); @@ -104,20 +170,107 @@ TEST(MathUtilTest, CopySignPowWithMaxMagnitude) { } TEST(MathUtilTest, CopySignPowWithUnits) { - EXPECT_DOUBLE_EQ( - 0, frc::CopySignPow(0_mps, 2.0).value()); - EXPECT_DOUBLE_EQ( - 1, frc::CopySignPow(1_mps, 2.0).value()); - EXPECT_DOUBLE_EQ( - -1, frc::CopySignPow(-1_mps, 2.0).value()); + EXPECT_UNITS_EQ(0_mps, + frc::CopySignPow(0_mps, 2.0)); + EXPECT_UNITS_EQ(1_mps, + frc::CopySignPow(1_mps, 2.0)); + EXPECT_UNITS_EQ(-1_mps, + frc::CopySignPow(-1_mps, 2.0)); - EXPECT_DOUBLE_EQ( - 0.5 * 0.5 * 10, - frc::CopySignPow(5_mps, 2.0, 10_mps).value()); - EXPECT_DOUBLE_EQ( - -0.5 * 0.5 * 10, - frc::CopySignPow(-5_mps, 2.0, 10_mps) - .value()); + EXPECT_UNITS_EQ( + units::meters_per_second_t{0.5 * 0.5 * 10}, + frc::CopySignPow(5_mps, 2.0, 10_mps)); + EXPECT_UNITS_EQ( + units::meters_per_second_t{-0.5 * 0.5 * 10}, + frc::CopySignPow(-5_mps, 2.0, 10_mps)); +} + +TEST(MathUtilTest, CopyDirectionPow2d) { + EXPECT_EQ((Eigen::Vector2d{{0.5}, {0.0}}), + frc::CopyDirectionPow(Eigen::Vector2d{{0.5}, {0.0}}, 1.0)); + EXPECT_EQ((Eigen::Vector2d{{-0.5}, {0.0}}), + frc::CopyDirectionPow(Eigen::Vector2d{{-0.5}, {0.0}}, 1.0)); + + EXPECT_EQ((Eigen::Vector2d{{0.25}, {0.0}}), + frc::CopyDirectionPow(Eigen::Vector2d{{0.5}, {0.0}}, 2.0)); + EXPECT_EQ((Eigen::Vector2d{{-0.25}, {0.0}}), + frc::CopyDirectionPow(Eigen::Vector2d{{-0.5}, {0.0}}, 2.0)); + + EXPECT_EQ((Eigen::Vector2d{{std::sqrt(0.5)}, {0.0}}), + frc::CopyDirectionPow(Eigen::Vector2d{{0.5}, {0.0}}, 0.5)); + EXPECT_EQ((Eigen::Vector2d{{-std::sqrt(0.5)}, {0.0}}), + frc::CopyDirectionPow(Eigen::Vector2d{{-0.5}, {0.0}}, 0.5)); + + EXPECT_EQ((Eigen::Vector2d{{0.0}, {0.0}}), + frc::CopyDirectionPow(Eigen::Vector2d{{0.0}, {0.0}}, 2.0)); + + EXPECT_EQ((Eigen::Vector2d{{1.0}, {0.0}}), + frc::CopyDirectionPow(Eigen::Vector2d{{1.0}, {0.0}}, 2.0)); + EXPECT_EQ((Eigen::Vector2d{{-1.0}, {0.0}}), + frc::CopyDirectionPow(Eigen::Vector2d{{-1.0}, {0.0}}, 2.0)); + EXPECT_EQ((Eigen::Vector2d{{0.0}, {1.0}}), + frc::CopyDirectionPow(Eigen::Vector2d{{0.0}, {1.0}}, 2.0)); + EXPECT_EQ((Eigen::Vector2d{{0.0}, {-1.0}}), + frc::CopyDirectionPow(Eigen::Vector2d{{0.0}, {-1.0}}, 2.0)); + + EXPECT_EQ((Eigen::Vector2d{{0.0}, {std::pow(0.8, 0.3)}}), + frc::CopyDirectionPow(Eigen::Vector2d{{0.0}, {0.8}}, 0.3)); + EXPECT_EQ((Eigen::Vector2d{{0.0}, {-std::pow(0.8, 0.3)}}), + frc::CopyDirectionPow(Eigen::Vector2d{{0.0}, {-0.8}}, 0.3)); +} + +TEST(MathUtilTest, CopyDirectionPow2dMaxDistance) { + EXPECT_EQ((Eigen::Vector2d{{5.0}, {0.0}}), + frc::CopyDirectionPow(Eigen::Vector2d{{5.0}, {0.0}}, 1.0, 10.0)); + EXPECT_EQ((Eigen::Vector2d{{-5.0}, {0.0}}), + frc::CopyDirectionPow(Eigen::Vector2d{{-5.0}, {0.0}}, 1.0, 10.0)); + + EXPECT_EQ((Eigen::Vector2d{{2.5}, {0.0}}), + frc::CopyDirectionPow(Eigen::Vector2d{{5.0}, {0.0}}, 2.0, 10.0)); + EXPECT_EQ((Eigen::Vector2d{{-2.5}, {0.0}}), + frc::CopyDirectionPow(Eigen::Vector2d{{-5.0}, {0.0}}, 2.0, 10.0)); + + EXPECT_EQ((Eigen::Vector2d{{std::sqrt(0.5) * 10.0}, {0.0}}), + frc::CopyDirectionPow(Eigen::Vector2d{{5.0}, {0.0}}, 0.5, 10.0)); + EXPECT_EQ((Eigen::Vector2d{{-std::sqrt(0.5) * 10.0}, {0.0}}), + frc::CopyDirectionPow(Eigen::Vector2d{{-5.0}, {0.0}}, 0.5, 10.0)); + + EXPECT_EQ((Eigen::Vector2d{{0.0}, {0.0}}), + frc::CopyDirectionPow(Eigen::Vector2d{{0.0}, {0.0}}, 2.0, 5.0)); + EXPECT_EQ((Eigen::Vector2d{{5.0}, {0.0}}), + frc::CopyDirectionPow(Eigen::Vector2d{{5.0}, {0.0}}, 2.0, 5.0)); + EXPECT_EQ((Eigen::Vector2d{{-5.0}, {0.0}}), + frc::CopyDirectionPow(Eigen::Vector2d{{-5.0}, {0.0}}, 2.0, 5.0)); + + EXPECT_EQ((Eigen::Vector2d{{0.0}, {std::pow(0.8, 0.3) * 100.0}}), + frc::CopyDirectionPow(Eigen::Vector2d{{0.0}, {80.0}}, 0.3, 100.0)); + EXPECT_EQ((Eigen::Vector2d{{0.0}, {-std::pow(0.8, 0.3) * 100.0}}), + frc::CopyDirectionPow(Eigen::Vector2d{{0.0}, {-80.0}}, 0.3, 100.0)); +} + +TEST(MathUtilTest, CopyDirectionPow2dUnits) { + EXPECT_EQ( + (Eigen::Vector{1_mps, 0_mps}), + frc::CopyDirectionPow( + Eigen::Vector{1_mps, 0_mps}, 2.0)); + EXPECT_EQ( + (Eigen::Vector{-1_mps, 0_mps}), + frc::CopyDirectionPow( + Eigen::Vector{-1_mps, 0_mps}, 2.0)); + + EXPECT_EQ((Eigen::Vector{0_mps, 0_mps}), + frc::CopyDirectionPow( + Eigen::Vector{0_mps, 0_mps}, 2.0, + 5_mps)); + + EXPECT_EQ((Eigen::Vector{5_mps, 0_mps}), + frc::CopyDirectionPow( + Eigen::Vector{5_mps, 0_mps}, 2.0, + 5_mps)); + EXPECT_EQ((Eigen::Vector{-5_mps, 0_mps}), + frc::CopyDirectionPow( + Eigen::Vector{-5_mps, 0_mps}, + 2.0, 5_mps)); } TEST(MathUtilTest, InputModulus) {