1use std::collections::BTreeMap;
4use std::f64::consts::TAU;
5use std::ops::Range;
6
7use kurbo::{Affine, Point, Vec2};
8
9use crate::Interpolate;
10use crate::color::{Color, WHITE};
11use crate::geom::VPath;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub struct MobjectId(pub u32);
16
17pub type SceneState = BTreeMap<MobjectId, VState>;
19
20#[derive(Debug, Clone, Copy, PartialEq)]
22pub struct Stroke {
23 pub color: Color,
25 pub width: f64,
27}
28
29#[derive(Debug, Clone, PartialEq)]
33pub struct VState {
34 pub path: VPath,
36 pub fill: Color,
38 pub stroke: Stroke,
40 pub opacity: f32,
42 pub z_index: i32,
44 pub draw_range: Range<f32>,
46}
47
48impl VState {
49 pub fn new(path: VPath) -> Self {
51 Self {
52 path,
53 fill: Color::TRANSPARENT,
54 stroke: Stroke {
55 color: WHITE,
56 width: 0.04,
57 },
58 opacity: 1.0,
59 z_index: 0,
60 draw_range: 0.0..1.0,
61 }
62 }
63
64 pub fn circle(radius: f64) -> Self {
66 Self::new(VPath::arc(radius, 0.0, TAU))
67 }
68
69 pub fn arc(radius: f64, start: f64, sweep: f64) -> Self {
71 Self::new(VPath::arc(radius, start, sweep))
72 }
73
74 pub fn rectangle(width: f64, height: f64) -> Self {
76 let (w, h) = (width / 2.0, height / 2.0);
77 let pts = [(w, h), (-w, h), (-w, -h), (w, -h)].map(Point::from);
78 Self::new(VPath::polyline(&pts, true))
79 }
80
81 pub fn square(side: f64) -> Self {
83 Self::rectangle(side, side)
84 }
85
86 pub fn polygon(points: &[Point]) -> Self {
88 Self::new(VPath::polyline(points, true))
89 }
90
91 pub fn line(a: Point, b: Point) -> Self {
93 Self::new(VPath::polyline(&[a, b], false))
94 }
95
96 pub fn function_graph(f: impl Fn(f64) -> f64, x_range: Range<f64>, segments: usize) -> Self {
99 let n = segments.max(1);
100 let h = (x_range.end - x_range.start) / n as f64;
101 let slope = |x: f64| (f(x + h * 1e-3) - f(x - h * 1e-3)) / (h * 2e-3);
102 let segs = (0..n)
103 .map(|i| {
104 let (x0, x1) = (
105 x_range.start + h * i as f64,
106 x_range.start + h * (i + 1) as f64,
107 );
108 let (p0, p3) = (Point::new(x0, f(x0)), Point::new(x1, f(x1)));
109 let (d0, d1) = (Vec2::new(h, h * slope(x0)), Vec2::new(h, h * slope(x1)));
110 kurbo::CubicBez::new(p0, p0 + d0 / 3.0, p3 - d1 / 3.0, p3)
111 })
112 .collect();
113 Self::new(VPath {
114 subpaths: vec![crate::geom::SubPath {
115 segments: segs,
116 closed: false,
117 }],
118 })
119 }
120
121 pub fn dot(p: Point) -> Self {
123 Self::circle(0.08).fill(WHITE).shift(p.to_vec2())
124 }
125
126 pub fn fill(self, fill: Color) -> Self {
128 Self { fill, ..self }
129 }
130
131 pub fn stroke(self, color: Color, width: f64) -> Self {
133 Self {
134 stroke: Stroke { color, width },
135 ..self
136 }
137 }
138
139 pub fn z_index(self, z_index: i32) -> Self {
141 Self { z_index, ..self }
142 }
143
144 pub fn transform(self, a: Affine) -> Self {
146 Self {
147 path: self.path.transform(a),
148 ..self
149 }
150 }
151
152 pub fn shift(self, v: Vec2) -> Self {
154 self.transform(Affine::translate(v))
155 }
156
157 pub fn move_to(self, p: Point) -> Self {
159 let c = self.path.center();
160 self.shift(p - c)
161 }
162
163 pub fn scale(self, factor: f64) -> Self {
165 let c = self.path.center();
166 self.transform(Affine::scale_about(factor, c))
167 }
168
169 pub fn rotate(self, angle: f64) -> Self {
171 let c = self.path.center();
172 self.transform(Affine::rotate_about(angle, c))
173 }
174}
175
176impl crate::position::Position for VState {
177 fn bbox(&self) -> Option<kurbo::Rect> {
178 self.path.bbox()
179 }
180 fn transform(self, a: Affine) -> Self {
181 VState::transform(self, a)
182 }
183}
184
185impl Interpolate for f32 {
186 fn lerp(a: &Self, b: &Self, t: f32) -> Self {
187 a + (b - a) * t
188 }
189}
190
191impl Interpolate for VPath {
192 fn lerp(a: &Self, b: &Self, t: f32) -> Self {
194 assert_eq!(
195 a.subpaths.len(),
196 b.subpaths.len(),
197 "lerp of unaligned paths"
198 );
199 let t64 = f64::from(t);
200 let subpaths = a
201 .subpaths
202 .iter()
203 .zip(&b.subpaths)
204 .map(|(sa, sb)| {
205 assert_eq!(
206 sa.segments.len(),
207 sb.segments.len(),
208 "lerp of unaligned paths"
209 );
210 let segments = sa
211 .segments
212 .iter()
213 .zip(&sb.segments)
214 .map(|(x, y)| {
215 kurbo::CubicBez::new(
216 x.p0.lerp(y.p0, t64),
217 x.p1.lerp(y.p1, t64),
218 x.p2.lerp(y.p2, t64),
219 x.p3.lerp(y.p3, t64),
220 )
221 })
222 .collect();
223 let closed = if t <= 0.0 {
225 sa.closed
226 } else if t >= 1.0 {
227 sb.closed
228 } else {
229 sa.closed && sb.closed
230 };
231 crate::geom::SubPath { segments, closed }
232 })
233 .collect();
234 VPath { subpaths }
235 }
236}
237
238impl Interpolate for VState {
239 fn lerp(a: &Self, b: &Self, t: f32) -> Self {
241 Self {
242 path: VPath::lerp(&a.path, &b.path, t),
243 fill: Color::lerp(&a.fill, &b.fill, t),
244 stroke: Stroke {
245 color: Color::lerp(&a.stroke.color, &b.stroke.color, t),
246 width: a.stroke.width + (b.stroke.width - a.stroke.width) * f64::from(t),
247 },
248 opacity: f32::lerp(&a.opacity, &b.opacity, t),
249 z_index: if t < 1.0 { a.z_index } else { b.z_index },
250 draw_range: f32::lerp(&a.draw_range.start, &b.draw_range.start, t)
251 ..f32::lerp(&a.draw_range.end, &b.draw_range.end, t),
252 }
253 }
254}