// Multi-axis motion: every axis has its own duration, and the shortest ones are
// slowed so that all of them finish together. A coordinated move that let one
// axis arrive early would draw a different path than the one that was planned.
///|
/// Several single-axis profiles run together.
///
/// Axes are slowed by sampling them on a stretched clock: axis `i` is sampled at
/// `t * duration_i / duration`, and its velocity and acceleration are scaled by
/// the same factor and its square. The path is preserved, the timing is not.
pub(all) struct Synced {
axes : Array[&@contract.Trajectory]
duration : Double
}
///|
/// A synchronised set from axes that already have a duration.
pub fn Synced::new(
axes~ : Array[&@contract.Trajectory],
) -> Synced raise @linear.Refused {
if axes.length() == 0 {
raise @linear.Refused::Empty
}
let mut longest = 0.0
for i in 0.. longest {
longest = duration
}
}
if longest <= 0.0 {
raise @linear.Refused::Unreachable
}
{ axes, duration: longest, }
}
///|
/// The stretch factor of axis `index`.
fn Synced::stretch(self : Synced, index : Int32) -> Double {
self.axes[index].duration() / self.duration
}
///|
/// One axis sampled on the common clock, with its derivatives scaled back to the
/// axis's own time.
pub fn Synced::axis_sample(
self : Synced,
index : Int32,
t : Double,
) -> @contract.Sample {
let factor = self.stretch(index)
let sample = self.axes[index].sample(t * factor)
@contract.Sample::new(
position=sample.position,
velocity=sample.velocity * factor,
acceleration=sample.acceleration * factor * factor,
jerk=sample.jerk * factor * factor * factor,
)
}
///|
/// Every axis sampled at the same instant.
pub fn Synced::sample_all(self : Synced, t : Double) -> Array[@contract.Sample] {
let out = Array::make(self.axes.length(), @contract.Sample::still(0.0))
for i in 0..