use std::collections::BTreeMap;
use std::f64::consts::TAU;
use std::ops::Range;
use kurbo::{Affine, Point, Vec2};
use crate::Interpolate;
use crate::color::{Color, WHITE};
use crate::geom::VPath;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct MobjectId(pub u32);
pub type SceneState = BTreeMap<MobjectId, VState>;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Stroke {
pub color: Color,
pub width: f64,
}
#[derive(Debug, Clone, PartialEq)]
pub struct VState {
pub path: VPath,
pub fill: Color,
pub stroke: Stroke,
pub opacity: f32,
pub z_index: i32,
pub draw_range: Range<f32>,
}
impl VState {
pub fn new(path: VPath) -> Self {
Self {
path,
fill: Color::TRANSPARENT,
stroke: Stroke {
color: WHITE,
width: 0.04,
},
opacity: 1.0,
z_index: 0,
draw_range: 0.0..1.0,
}
}
pub fn circle(radius: f64) -> Self {
Self::new(VPath::arc(radius, 0.0, TAU))
}
pub fn arc(radius: f64, start: f64, sweep: f64) -> Self {
Self::new(VPath::arc(radius, start, sweep))
}
pub fn rectangle(width: f64, height: f64) -> Self {
let (w, h) = (width / 2.0, height / 2.0);
let pts = [(w, h), (-w, h), (-w, -h), (w, -h)].map(Point::from);
Self::new(VPath::polyline(&pts, true))
}
pub fn square(side: f64) -> Self {
Self::rectangle(side, side)
}
pub fn polygon(points: &[Point]) -> Self {
Self::new(VPath::polyline(points, true))
}
pub fn line(a: Point, b: Point) -> Self {
Self::new(VPath::polyline(&[a, b], false))
}
pub fn function_graph(f: impl Fn(f64) -> f64, x_range: Range<f64>, segments: usize) -> Self {
let n = segments.max(1);
let h = (x_range.end - x_range.start) / n as f64;
let slope = |x: f64| (f(x + h * 1e-3) - f(x - h * 1e-3)) / (h * 2e-3);
let segs = (0..n)
.map(|i| {
let (x0, x1) = (
x_range.start + h * i as f64,
x_range.start + h * (i + 1) as f64,
);
let (p0, p3) = (Point::new(x0, f(x0)), Point::new(x1, f(x1)));
let (d0, d1) = (Vec2::new(h, h * slope(x0)), Vec2::new(h, h * slope(x1)));
kurbo::CubicBez::new(p0, p0 + d0 / 3.0, p3 - d1 / 3.0, p3)
})
.collect();
Self::new(VPath {
subpaths: vec![crate::geom::SubPath {
segments: segs,
closed: false,
}],
})
}
pub fn dot(p: Point) -> Self {
Self::circle(0.08).fill(WHITE).shift(p.to_vec2())
}
pub fn fill(self, fill: Color) -> Self {
Self { fill, ..self }
}
pub fn stroke(self, color: Color, width: f64) -> Self {
Self {
stroke: Stroke { color, width },
..self
}
}
pub fn z_index(self, z_index: i32) -> Self {
Self { z_index, ..self }
}
pub fn transform(self, a: Affine) -> Self {
Self {
path: self.path.transform(a),
..self
}
}
pub fn shift(self, v: Vec2) -> Self {
self.transform(Affine::translate(v))
}
pub fn move_to(self, p: Point) -> Self {
let c = self.path.center();
self.shift(p - c)
}
pub fn scale(self, factor: f64) -> Self {
let c = self.path.center();
self.transform(Affine::scale_about(factor, c))
}
pub fn rotate(self, angle: f64) -> Self {
let c = self.path.center();
self.transform(Affine::rotate_about(angle, c))
}
}
impl crate::position::Position for VState {
fn bbox(&self) -> Option<kurbo::Rect> {
self.path.bbox()
}
fn transform(self, a: Affine) -> Self {
VState::transform(self, a)
}
}
impl Interpolate for f32 {
fn lerp(a: &Self, b: &Self, t: f32) -> Self {
a + (b - a) * t
}
}
impl Interpolate for VPath {
fn lerp(a: &Self, b: &Self, t: f32) -> Self {
assert_eq!(
a.subpaths.len(),
b.subpaths.len(),
"lerp of unaligned paths"
);
let t64 = f64::from(t);
let subpaths = a
.subpaths
.iter()
.zip(&b.subpaths)
.map(|(sa, sb)| {
assert_eq!(
sa.segments.len(),
sb.segments.len(),
"lerp of unaligned paths"
);
let segments = sa
.segments
.iter()
.zip(&sb.segments)
.map(|(x, y)| {
kurbo::CubicBez::new(
x.p0.lerp(y.p0, t64),
x.p1.lerp(y.p1, t64),
x.p2.lerp(y.p2, t64),
x.p3.lerp(y.p3, t64),
)
})
.collect();
let closed = if t <= 0.0 {
sa.closed
} else if t >= 1.0 {
sb.closed
} else {
sa.closed && sb.closed
};
crate::geom::SubPath { segments, closed }
})
.collect();
VPath { subpaths }
}
}
impl Interpolate for VState {
fn lerp(a: &Self, b: &Self, t: f32) -> Self {
Self {
path: VPath::lerp(&a.path, &b.path, t),
fill: Color::lerp(&a.fill, &b.fill, t),
stroke: Stroke {
color: Color::lerp(&a.stroke.color, &b.stroke.color, t),
width: a.stroke.width + (b.stroke.width - a.stroke.width) * f64::from(t),
},
opacity: f32::lerp(&a.opacity, &b.opacity, t),
z_index: if t < 1.0 { a.z_index } else { b.z_index },
draw_range: f32::lerp(&a.draw_range.start, &b.draw_range.start, t)
..f32::lerp(&a.draw_range.end, &b.draw_range.end, t),
}
}
}