solstice_2d/d2/
shapes.rs

1use crate::{
2    d2::{SimpleConvexGeometry, Vertex2D},
3    Geometry,
4};
5
6/// An angle, in radians.
7#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Default)]
8pub struct Rad(pub f32);
9
10/// An angle, in degrees.
11#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Default)]
12pub struct Deg(pub f32);
13
14impl From<Rad> for Deg {
15    #[inline]
16    fn from(rad: Rad) -> Deg {
17        Deg(rad.0 * 180.0 / std::f32::consts::PI)
18    }
19}
20
21impl From<Deg> for Rad {
22    #[inline]
23    fn from(deg: Deg) -> Rad {
24        Rad(deg.0 * std::f32::consts::PI / 180.0)
25    }
26}
27
28#[derive(Debug, Copy, Clone, PartialEq)]
29pub enum ArcType {
30    Pie,
31    Open,
32    Closed,
33}
34
35impl Default for ArcType {
36    fn default() -> Self {
37        ArcType::Pie
38    }
39}
40
41#[derive(Debug, Copy, Clone, Default, PartialEq)]
42pub struct Arc {
43    pub arc_type: ArcType,
44    pub x: f32,
45    pub y: f32,
46    pub radius: f32,
47    pub angle1: Rad,
48    pub angle2: Rad,
49    pub segments: u32,
50}
51
52impl SimpleConvexGeometry for Arc {
53    type Vertices = std::vec::IntoIter<Vertex2D>;
54
55    fn vertices(&self) -> Self::Vertices {
56        let Arc {
57            arc_type,
58            x,
59            y,
60            radius,
61            angle1,
62            angle2,
63            segments,
64        } = *self;
65        let (angle1, angle2) = (angle1.0, angle2.0);
66
67        if segments == 0 || (angle1 - angle2).abs() < f32::EPSILON {
68            return Vec::<Vertex2D>::new().into_iter();
69        }
70
71        const TWO_PI: f32 = std::f32::consts::PI * 2.;
72        if (angle1 - angle2).abs() >= TWO_PI {
73            return SimpleConvexGeometry::vertices(&Circle {
74                x,
75                y,
76                radius,
77                segments,
78            })
79            .collect::<Vec<_>>() // type constraints require that we collect here
80            .into_iter();
81        }
82
83        let angle_shift = (angle2 - angle1) / segments as f32;
84        if angle_shift == 0. {
85            return Vec::<Vertex2D>::new().into_iter(); // bail on precision fail
86        }
87
88        let mut create_points = {
89            let mut phi = angle1;
90            move |coordinates: &mut [Vertex2D]| {
91                for coordinate in coordinates.iter_mut() {
92                    let (s, c) = phi.sin_cos();
93                    let x = x + radius * c;
94                    let y = y + radius * s;
95                    coordinate.position[0] = x;
96                    coordinate.position[1] = y;
97                    coordinate.uv[0] = (c + 1.) / 2.;
98                    coordinate.uv[1] = (s + 1.) / 2.;
99                    phi += angle_shift;
100                }
101            }
102        };
103
104        let vertices = match arc_type {
105            ArcType::Pie => {
106                let num_coords = segments as usize + 3;
107                let mut coords = vec![Vertex2D::default(); num_coords];
108                let anchor = Vertex2D {
109                    position: [x, y],
110                    ..Vertex2D::default()
111                };
112                coords[0] = anchor;
113                coords[num_coords - 1] = anchor;
114                create_points(&mut coords[1..num_coords - 1]);
115                coords
116            }
117            ArcType::Open => {
118                let num_coords = segments as usize + 1;
119                let mut coords = vec![Vertex2D::default(); num_coords];
120                create_points(&mut coords);
121                coords
122            }
123            ArcType::Closed => {
124                let num_coords = segments as usize + 2;
125                let mut coords = vec![Vertex2D::default(); num_coords];
126                create_points(&mut coords);
127                coords[num_coords - 1] = coords[0];
128                coords
129            }
130        };
131
132        vertices.into_iter()
133    }
134
135    fn vertex_count(&self) -> usize {
136        match self.arc_type {
137            ArcType::Pie => self.segments as usize + 3,
138            ArcType::Open => self.segments as usize + 1,
139            ArcType::Closed => self.segments as usize + 2,
140        }
141    }
142}
143
144#[derive(Debug, Copy, Clone, Default, PartialEq)]
145pub struct Circle {
146    pub x: f32,
147    pub y: f32,
148    pub radius: f32,
149    pub segments: u32,
150}
151
152impl SimpleConvexGeometry for Circle {
153    type Vertices = <SimpleConvexPolygon as SimpleConvexGeometry>::Vertices;
154
155    fn vertices(&self) -> Self::Vertices {
156        let ellipse: Ellipse = (*self).into();
157        let polygon: SimpleConvexPolygon = ellipse.into();
158        SimpleConvexGeometry::vertices(&polygon)
159    }
160
161    fn vertex_count(&self) -> usize {
162        self.segments as usize
163    }
164}
165
166impl From<Circle> for Ellipse {
167    fn from(c: Circle) -> Self {
168        Self {
169            x: c.x,
170            y: c.y,
171            radius_x: c.radius,
172            radius_y: c.radius,
173            segments: c.segments,
174        }
175    }
176}
177
178#[derive(Copy, Clone, Default, Debug, PartialEq)]
179pub struct Ellipse {
180    pub x: f32,
181    pub y: f32,
182    pub radius_x: f32,
183    pub radius_y: f32,
184    pub segments: u32,
185}
186
187impl From<Ellipse> for SimpleConvexPolygon {
188    fn from(e: Ellipse) -> Self {
189        Self {
190            x: e.x,
191            y: e.y,
192            vertex_count: e.segments,
193            radius_x: e.radius_x,
194            radius_y: e.radius_y,
195        }
196    }
197}
198
199impl SimpleConvexGeometry for Ellipse {
200    type Vertices = <SimpleConvexPolygon as SimpleConvexGeometry>::Vertices;
201
202    fn vertices(&self) -> Self::Vertices {
203        let polygon: SimpleConvexPolygon = (*self).into();
204        SimpleConvexGeometry::vertices(&polygon)
205    }
206
207    fn vertex_count(&self) -> usize {
208        self.segments as usize
209    }
210}
211
212#[derive(Copy, Clone, Default, Debug, PartialEq)]
213pub struct Rectangle {
214    pub x: f32,
215    pub y: f32,
216    pub width: f32,
217    pub height: f32,
218}
219
220impl Rectangle {
221    pub fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
222        Self {
223            x,
224            y,
225            width,
226            height,
227        }
228    }
229
230    pub fn vertices(&self) -> Vec<Vertex2D> {
231        vec![
232            Vertex2D {
233                position: [self.x, self.y],
234                uv: [0., 0.],
235                ..Default::default()
236            },
237            Vertex2D {
238                position: [self.x, self.y + self.height],
239                uv: [0., 1.],
240                ..Default::default()
241            },
242            Vertex2D {
243                position: [self.x + self.width, self.y + self.height],
244                uv: [1., 1.],
245                ..Default::default()
246            },
247            Vertex2D {
248                position: [self.x + self.width, self.y],
249                uv: [1., 0.],
250                ..Default::default()
251            },
252        ]
253    }
254}
255
256impl From<Rectangle> for solstice::quad_batch::Quad<Vertex2D> {
257    fn from(r: Rectangle) -> Self {
258        use solstice::{quad_batch::Quad, viewport::Viewport};
259        let positions = Quad::from(Viewport::new(r.x, r.y, r.width, r.height));
260        let uvs = Quad::from(Viewport::new(0., 0., 1., 1.));
261        positions.zip(uvs).map(|((x, y), (s, t))| Vertex2D {
262            position: [x, y],
263            uv: [s, t],
264            ..Default::default()
265        })
266    }
267}
268
269impl From<Rectangle> for solstice::quad_batch::Quad<(f32, f32)> {
270    fn from(r: Rectangle) -> Self {
271        use solstice::{quad_batch::Quad, viewport::Viewport};
272        Quad::from(Viewport::new(r.x, r.y, r.width, r.height))
273    }
274}
275
276impl From<&Rectangle> for Geometry<'_, Vertex2D> {
277    fn from(r: &Rectangle) -> Self {
278        Geometry::new(r.vertices(), Some(&[0u32, 1, 2, 0, 3, 2][..]))
279    }
280}
281
282impl From<Rectangle> for Geometry<'_, Vertex2D> {
283    fn from(r: Rectangle) -> Self {
284        (&r).into()
285    }
286}
287
288impl From<solstice::quad_batch::Quad<Vertex2D>> for Geometry<'_, Vertex2D> {
289    fn from(quad: solstice::quad_batch::Quad<Vertex2D>) -> Self {
290        Geometry::new(
291            quad.vertices.to_vec(),
292            Some(
293                solstice::quad_batch::INDICES[..]
294                    .iter()
295                    .copied()
296                    .map(|i| i as u32)
297                    .collect::<Vec<_>>(),
298            ),
299        )
300    }
301}
302
303#[derive(Copy, Clone, Default, Debug, PartialEq)]
304pub struct RegularPolygon {
305    pub x: f32,
306    pub y: f32,
307    pub vertex_count: u32,
308    pub radius: f32,
309}
310
311impl RegularPolygon {
312    pub fn new(x: f32, y: f32, vertex_count: u32, radius: f32) -> Self {
313        Self {
314            x,
315            y,
316            vertex_count,
317            radius,
318        }
319    }
320}
321
322impl SimpleConvexGeometry for RegularPolygon {
323    type Vertices = std::iter::Map<std::ops::Range<u32>, Box<dyn Fn(u32) -> Vertex2D>>;
324
325    fn vertices(&self) -> Self::Vertices {
326        const TWO_PI: f32 = std::f32::consts::PI * 2.;
327        let RegularPolygon {
328            x,
329            y,
330            vertex_count,
331            radius,
332        } = *self;
333        let angle_shift = TWO_PI / vertex_count as f32;
334
335        // an allocation is a small price to pay for my sanity
336        (0..vertex_count).map(Box::new(move |i| {
337            let phi = angle_shift * i as f32;
338            let (s, c) = phi.sin_cos();
339            let (x, y) = (x + radius * c, y + radius * s);
340            Vertex2D::new([x, y], [1., 1., 1., 1.], [(c + 1.) / 2., (s + 1.) / 2.])
341        }))
342    }
343
344    fn vertex_count(&self) -> usize {
345        self.vertex_count as _
346    }
347}
348
349#[derive(Copy, Clone, Default, Debug, PartialEq)]
350pub struct SimpleConvexPolygon {
351    pub x: f32,
352    pub y: f32,
353    pub vertex_count: u32,
354    pub radius_x: f32,
355    pub radius_y: f32,
356}
357
358impl SimpleConvexGeometry for SimpleConvexPolygon {
359    type Vertices = std::iter::Map<std::ops::Range<u32>, Box<dyn Fn(u32) -> Vertex2D>>;
360
361    fn vertices(&self) -> Self::Vertices {
362        const TWO_PI: f32 = std::f32::consts::PI * 2.;
363        let SimpleConvexPolygon {
364            x,
365            y,
366            vertex_count,
367            radius_x,
368            radius_y,
369        } = *self;
370        let angle_shift = TWO_PI / vertex_count as f32;
371
372        // an allocation is a small price to pay for my sanity
373        (0..vertex_count).map(Box::new(move |i| {
374            let phi = angle_shift * i as f32;
375            let (x, y) = (x + radius_x * phi.cos(), y + radius_y * phi.sin());
376            Vertex2D::new([x, y], [1., 1., 1., 1.], [0.5, 0.5])
377        }))
378    }
379
380    fn vertex_count(&self) -> usize {
381        self.vertex_count as usize
382    }
383}
384
385impl<T> From<T> for Geometry<'_, Vertex2D>
386where
387    T: SimpleConvexGeometry,
388{
389    fn from(s: T) -> Self {
390        let vertices = s.vertices().collect::<Vec<_>>();
391        let indices = (1..(vertices.len() as u32).saturating_sub(1))
392            .flat_map(|i| [0, i, i + 1])
393            .collect::<Vec<_>>();
394        Geometry::new(vertices, Some(indices))
395    }
396}
397
398#[cfg(test)]
399mod tests {
400    use super::*;
401
402    #[test]
403    fn geometry() {
404        use crate::Geometry;
405        let geometry = SimpleConvexPolygon {
406            x: 0.0,
407            y: 0.0,
408            vertex_count: 3,
409            radius_x: 100.,
410            radius_y: 100.,
411        };
412
413        let data = Geometry::<'_, Vertex2D>::from(geometry);
414        assert_eq!(data.vertices.len(), 3);
415        assert_eq!(data.indices.unwrap().len(), 3);
416
417        let geometry = SimpleConvexPolygon {
418            vertex_count: 4,
419            ..geometry
420        };
421        let data = Geometry::<'_, Vertex2D>::from(geometry);
422        assert_eq!(data.vertices.len(), 4);
423        assert_eq!(data.indices.unwrap().len(), 6);
424
425        let geometry = SimpleConvexPolygon {
426            vertex_count: 5,
427            ..geometry
428        };
429        let data = Geometry::<'_, Vertex2D>::from(geometry);
430        assert_eq!(data.vertices.len(), 5);
431        assert_eq!(data.indices.unwrap().len(), (5 - 2) * 3);
432    }
433}