// Copyright (c) FIRST and other WPILib contributors. // Open Source Software; you can modify and/or share it under the terms of // the WPILib BSD license file in the root directory of this project. #pragma once #include "wpi/units/angle.hpp" #include "wpi/units/length.hpp" #include "wpi/units/math.hpp" #include "wpi/util/SymbolExports.hpp" namespace wpi::math { class Transform3d; /** * A change in distance along a 3D arc since the last pose update. We can use * ideas from differential calculus to create new Pose3ds from a Twist3d and * vice versa. * * A Twist can be used to represent a difference between two poses. */ struct WPILIB_DLLEXPORT Twist3d final { /** * Linear "dx" component */ wpi::units::meter_t dx = 0_m; /** * Linear "dy" component */ wpi::units::meter_t dy = 0_m; /** * Linear "dz" component */ wpi::units::meter_t dz = 0_m; /** * Rotation vector x component. */ wpi::units::radian_t rx = 0_rad; /** * Rotation vector y component. */ wpi::units::radian_t ry = 0_rad; /** * Rotation vector z component. */ wpi::units::radian_t rz = 0_rad; /** * Obtain a new Transform3d from a (constant curvature) velocity. * * See "https://file.tavsys.net/control/controls-engineering-in-frc.pdf" * Controls Engineering in the FIRST Robotics Competition section 10.2 * "Pose exponential" for a derivation. * * The twist is a change in pose in the robot's coordinate frame since the * previous pose update. When the user runs Exp() on the twist, the user will * receive the pose delta. * * "Exp" represents the pose exponential, which is solving a differential * equation moving the pose forward in time. * * @return The pose delta of the robot. */ constexpr Transform3d Exp() const; /** * Checks equality between this Twist3d and another object. * * @param other The other object. * @return Whether the two objects are equal. */ constexpr bool operator==(const Twist3d& other) const { return wpi::units::math::abs(dx - other.dx) < 1E-9_m && wpi::units::math::abs(dy - other.dy) < 1E-9_m && wpi::units::math::abs(dz - other.dz) < 1E-9_m && wpi::units::math::abs(rx - other.rx) < 1E-9_rad && wpi::units::math::abs(ry - other.ry) < 1E-9_rad && wpi::units::math::abs(rz - other.rz) < 1E-9_rad; } /** * Scale this by a given factor. * * @param factor The factor by which to scale. * @return The scaled Twist3d. */ constexpr Twist3d operator*(double factor) const { return Twist3d{dx * factor, dy * factor, dz * factor, rx * factor, ry * factor, rz * factor}; } }; } // namespace wpi::math #include "wpi/math/geometry/Transform3d.hpp" #include "wpi/math/geometry/detail/RotationVectorToMatrix.hpp" namespace wpi::math { constexpr Transform3d Twist3d::Exp() const { // Implementation from Section 3.2 of https://ethaneade.org/lie.pdf auto impl = [this]() -> Transform3d { Vector3d u{{dx.value(), dy.value(), dz.value()}}; Vector3d rvec{{rx.value(), ry.value(), rz.value()}}; Matrix3d omega = detail::RotationVectorToMatrix(rvec); Matrix3d omegaSq = omega * omega; double theta = rvec.norm(); double thetaSq = theta * theta; double A; double B; double C; if (gcem::abs(theta) < 1E-7) { // Taylor Expansions around θ = 0 // A = 1/1! - θ²/3! + θ⁴/5! // B = 1/2! - θ²/4! + θ⁴/6! // C = 1/3! - θ²/5! + θ⁴/7! // sources: // A: // https://www.wolframalpha.com/input?i2d=true&i=series+expansion+of+Divide%5Bsin%5C%2840%29x%5C%2841%29%2Cx%5D+at+x%3D0 // B: // 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 // C: // 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 A = 1 - thetaSq / 6 + thetaSq * thetaSq / 120; B = 1 / 2.0 - thetaSq / 24 + thetaSq * thetaSq / 720; C = 1 / 6.0 - thetaSq / 120 + thetaSq * thetaSq / 5040; } else { // A = std::sin(θ)/θ // B = (1 - std::cos(θ)) / θ² // C = (1 - A) / θ² A = gcem::sin(theta) / theta; B = (1 - gcem::cos(theta)) / thetaSq; C = (1 - A) / thetaSq; } Matrix3d R = Matrix3d::Identity() + A * omega + B * omegaSq; Matrix3d V = Matrix3d::Identity() + B * omega + C * omegaSq; Vector3d translation_component = V * u; const Transform3d transform{ Translation3d{wpi::units::meter_t{translation_component(0)}, wpi::units::meter_t{translation_component(1)}, wpi::units::meter_t{translation_component(2)}}, Rotation3d{R}}; return transform; }; if (std::is_constant_evaluated()) { return impl.template operator()(); } return impl.template operator()(); } } // namespace wpi::math #include "wpi/math/geometry/proto/Twist3dProto.hpp" #include "wpi/math/geometry/struct/Twist3dStruct.hpp"