use serde::{Deserialize, Serialize};
use super::PortableError;
use super::timeline::{Interp, Target, Track};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Grid {
pub target: Target,
pub times: Vec<f64>,
pub positions: Vec<f64>,
pub values: Vec<f64>,
pub stride: usize,
pub interp: Interp,
}
impl Grid {
pub fn new(
target: Target,
times: Vec<f64>,
positions: Vec<f64>,
frames: Vec<Vec<Vec<f64>>>,
interp: Interp,
) -> Result<Grid, PortableError> {
for (name, axis) in [("times", ×), ("positions", &positions)] {
if axis.is_empty() {
return Err(PortableError::Unsupported(format!(
"a grid needs at least one keyframe on its {name} axis"
)));
}
if !axis.windows(2).all(|w| w[0] < w[1]) {
return Err(PortableError::Unsupported(format!(
"grid {name} must be strictly increasing"
)));
}
}
if frames.len() != times.len() {
return Err(PortableError::Unsupported(format!(
"grid has {} time keyframes but {} rows of frames",
times.len(),
frames.len()
)));
}
let stride = frames[0].first().map_or(0, Vec::len);
let mut values = Vec::with_capacity(times.len() * positions.len() * stride);
for (i, row) in frames.iter().enumerate() {
if row.len() != positions.len() {
return Err(PortableError::Unsupported(format!(
"grid row {i} has {} frames but there are {} positions",
row.len(),
positions.len()
)));
}
for (j, frame) in row.iter().enumerate() {
if frame.len() != stride {
return Err(PortableError::Unsupported(format!(
"grid frame ({i}, {j}) has {} values but frame (0, 0) has \
{stride}; every frame of a grid must be the same width",
frame.len()
)));
}
values.extend_from_slice(frame);
}
}
Ok(Grid {
target,
times,
positions,
values,
stride,
interp,
})
}
pub(crate) fn validate(&self) -> Result<(), String> {
for (name, axis) in [("times", &self.times), ("positions", &self.positions)] {
if axis.is_empty() {
return Err(format!("has an empty {name} axis"));
}
if !axis.windows(2).all(|w| w[0] < w[1]) {
return Err(format!("{name} are not strictly increasing"));
}
}
if self
.times
.len()
.checked_mul(self.positions.len())
.and_then(|n| n.checked_mul(self.stride))
!= Some(self.values.len())
{
return Err(format!(
"declares a {} x {} lattice of stride {} but carries {} values",
self.times.len(),
self.positions.len(),
self.stride,
self.values.len()
));
}
Ok(())
}
#[must_use]
pub fn sample(&self, t: f64, p: f64) -> Vec<f64> {
let (ti, tj, tu) = bracket(&self.times, t, self.interp);
let (pi, pj, pu) = bracket(&self.positions, p, self.interp);
let cols = self.positions.len();
let frame = |i: usize, j: usize| {
let start = (i * cols + j) * self.stride;
&self.values[start..start + self.stride]
};
let (a, b, c, d) = (frame(ti, pi), frame(ti, pj), frame(tj, pi), frame(tj, pj));
(0..self.stride)
.map(|k| {
let lo = a[k] + (b[k] - a[k]) * pu;
let hi = c[k] + (d[k] - c[k]) * pu;
lo + (hi - lo) * tu
})
.collect()
}
}
fn bracket(axis: &[f64], x: f64, interp: Interp) -> (usize, usize, f64) {
let last = axis.len() - 1;
if x <= axis[0] {
return (0, 0, 0.0);
}
if x >= axis[last] {
return (last, last, 0.0);
}
let hi = axis.partition_point(|&k| k <= x);
let lo = hi - 1;
if interp == Interp::Step {
return (lo, lo, 0.0);
}
let span = axis[hi] - axis[lo];
let u = if span > 0.0 {
(x - axis[lo]) / span
} else {
0.0
};
(lo, hi, u)
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Control {
pub label: String,
pub min: f64,
pub max: f64,
pub default: f64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub step: Option<f64>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tracks: Vec<Track>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub grids: Vec<Grid>,
}
impl Control {
pub fn new(label: &str, min: f64, max: f64, default: f64) -> Result<Control, PortableError> {
if !(min.is_finite() && max.is_finite() && default.is_finite()) || min >= max {
return Err(PortableError::Unsupported(format!(
"control \"{label}\" needs a finite increasing range, got [{min}, {max}]"
)));
}
if !(min..=max).contains(&default) {
return Err(PortableError::Unsupported(format!(
"control \"{label}\" defaults to {default}, outside [{min}, {max}]"
)));
}
Ok(Control {
label: label.to_string(),
min,
max,
default,
step: None,
tracks: Vec::new(),
grids: Vec::new(),
})
}
pub fn with_step(mut self, step: f64) -> Result<Control, PortableError> {
if !(step.is_finite() && step > 0.0) {
return Err(PortableError::Unsupported(format!(
"control \"{}\" needs a positive finite step, got {step}",
self.label
)));
}
self.step = Some(step);
Ok(self)
}
pub fn push_track(&mut self, track: Track) -> &mut Self {
self.tracks.push(track);
self
}
pub fn push_grid(&mut self, grid: Grid) -> &mut Self {
self.grids.push(grid);
self
}
pub(crate) fn validate(&self) -> Result<(), String> {
if !(self.min.is_finite() && self.max.is_finite() && self.default.is_finite())
|| self.min >= self.max
|| !(self.min..=self.max).contains(&self.default)
{
return Err(format!(
"range [{}, {}] with default {} is not finite, increasing, and containing",
self.min, self.max, self.default
));
}
if let Some(step) = self.step.filter(|step| !(step.is_finite() && *step > 0.0)) {
return Err(format!("step {step} is not finite and positive"));
}
for (i, track) in self.tracks.iter().enumerate() {
track.validate().map_err(|e| format!("track {i} {e}"))?;
}
for (i, grid) in self.grids.iter().enumerate() {
grid.validate().map_err(|e| format!("grid {i} {e}"))?;
}
Ok(())
}
#[must_use]
pub fn normalize(&self, value: f64) -> f64 {
if !value.is_finite() {
return self.default;
}
let clamped = value.clamp(self.min, self.max);
match self.step {
Some(step) => {
(self.min + ((clamped - self.min) / step).round() * step).clamp(self.min, self.max)
}
None => clamped,
}
}
}