geng_draw2d/
chain.rs

1use super::*;
2
3pub struct Chain {
4    pub transform: mat3<f32>,
5    pub vertices: Vec<ColoredVertex>,
6}
7
8impl Chain {
9    pub fn new(
10        chain: batbox_lapp::Chain<f32>,
11        width: f32,
12        color: Rgba<f32>,
13        round_resolution: usize,
14    ) -> Self {
15        Self::new_gradient(
16            chain
17                .vertices
18                .into_iter()
19                .map(|pos| ColoredVertex {
20                    a_pos: pos,
21                    a_color: color,
22                })
23                .collect(),
24            width,
25            round_resolution,
26        )
27    }
28
29    pub fn new_gradient(vertices: Vec<ColoredVertex>, width: f32, round_resolution: usize) -> Self {
30        let len = vertices.len();
31        if len < 2 {
32            return Self {
33                transform: mat3::identity(),
34                vertices: vec![],
35            };
36        }
37
38        let polygon_vertices = (len - 1) * 6;
39        let mut polygon = Vec::with_capacity(polygon_vertices);
40
41        // Start
42        {
43            let dir = (vertices[1].a_pos - vertices[0].a_pos)
44                .normalize_or_zero()
45                .rotate_90()
46                * width
47                / 2.0;
48            polygon.push(ColoredVertex {
49                a_pos: vertices[0].a_pos + dir,
50                ..vertices[0]
51            });
52            let right = ColoredVertex {
53                a_pos: vertices[0].a_pos - dir,
54                ..vertices[0]
55            };
56            polygon.push(right);
57            polygon.push(right); // Temp
58            polygon.push(right);
59        }
60
61        // Middle
62        let mut vertex_iter = vertices.iter().copied();
63        let (mut prev, mut current) = (vertex_iter.next().unwrap(), vertex_iter.next().unwrap());
64        {
65            for next in vertex_iter {
66                // Calculate angles
67                let backward = (prev.a_pos - current.a_pos).normalize_or_zero();
68                let forward = (next.a_pos - current.a_pos).normalize_or_zero();
69                if backward == vec2::ZERO || forward == vec2::ZERO {
70                    // Too small distance
71                    current = next;
72                    continue;
73                }
74
75                let cos = -vec2::dot(forward, backward);
76                let cos_half = ((cos + 1.0) / 2.0).max(0.0).sqrt();
77
78                if cos_half.approx_eq(&1.0) {
79                    // Straight line -> no rounding
80                    let dir =
81                        (current.a_pos - prev.a_pos).normalize_or_zero().rotate_90() * width / 2.0;
82                    let left = ColoredVertex {
83                        a_pos: current.a_pos + dir,
84                        ..current
85                    };
86                    let right = ColoredVertex {
87                        a_pos: current.a_pos - dir,
88                        ..current
89                    };
90                    // Finish incoming segment
91                    let temp = polygon.len() - 2;
92                    polygon[temp] = left;
93                    polygon.push(left);
94                    polygon.push(right);
95                    // Start outcoming segment
96                    polygon.push(left);
97                    polygon.push(right);
98                    polygon.push(right); // Temp
99                    polygon.push(right);
100
101                    prev = current;
102                    current = next;
103                    continue;
104                }
105
106                // Magic constant (0.1) avoids very large distance when the angle is small
107                // (i.e. when the chain is going back at itself)
108                let d = width / cos_half.max(0.1) / 2.0;
109
110                let inside_dir = (backward + forward).normalize_or_zero();
111                let inner = current.a_pos + inside_dir * d;
112
113                // Positive side -> turn left
114                // Negative side -> turn right
115                let side = vec2::dot(
116                    (next.a_pos - prev.a_pos).normalize_or_zero().rotate_90(),
117                    inside_dir,
118                )
119                .signum();
120
121                let inner_vertex = ColoredVertex {
122                    a_pos: inner,
123                    ..current
124                };
125
126                let backward_norm = backward.rotate_90() * side;
127                let back_vertex = ColoredVertex {
128                    a_pos: inner + backward_norm * width,
129                    ..current
130                };
131
132                let forward_norm = -forward.rotate_90() * side;
133                let forward_vertex = ColoredVertex {
134                    a_pos: inner + forward_norm * width,
135                    ..current
136                };
137
138                // Finish incoming segment
139                {
140                    let (left, right) = if side.is_sign_positive() {
141                        (inner_vertex, back_vertex) // Turn left
142                    } else {
143                        (back_vertex, inner_vertex) // Turn right
144                    };
145                    let temp = polygon.len() - 2;
146                    polygon[temp] = left;
147                    polygon.push(left);
148                    polygon.push(right);
149                }
150
151                // Round
152                {
153                    let angle =
154                        Angle::acos(vec2::dot(forward_norm, backward_norm).clamp(-1.0, 1.0));
155                    let (start, end, shift) = if side.is_sign_positive() {
156                        (back_vertex, forward_vertex, backward_norm * width)
157                    } else {
158                        (forward_vertex, back_vertex, forward_norm * width)
159                    };
160                    let mut round = Vec::with_capacity(round_resolution + 2);
161                    round.push(start);
162                    for i in 1..=round_resolution {
163                        round.push(ColoredVertex {
164                            a_pos: inner
165                                + shift.rotate(angle * i as f32 / (round_resolution + 1) as f32),
166                            ..current
167                        });
168                    }
169                    round.push(end);
170
171                    // Triangle fan
172                    for i in 0..=round_resolution {
173                        polygon.push(inner_vertex);
174                        polygon.push(round[i]);
175                        polygon.push(round[i + 1]);
176                    }
177                }
178
179                // Start outcoming segment
180                {
181                    let (left, right) = if side.is_sign_positive() {
182                        (inner_vertex, forward_vertex) // Turn left
183                    } else {
184                        (forward_vertex, inner_vertex) // Turn right
185                    };
186                    polygon.push(left);
187                    polygon.push(right);
188                    polygon.push(right); // Temp
189                    polygon.push(right);
190                }
191
192                prev = current;
193                current = next;
194            }
195        }
196
197        // End
198        {
199            let dir = (current.a_pos - prev.a_pos).normalize_or_zero().rotate_90() * width / 2.0;
200            let left = ColoredVertex {
201                a_pos: vertices[len - 1].a_pos + dir,
202                ..vertices[len - 1]
203            };
204            let temp = polygon.len() - 2;
205            polygon[temp] = left; // Temp
206            polygon.push(left);
207            polygon.push(ColoredVertex {
208                a_pos: vertices[len - 1].a_pos - dir,
209                ..vertices[len - 1]
210            });
211        }
212
213        let (transform, vertices) = Polygon::normalize(polygon);
214        Self {
215            transform,
216            vertices,
217        }
218    }
219}
220
221impl Transform2d<f32> for Chain {
222    fn bounding_quad(&self) -> batbox_lapp::Quad<f32> {
223        batbox_lapp::Quad {
224            transform: self.transform,
225        }
226    }
227
228    fn apply_transform(&mut self, transform: mat3<f32>) {
229        self.transform = transform * self.transform;
230    }
231}
232
233impl Draw2d for Chain {
234    fn draw2d_transformed(
235        &self,
236        helper: &Helper,
237        framebuffer: &mut ugli::Framebuffer,
238        camera: &dyn AbstractCamera2d,
239        transform: mat3<f32>,
240    ) {
241        let framebuffer_size = framebuffer.size();
242        ugli::draw(
243            framebuffer,
244            &helper.color_program,
245            ugli::DrawMode::Triangles,
246            &ugli::VertexBuffer::new_dynamic(helper.ugli(), self.vertices.clone()),
247            (
248                ugli::uniforms! {
249                    u_color: Rgba::WHITE,
250                    u_framebuffer_size: framebuffer_size,
251                    u_model_matrix: transform * self.transform,
252                },
253                camera.uniforms(framebuffer_size.map(|x| x as f32)),
254            ),
255            ugli::DrawParameters {
256                blend_mode: Some(ugli::BlendMode::straight_alpha()),
257                ..Default::default()
258            },
259        );
260    }
261}