using CartographerSharp.Transform; using RobotNet10.Shared.Numbers; namespace CartographerSharp.Mapping.Internal.Optimization; /// /// Helper utilities for optimization problems. /// Provides common operations for pose parameter conversion and angle normalization. /// public static class OptimizationHelpers { /// /// Normalizes angle difference to [-pi, pi]. /// Uses modulo-based approach for efficiency with large angles. /// /// The angle to normalize. /// Normalized angle in [-pi, pi]. public static double NormalizeAngleDifference(double angle) { // Use modulo for efficiency - handles large angles in O(1) const double twoPi = 2.0 * Math.PI; angle = angle % twoPi; if (angle > Math.PI) angle -= twoPi; else if (angle < -Math.PI) angle += twoPi; return angle; } /// /// Converts Rigid2d pose to parameter array [x, y, theta]. /// /// The 2D pose. /// Parameter array [x, y, theta]. public static double[] Rigid2dToParameters(Rigid2d pose) => [ pose.Translation.X, pose.Translation.Y, pose.Rotation ]; /// /// Converts parameter array [x, y, theta] to Rigid2d pose. /// /// Parameter array [x, y, theta]. /// The 2D pose. public static Rigid2d ParametersToRigid2d(double[] parameters) { if (parameters == null || parameters.Length < 3) throw new ArgumentException("Parameters array must have at least 3 elements", nameof(parameters)); return new Rigid2d( new Vector2(parameters[0], parameters[1]), parameters[2] ); } /// /// Converts Rigid3d pose to parameter arrays (rotation and translation). /// /// The 3D pose. /// Tuple of (rotation[4], translation[3]). public static (double[] rotation, double[] translation) Rigid3dToParameters(Rigid3d pose) { var rotation = new double[4] { pose.Rotation.W, pose.Rotation.X, pose.Rotation.Y, pose.Rotation.Z }; var translation = new double[3] { pose.Translation.X, pose.Translation.Y, pose.Translation.Z }; return (rotation, translation); } /// /// Converts parameter arrays to Rigid3d pose. /// /// Rotation parameters [w, x, y, z]. /// Translation parameters [x, y, z]. /// The 3D pose. public static Rigid3d ParametersToRigid3d(double[] rotation, double[] translation) { if (rotation == null || rotation.Length < 4) throw new ArgumentException("Rotation array must have at least 4 elements", nameof(rotation)); if (translation == null || translation.Length < 3) throw new ArgumentException("Translation array must have at least 3 elements", nameof(translation)); // Convert from [w, x, y, z] to (x, y, z, w) for System.Numerics.Quaternion return new Rigid3d( new Vector3(translation[0], translation[1], translation[2]), new Quaternion(rotation[1], rotation[2], rotation[3], rotation[0]) ); } /// /// Converts Quaternion to parameter array [w, x, y, z]. /// /// The quaternion. /// Parameter array [w, x, y, z]. public static double[] QuaternionToParameters(Quaternion quaternion) => [ quaternion.W, quaternion.X, quaternion.Y, quaternion.Z ]; /// /// Converts parameter array [w, x, y, z] to System.Numerics.Quaternion (x, y, z, w). /// Match C++: Eigen::Quaternion uses (w, x, y, z) format. /// System.Numerics.Quaternion uses (x, y, z, w) format. /// /// Parameter array [w, x, y, z]. /// The quaternion. public static Quaternion ParametersToQuaternion(double[] parameters) { if (parameters == null || parameters.Length < 4) throw new ArgumentException("Parameters array must have at least 4 elements", nameof(parameters)); // Convert from [w, x, y, z] to (x, y, z, w) return new Quaternion( parameters[1], // x parameters[2], // y parameters[3], // z parameters[0] // w ); } /// /// Converts Vector3 to parameter array [x, y, z]. /// /// The vector. /// Parameter array [x, y, z]. public static double[] Vector3ToParameters(Vector3 vector) => [ vector.X, vector.Y, vector.Z ]; /// /// Converts parameter array [x, y, z] to Vector3. /// /// Parameter array [x, y, z]. /// The vector. public static Vector3 ParametersToVector3(double[] parameters) { if (parameters == null || parameters.Length < 3) throw new ArgumentException("Parameters array must have at least 3 elements", nameof(parameters)); return new Vector3( parameters[0], parameters[1], parameters[2] ); } /// /// Computes interpolation parameter for time-based interpolation. /// /// The observation time. /// The previous node time. /// The next node time. /// Interpolation parameter in [0, 1]. public static double ComputeInterpolationParameter(long observationTime, long prevTime, long nextTime) { var timeDiff = nextTime - prevTime; if (timeDiff == 0) return 0.0; // Cast to double to avoid integer division return (double)(observationTime - prevTime) / timeDiff; } }