using RobotNet10.Shared.Geometry;
using RobotNet10.Shared.Numbers;
namespace RobotNet10.RobotApp.SLAM.Cartographer.Mcl;
///
/// Converts point cloud (x,y) to MCL scan format (angle_min, angle_max, angle_increment, range_min, range_max, ranges[]).
/// Used when feeding TimedPointCloudData to MCL (bin by angle, take min range per bin).
///
public static class MclScanHelper
{
///
/// Transforms points from base_link frame to sensor (lidar) frame.
/// baseFromLidar: p_base = R(yaw)*p_lidar + (tx, ty). So p_lidar = R(-yaw)*(p_base - (tx, ty)).
///
/// Points (x, y) in base_link frame
/// X of lidar origin in base_link (baseFromLidar translation)
/// Y of lidar origin in base_link
/// Yaw of lidar frame in base_link (radians)
/// Points (x, y) in sensor/lidar frame
public static IEnumerable<(double x, double y)> BaseToSensorFrame(
IEnumerable<(double x, double y)> pointsBase,
double tx, double ty, double yawRad)
{
double c = Math.Cos(-yawRad);
double s = Math.Sin(-yawRad);
foreach (var (xb, yb) in pointsBase)
{
double dx = xb - tx;
double dy = yb - ty;
yield return (c * dx - s * dy, s * dx + c * dy);
}
}
///
/// Bins points by angle and returns ranges (min range per bin). Angles in radians; ranges in meters.
/// Points should be in sensor (lidar) frame so that angles are relative to sensor (required by MCL likelihood).
///
public static (double angleMin, double angleMax, double angleIncrement, double rangeMin, double rangeMax, double[] ranges) ConvertToScan(
IEnumerable points,
double angleMinDeg,
double angleMaxDeg,
int numBins,
double rangeMin,
double rangeMax)
{
double angleMin = angleMinDeg * Math.PI / 180.0;
double angleMax = angleMaxDeg * Math.PI / 180.0;
double angleIncrement = (angleMax - angleMin) / Math.Max(1, numBins);
var bins = new List[numBins];
for (int i = 0; i < numBins; i++)
bins[i] = [];
foreach (var point in points)
{
var range = Math.Sqrt(point.X * point.X + point.Y * point.Y);
if (range < 1e-6) continue;
var angle = Math.Atan2(point.Y, point.X);
int bin = (int)Math.Floor((angle - angleMin) / angleIncrement);
if (bin < 0) bin = 0;
if (bin >= numBins) bin = numBins - 1;
bins[bin].Add(range);
}
var ranges = new double[numBins];
for (int i = 0; i < numBins; i++)
{
if (bins[i].Count == 0)
ranges[i] = rangeMax;
else
ranges[i] = bins[i].Min();
}
return (angleMin, angleMax, angleIncrement, rangeMin, rangeMax, ranges);
}
}