/*
* Copyright 2018 The Cartographer Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using CartographerSharp.Transform;
using RobotNet10.Shared.Numbers;
namespace CartographerSharp.Mapping.Internal.Optimization;
///
/// Helper functions for cost function computation.
///
internal static class CostHelpers
{
///
/// Computes spherical linear interpolation of unit quaternions.
///
public static Quaternion SlerpQuaternions(Quaternion start, Quaternion end, double factor)
{
// Normalize quaternions
start = Quaternion.Normalize(start);
end = Quaternion.Normalize(end);
// Compute dot product
var cosTheta = start.W * end.W + start.X * end.X + start.Y * end.Y + start.Z * end.Z;
// Clamp to [-1, 1] to handle floating-point errors that could cause Math.Acos to return NaN
var absCosTheta = Math.Min(1.0, Math.Abs(cosTheta));
// If quaternions are nearly collinear, use linear interpolation
const double kEpsilon = 1e-6;
double prevScale = 1.0 - factor;
double nextScale = factor;
if (absCosTheta < 1.0 - kEpsilon)
{
var theta = Math.Acos(absCosTheta);
var sinTheta = Math.Sin(theta);
if (sinTheta > kEpsilon)
{
prevScale = Math.Sin((1.0 - factor) * theta) / sinTheta;
nextScale = Math.Sin(factor * theta) / sinTheta;
}
}
if (cosTheta < 0.0)
{
nextScale = -nextScale;
}
// Quaternion constructor is (x, y, z, w), matching C++ output format [w, x, y, z]
// but converting to C# Quaternion format (x, y, z, w)
var result = new Quaternion(
prevScale * start.X + nextScale * end.X,
prevScale * start.Y + nextScale * end.Y,
prevScale * start.Z + nextScale * end.Z,
prevScale * start.W + nextScale * end.W
);
// Normalize to ensure unit quaternion (Eigen SLERP automatically normalizes)
return Quaternion.Normalize(result);
}
///
/// Interpolates 3D nodes.
///
public static (Quaternion rotation, Vector3 translation) InterpolateNodes3D(
double[] prevNodeRotation, // [w, x, y, z]
double[] prevNodeTranslation, // [x, y, z]
double[] nextNodeRotation, // [w, x, y, z]
double[] nextNodeTranslation, // [x, y, z]
double interpolationParameter)
{
// Match C++: prev_node_rotation is [w, x, y, z]
// System.Numerics.Quaternion constructor is (x, y, z, w)
var prevQuaternion = new Quaternion(
prevNodeRotation[1], // x
prevNodeRotation[2], // y
prevNodeRotation[3], // z
prevNodeRotation[0] // w
);
var nextQuaternion = new Quaternion(
nextNodeRotation[1], // x
nextNodeRotation[2], // y
nextNodeRotation[3], // z
nextNodeRotation[0] // w
);
// Interpolate rotation using SLERP
var interpolatedRotation = SlerpQuaternions(prevQuaternion, nextQuaternion, interpolationParameter);
// Interpolate translation linearly
var interpolatedTranslation = new Vector3(
(prevNodeTranslation[0] + interpolationParameter * (nextNodeTranslation[0] - prevNodeTranslation[0])),
(prevNodeTranslation[1] + interpolationParameter * (nextNodeTranslation[1] - prevNodeTranslation[1])),
(prevNodeTranslation[2] + interpolationParameter * (nextNodeTranslation[2] - prevNodeTranslation[2]))
);
return (interpolatedRotation, interpolatedTranslation);
}
///
/// Interpolates 2D nodes embedded in 3D space.
///
public static (Quaternion rotation, Vector3 translation) InterpolateNodes2D(
double[] prevNodePose, // [x, y, theta]
Quaternion prevNodeGravityAlignment,
double[] nextNodePose, // [x, y, theta]
Quaternion nextNodeGravityAlignment,
double interpolationParameter)
{
// Embed 2D pose into 3D with gravity alignment
// Equivalent to: Embed3D(prev_node_pose) * Rigid3d::Rotation(prev_node_gravity_alignment)
var prevRotation2D = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, prevNodePose[2]);
var prevQuaternion = Quaternion.Normalize(prevRotation2D * prevNodeGravityAlignment);
var nextRotation2D = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, nextNodePose[2]);
var nextQuaternion = Quaternion.Normalize(nextRotation2D * nextNodeGravityAlignment);
// Interpolate rotation using SLERP
var interpolatedRotation = SlerpQuaternions(prevQuaternion, nextQuaternion, interpolationParameter);
// Interpolate translation linearly (2D, z=0)
var interpolatedTranslation = new Vector3(
(prevNodePose[0] + interpolationParameter * (nextNodePose[0] - prevNodePose[0])),
(prevNodePose[1] + interpolationParameter * (nextNodePose[1] - prevNodePose[1])),
0.0
);
return (interpolatedRotation, interpolatedTranslation);
}
///
/// Computes unscaled error for 3D poses.
/// Error = observed_relative_pose - computed_relative_pose
///
public static double[] ComputeUnscaledError3D(
Rigid3d observedRelativePose,
Quaternion startRotation,
Vector3 startTranslation,
Quaternion endRotation,
Vector3 endTranslation)
{
// Compute relative transform: start^-1 * end
var startInverse = Quaternion.Inverse(startRotation);
var deltaTranslation = endTranslation - startTranslation;
var rotatedDelta = Vector3.Transform(deltaTranslation, startInverse);
// Compute h_rotation_inverse = (end^-1) * start (matching C++ implementation)
// This is equivalent to: endRotation.Inverse() * startRotation
var endInverse = Quaternion.Inverse(endRotation);
var hRotationInverse = endInverse * startRotation;
// Error rotation: h_rotation_inverse * observed_relative_rotation
var errorRotation = hRotationInverse * observedRelativePose.Rotation;
// Convert rotation error to angle-axis
var angleAxis = TransformOperations.RotationQuaternionToAngleAxisVector(errorRotation);
return
[
observedRelativePose.Translation.X - rotatedDelta.X,
observedRelativePose.Translation.Y - rotatedDelta.Y,
observedRelativePose.Translation.Z - rotatedDelta.Z,
angleAxis.X,
angleAxis.Y,
angleAxis.Z
];
}
///
/// Scales error with translation and rotation weights.
///
public static double[] ScaleError3D(
double[] unscaledError,
double translationWeight,
double rotationWeight)
{
return
[
translationWeight * unscaledError[0],
translationWeight * unscaledError[1],
translationWeight * unscaledError[2],
rotationWeight * unscaledError[3],
rotationWeight * unscaledError[4],
rotationWeight * unscaledError[5]
];
}
}