using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using MyParking.Shared;
namespace MultiWheelC.Trajectory
{
///
/// 保存一条经过基本合法性检查的只读二维参考轨迹。
///
public sealed class Trajectory2D
{
private const double StartArcLengthToleranceMeters = 1e-9;
private const double MinimumSegmentLengthMeters = 1e-6;
private readonly TrajectoryPoint[] _points;
private readonly ReadOnlyCollection _readOnlyPoints;
///
/// 复制并验证按累计弧长升序排列的参考轨迹点。
///
public Trajectory2D(
IEnumerable points)
{
if (points == null)
{
throw new ArgumentNullException(
nameof(points));
}
_points = points.ToArray();
if (_points.Length < 2)
{
throw new ArgumentException(
"二维轨迹至少需要两个轨迹点。",
nameof(points));
}
if (Math.Abs(_points[0].ArcLengthMeters) >
StartArcLengthToleranceMeters)
{
throw new ArgumentException(
"二维轨迹起点的累计弧长必须为0m。",
nameof(points));
}
for (var index = 1;
index < _points.Length;
index++)
{
ValidateSegment(
_points[index - 1],
_points[index],
index,
nameof(points));
}
_readOnlyPoints =
Array.AsReadOnly(_points);
}
///
/// 获取轨迹点数量。
///
public int Count => _points.Length;
///
/// 获取指定索引处的轨迹点。
///
public TrajectoryPoint this[int index] =>
_points[index];
///
/// 获取不可修改的有序轨迹点集合。
///
public IReadOnlyList Points =>
_readOnlyPoints;
///
/// 获取轨迹起点。
///
public TrajectoryPoint StartPoint =>
_points[0];
///
/// 获取轨迹终点。
///
public TrajectoryPoint EndPoint =>
_points[_points.Length - 1];
///
/// 获取轨迹总弧长,单位为m。
///
public double TotalLengthMeters =>
EndPoint.ArcLengthMeters;
///
/// 根据当前累计弧长计算到轨迹终点的剩余距离。
///
public double GetRemainingDistanceMeters(
double arcLengthMeters)
{
EnsureFinite(
arcLengthMeters,
nameof(arcLengthMeters));
if (arcLengthMeters <= 0.0)
return TotalLengthMeters;
if (arcLengthMeters >= TotalLengthMeters)
return 0.0;
return TotalLengthMeters - arcLengthMeters;
}
///
/// 按累计弧长在线性位置、航向、曲率和参考速度之间插值得到轨迹点。
///
public TrajectoryPoint SampleAtArcLength(
double arcLengthMeters)
{
EnsureFinite(
arcLengthMeters,
nameof(arcLengthMeters));
if (arcLengthMeters <= 0.0)
{
return StartPoint;
}
if (arcLengthMeters >= TotalLengthMeters)
{
return EndPoint;
}
var segmentStartIndex =
FindSegmentStartIndex(
arcLengthMeters);
var segmentStart =
_points[segmentStartIndex];
var segmentEnd =
_points[segmentStartIndex + 1];
var interpolationRatio =
(arcLengthMeters -
segmentStart.ArcLengthMeters) /
(segmentEnd.ArcLengthMeters -
segmentStart.ArcLengthMeters);
return new TrajectoryPoint(
arcLengthMeters,
new Pose2D(
InterpolationMath.Lerp(
segmentStart.PoseInWorld.XMeters,
segmentEnd.PoseInWorld.XMeters,
interpolationRatio),
InterpolationMath.Lerp(
segmentStart.PoseInWorld.YMeters,
segmentEnd.PoseInWorld.YMeters,
interpolationRatio),
AngleMath.LerpRadians(
segmentStart.PoseInWorld.YawRadians,
segmentEnd.PoseInWorld.YawRadians,
interpolationRatio)),
InterpolationMath.Lerp(
segmentStart.CurvaturePerMeter,
segmentEnd.CurvaturePerMeter,
interpolationRatio),
InterpolationMath.Lerp(
segmentStart.ReferenceSpeedMetersPerSecond,
segmentEnd.ReferenceSpeedMetersPerSecond,
interpolationRatio));
}
///
/// 使用二分查找获取包含指定累计弧长的线段起点索引。
///
private int FindSegmentStartIndex(
double arcLengthMeters)
{
var lowerIndex = 0;
var upperIndex = _points.Length - 1;
while (upperIndex - lowerIndex > 1)
{
var middleIndex =
lowerIndex +
(upperIndex - lowerIndex) / 2;
if (_points[middleIndex].ArcLengthMeters <=
arcLengthMeters)
{
lowerIndex = middleIndex;
}
else
{
upperIndex = middleIndex;
}
}
return lowerIndex;
}
///
/// 检查相邻轨迹点是否构成有效的非零长度有序线段。
///
private static void ValidateSegment(
TrajectoryPoint previous,
TrajectoryPoint current,
int currentIndex,
string parameterName)
{
if (current.ArcLengthMeters <=
previous.ArcLengthMeters)
{
throw new ArgumentException(
$"轨迹点{currentIndex}的累计弧长必须严格大于前一个点。",
parameterName);
}
var deltaX =
current.PoseInWorld.XMeters -
previous.PoseInWorld.XMeters;
var deltaY =
current.PoseInWorld.YMeters -
previous.PoseInWorld.YMeters;
var segmentLengthSquared =
deltaX * deltaX +
deltaY * deltaY;
var minimumLengthSquared =
MinimumSegmentLengthMeters *
MinimumSegmentLengthMeters;
if (segmentLengthSquared <
minimumLengthSquared)
{
throw new ArgumentException(
$"轨迹点{currentIndex}与前一个点的位置过近,无法构成有效投影线段。",
parameterName);
}
}
///
/// 检查数值是否为有限值。
///
private static void EnsureFinite(
double value,
string parameterName)
{
if (double.IsNaN(value) ||
double.IsInfinity(value))
{
throw new ArgumentOutOfRangeException(
parameterName,
"轨迹弧长必须是有限值。");
}
}
}
}