using Flyshot.Core.Domain;
using Flyshot.Core.Planning.Kinematics;
namespace Flyshot.Core.Planning.Sampling;
///
/// 从规划轨迹生成稠密关节/笛卡尔采样序列。
///
/// 为什么采样周期默认 0.016s?
/// ---
/// 真机伺服周期是 8ms(125Hz),但规划层通常以 16ms(约 60Hz)输出到上层。
/// 0.016 是一个经验默认值,与真机观测到的稠密轨迹间隔一致。
///
public static class TrajectorySampler
{
///
/// 生成稠密关节轨迹采样,每行格式:[time, j1, j2, ..., jN],保留 6 位小数。
///
public static IReadOnlyList> SampleJointTrajectory(
PlannedTrajectory trajectory,
double samplePeriod = 0.016,
int decimals = 6,
bool smoothStartStop = false)
{
var spline = RebuildSpline(trajectory);
double duration = trajectory.WaypointTimes[^1];
var times = GenerateSampleTimes(duration, samplePeriod);
var result = new List>(times.Count);
foreach (var t in times)
{
var evaluationTime = smoothStartStop
? MapSmoothStartStopEvaluationTime(t, duration)
: t;
var pos = spline.Evaluate(evaluationTime);
var row = new List(pos.Length + 1);
row.Add(Math.Round(t, decimals));
foreach (var value in pos)
{
row.Add(Math.Round(value, decimals));
}
result.Add(row);
}
return result;
}
///
/// 生成稠密笛卡尔轨迹采样,每行格式:[time, x, y, z, qx, qy, qz, qw],保留 6 位小数。
///
public static IReadOnlyList> SampleCartesianTrajectory(
PlannedTrajectory trajectory,
RobotKinematicsModel kinematicsModel,
double samplePeriod = 0.016,
int decimals = 6,
bool smoothStartStop = false)
{
var spline = RebuildSpline(trajectory);
double duration = trajectory.WaypointTimes[^1];
var times = GenerateSampleTimes(duration, samplePeriod);
var result = new List>(times.Count);
foreach (var t in times)
{
var evaluationTime = smoothStartStop
? MapSmoothStartStopEvaluationTime(t, duration)
: t;
var jointPos = spline.Evaluate(evaluationTime);
var pose = RobotKinematics.ForwardKinematics(kinematicsModel, jointPos);
var row = new List(pose.Length + 1);
row.Add(Math.Round(t, decimals));
foreach (var value in pose)
{
row.Add(Math.Round(value, decimals));
}
result.Add(row);
}
return result;
}
///
/// 从规划轨迹重建 CubicSplineInterpolator 用于稠密采样。
///
private static CubicSplineInterpolator RebuildSpline(PlannedTrajectory trajectory)
{
var times = trajectory.WaypointTimes.ToArray();
var values = trajectory.PlannedWaypoints.Select(wp => wp.Positions.ToArray()).ToArray();
return new CubicSplineInterpolator(times, values);
}
///
/// 生成采样时间点,强制包含终点。
///
private static IReadOnlyList GenerateSampleTimes(double duration, double samplePeriod)
{
var times = new List();
double t = 0.0;
while (t < duration - 1e-12)
{
times.Add(t);
t += samplePeriod;
}
if (times.Count == 0 || Math.Abs(times[^1] - duration) > 1e-12)
{
times.Add(duration);
}
return times;
}
///
/// 把线性采样时间映射为整段平滑起停的评估时间。
/// 使用 7 次 smootherstep 时间律,让起点和终点的一到三阶导都自然收敛到 0。
///
private static double MapSmoothStartStopEvaluationTime(double sampleTime, double duration)
{
if (duration <= 0.0)
{
return 0.0;
}
var normalizedTime = Math.Clamp(sampleTime / duration, 0.0, 1.0);
var u2 = normalizedTime * normalizedTime;
var u3 = u2 * normalizedTime;
var u4 = u3 * normalizedTime;
var u5 = u4 * normalizedTime;
var u6 = u5 * normalizedTime;
var u7 = u6 * normalizedTime;
var progress = (35.0 * u4) - (84.0 * u5) + (70.0 * u6) - (20.0 * u7);
return duration * progress;
}
}