Skip to main content

gled_model/patching/
geometry.rs

1//! Placing a definition's physical geometry into a layout's scene coordinates.
2
3use crate::patching::definition::FixtureDefinition;
4use crate::patching::fixture::{LayoutPosition, UNITS_PER_METRE};
5use emath::Rot2;
6use emath::{Pos2, Vec2, pos2, vec2};
7
8impl LayoutPosition {
9    /// Metres in the definition's own frame (an offset or a size) to scene
10    /// units in the layout, scaled as the layout scales the fixture but not
11    /// yet turned with it.
12    pub fn scaled(&self, metres: [f32; 2]) -> Vec2 {
13        vec2(metres[0] * self.scale[0], metres[1] * self.scale[1]) * UNITS_PER_METRE
14    }
15
16    /// An offset in metres in the definition's own frame to scene units in
17    /// the layout, turned with the fixture.
18    fn scene_offset(&self, offset_m: [f32; 3]) -> Vec2 {
19        Rot2::from_angle(self.angle) * self.scaled([offset_m[0], offset_m[1]])
20    }
21}
22
23impl FixtureDefinition {
24    /// Scene position of every pixel at `position`, in pixel order.
25    pub fn pixel_scene_positions(&self, position: &LayoutPosition) -> Vec<Pos2> {
26        self.pixels
27            .iter()
28            .map(|pixel| position.pos + position.scene_offset(pixel.position_m))
29            .collect()
30    }
31}
32
33/// Centroid of a set of scene positions.
34pub fn centroid(positions: &[Pos2]) -> Option<Pos2> {
35    if positions.is_empty() {
36        return None;
37    }
38    let sum = positions
39        .iter()
40        .fold(Vec2::ZERO, |acc, pos| acc + pos.to_vec2());
41    Some(pos2(
42        sum.x / positions.len() as f32,
43        sum.y / positions.len() as f32,
44    ))
45}
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50    use crate::test_support::rgb_bar as bar;
51
52    #[test]
53    fn a_one_metre_bar_spans_one_hundred_units() {
54        let definition = bar(2, 1.0);
55        let position = LayoutPosition::at(pos2(0.0, 0.0));
56        let positions = definition.pixel_scene_positions(&position);
57        assert_eq!(positions[0], pos2(-25.0, 0.0));
58        assert_eq!(positions[1], pos2(25.0, 0.0));
59        // a 1 m definition spans 100 scene units
60        assert_eq!(positions[1].x - positions[0].x, 50.0);
61    }
62
63    #[test]
64    fn scale_stretches_per_axis() {
65        let definition = bar(2, 1.0);
66        let position = LayoutPosition::new(pos2(0.0, 0.0), 0.0, [2.0, 1.0]);
67        let positions = definition.pixel_scene_positions(&position);
68        assert_eq!(positions[0], pos2(-50.0, 0.0));
69        assert_eq!(positions[1], pos2(50.0, 0.0));
70    }
71
72    #[test]
73    fn angle_rotates_the_pixels_about_the_origin() {
74        let definition = bar(2, 1.0);
75        let position =
76            LayoutPosition::new(pos2(10.0, 10.0), std::f32::consts::FRAC_PI_2, [1.0, 1.0]);
77        let positions = definition.pixel_scene_positions(&position);
78        // a quarter turn puts the bar's pixels above and below its origin
79        assert!((positions[0].x - 10.0).abs() < 1e-3);
80        assert!((positions[0].y - -15.0).abs() < 1e-3);
81        assert!((positions[1].y - 35.0).abs() < 1e-3);
82    }
83
84    #[test]
85    fn centroid_of_a_group_is_its_midpoint() {
86        let positions = [pos2(0.0, 0.0), pos2(10.0, 20.0)];
87        assert_eq!(centroid(&positions), Some(pos2(5.0, 10.0)));
88        assert_eq!(centroid(&[]), None);
89    }
90}