gled-model 2.32.0

The data model of gled: projects, scenes, effects, animations, curves and assets
Documentation
//! Placing a definition's physical geometry into a layout's scene coordinates.

use crate::patching::definition::FixtureDefinition;
use crate::patching::fixture::{LayoutPosition, UNITS_PER_METRE};
use emath::Rot2;
use emath::{Pos2, Vec2, pos2, vec2};

impl LayoutPosition {
    /// Metres in the definition's own frame (an offset or a size) to scene
    /// units in the layout, scaled as the layout scales the fixture but not
    /// yet turned with it.
    pub fn scaled(&self, metres: [f32; 2]) -> Vec2 {
        vec2(metres[0] * self.scale[0], metres[1] * self.scale[1]) * UNITS_PER_METRE
    }

    /// An offset in metres in the definition's own frame to scene units in
    /// the layout, turned with the fixture.
    fn scene_offset(&self, offset_m: [f32; 3]) -> Vec2 {
        Rot2::from_angle(self.angle) * self.scaled([offset_m[0], offset_m[1]])
    }
}

impl FixtureDefinition {
    /// Scene position of every pixel at `position`, in pixel order.
    pub fn pixel_scene_positions(&self, position: &LayoutPosition) -> Vec<Pos2> {
        self.pixels
            .iter()
            .map(|pixel| position.pos + position.scene_offset(pixel.position_m))
            .collect()
    }
}

/// Centroid of a set of scene positions.
pub fn centroid(positions: &[Pos2]) -> Option<Pos2> {
    if positions.is_empty() {
        return None;
    }
    let sum = positions
        .iter()
        .fold(Vec2::ZERO, |acc, pos| acc + pos.to_vec2());
    Some(pos2(
        sum.x / positions.len() as f32,
        sum.y / positions.len() as f32,
    ))
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::test_support::rgb_bar as bar;

    #[test]
    fn a_one_metre_bar_spans_one_hundred_units() {
        let definition = bar(2, 1.0);
        let position = LayoutPosition::at(pos2(0.0, 0.0));
        let positions = definition.pixel_scene_positions(&position);
        assert_eq!(positions[0], pos2(-25.0, 0.0));
        assert_eq!(positions[1], pos2(25.0, 0.0));
        // a 1 m definition spans 100 scene units
        assert_eq!(positions[1].x - positions[0].x, 50.0);
    }

    #[test]
    fn scale_stretches_per_axis() {
        let definition = bar(2, 1.0);
        let position = LayoutPosition::new(pos2(0.0, 0.0), 0.0, [2.0, 1.0]);
        let positions = definition.pixel_scene_positions(&position);
        assert_eq!(positions[0], pos2(-50.0, 0.0));
        assert_eq!(positions[1], pos2(50.0, 0.0));
    }

    #[test]
    fn angle_rotates_the_pixels_about_the_origin() {
        let definition = bar(2, 1.0);
        let position =
            LayoutPosition::new(pos2(10.0, 10.0), std::f32::consts::FRAC_PI_2, [1.0, 1.0]);
        let positions = definition.pixel_scene_positions(&position);
        // a quarter turn puts the bar's pixels above and below its origin
        assert!((positions[0].x - 10.0).abs() < 1e-3);
        assert!((positions[0].y - -15.0).abs() < 1e-3);
        assert!((positions[1].y - 35.0).abs() < 1e-3);
    }

    #[test]
    fn centroid_of_a_group_is_its_midpoint() {
        let positions = [pos2(0.0, 0.0), pos2(10.0, 20.0)];
        assert_eq!(centroid(&positions), Some(pos2(5.0, 10.0)));
        assert_eq!(centroid(&[]), None);
    }
}