mod engine;
mod session;
pub use session::FrameSessionMetrics;
pub use session::{FrameSession, FrameSessionConfig};
#[cfg(test)]
pub(crate) use engine::{ColorDelayLine, PresentationEngine};
use crate::types::LaserPoint;
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputResetReason {
SessionStart,
Reconnect,
Arm,
Disarm,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PresentedSliceKind {
FifoChunk,
FrameSwapFrame,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OutputFilterContext {
pub pps: u32,
pub kind: PresentedSliceKind,
pub is_cyclic: bool,
}
pub trait OutputFilter: Send + 'static {
fn reset(&mut self, _reason: OutputResetReason) {}
fn filter(&mut self, points: &mut [LaserPoint], ctx: &OutputFilterContext);
}
#[derive(Clone, Debug)]
pub struct Frame {
points: Arc<Vec<LaserPoint>>,
}
impl Frame {
pub fn new(points: Vec<LaserPoint>) -> Self {
Self {
points: Arc::new(points),
}
}
pub fn points(&self) -> &[LaserPoint] {
&self.points
}
pub fn first_point(&self) -> Option<&LaserPoint> {
self.points.first()
}
pub fn last_point(&self) -> Option<&LaserPoint> {
self.points.last()
}
pub fn len(&self) -> usize {
self.points.len()
}
pub fn is_empty(&self) -> bool {
self.points.is_empty()
}
}
impl From<Vec<LaserPoint>> for Frame {
fn from(points: Vec<LaserPoint>) -> Self {
Self::new(points)
}
}
#[derive(Clone, Debug)]
pub enum TransitionPlan {
Transition(Vec<LaserPoint>),
Coalesce,
}
pub type TransitionFn = Box<dyn Fn(&LaserPoint, &LaserPoint) -> TransitionPlan + Send>;
const END_DWELL_US: f64 = 100.0;
const START_DWELL_US: f64 = 400.0;
pub fn default_transition(pps: u32) -> TransitionFn {
let end_dwell = (END_DWELL_US * pps as f64 / 1_000_000.0).round() as usize;
let start_dwell = (START_DWELL_US * pps as f64 / 1_000_000.0).round() as usize;
Box::new(move |from: &LaserPoint, to: &LaserPoint| {
let dx = to.x - from.x;
let dy = to.y - from.y;
let d_inf = dx.abs().max(dy.abs());
let transit = (32.0 * d_inf).ceil().clamp(0.0, 64.0) as usize;
let total = end_dwell + transit + start_dwell;
let mut points = Vec::with_capacity(total);
for _ in 0..end_dwell {
points.push(LaserPoint::blanked(from.x, from.y));
}
for i in 0..transit {
let t = (i as f32 + 1.0) / (transit as f32 + 1.0);
let t = quintic_ease_in_out(t);
points.push(LaserPoint::blanked(from.x + dx * t, from.y + dy * t));
}
for _ in 0..start_dwell {
points.push(LaserPoint::blanked(to.x, to.y));
}
TransitionPlan::Transition(points)
})
}
fn quintic_ease_in_out(t: f32) -> f32 {
if t < 0.5 {
16.0 * t * t * t * t * t
} else {
let u = 2.0 * t - 2.0;
0.5 * u * u * u * u * u + 1.0
}
}
#[cfg(test)]
mod tests;