1use yog_data::Schema;
5
6use crate::math::Vec2;
7use crate::rect::Rect;
8
9#[derive(Schema, Debug, Clone, Copy, PartialEq)]
11#[data(tag = "s")]
12pub enum Seg {
13 Move {
15 x: f32,
16 y: f32,
17 },
18 Line {
19 x: f32,
20 y: f32,
21 },
22 Quad {
24 x1: f32,
25 y1: f32,
26 x: f32,
27 y: f32,
28 },
29 Cubic {
31 x1: f32,
32 y1: f32,
33 x2: f32,
34 y2: f32,
35 x: f32,
36 y: f32,
37 },
38 Close,
40}
41
42pub fn bounds(path: &[Seg], r: f32) -> Option<Rect> {
44 let points = path.iter().flat_map(|s| match *s {
45 Seg::Move { x, y } | Seg::Line { x, y } => vec![Vec2::new(x, y)],
46 Seg::Quad { x1, y1, x, y } => vec![Vec2::new(x1, y1), Vec2::new(x, y)],
47 Seg::Cubic {
48 x1,
49 y1,
50 x2,
51 y2,
52 x,
53 y,
54 } => vec![Vec2::new(x1, y1), Vec2::new(x2, y2), Vec2::new(x, y)],
55 Seg::Close => vec![],
56 });
57 Rect::around(points).map(|b| b.outset(r))
58}
59
60pub fn flatten(path: &[Seg], to: impl Fn(Vec2) -> Vec2) -> Vec<Vec<Vec2>> {
64 let mut out = Vec::new();
65 let mut cur: Vec<Vec2> = Vec::new();
66 let mut at = Vec2::ZERO;
67 for seg in path {
68 match *seg {
69 Seg::Move { x, y } => {
70 if cur.len() > 1 {
71 out.push(std::mem::take(&mut cur));
72 }
73 at = to(Vec2::new(x, y));
74 cur = vec![at];
75 }
76 Seg::Line { x, y } => {
77 at = to(Vec2::new(x, y));
78 cur.push(at);
79 }
80 Seg::Quad { x1, y1, x, y } => {
81 let (c, p, from) = (to(Vec2::new(x1, y1)), to(Vec2::new(x, y)), at);
82 let n = steps(from.distance(c) + c.distance(p));
83 for i in 1..=n {
84 let t = i as f32 / n as f32;
85 cur.push(from.lerp(c, t).lerp(c.lerp(p, t), t));
86 }
87 at = p;
88 }
89 Seg::Cubic {
90 x1,
91 y1,
92 x2,
93 y2,
94 x,
95 y,
96 } => {
97 let (c1, c2, p, from) = (
98 to(Vec2::new(x1, y1)),
99 to(Vec2::new(x2, y2)),
100 to(Vec2::new(x, y)),
101 at,
102 );
103 let n = steps(from.distance(c1) + c1.distance(c2) + c2.distance(p));
104 for i in 1..=n {
105 let t = i as f32 / n as f32;
106 let (a, b, c) = (from.lerp(c1, t), c1.lerp(c2, t), c2.lerp(p, t));
107 cur.push(a.lerp(b, t).lerp(b.lerp(c, t), t));
108 }
109 at = p;
110 }
111 Seg::Close => {
112 if let Some(&first) = cur.first() {
113 if at != first {
114 cur.push(first);
115 }
116 at = first;
117 }
118 if cur.len() > 1 {
119 out.push(std::mem::take(&mut cur));
120 }
121 cur = vec![at];
122 }
123 }
124 }
125 if cur.len() > 1 {
126 out.push(cur);
127 }
128 out
129}
130
131const STEP_LENGTH: f32 = 2.0;
133const MAX_STEPS: f32 = 64.0;
134
135fn steps(len: f32) -> usize {
137 (len / STEP_LENGTH).ceil().clamp(1.0, MAX_STEPS) as usize
138}