plottery_lib 0.14.1

Core geometry library of Plottery, a creative coding framework for generative vector graphics and pen plotting.
Documentation
use crate::{Layer, Line, Rect, SampleSettings, V2};

use super::sample_triangle::{SamplePoint, SampleRect};

/// Iso-lines of a scalar field sampled over `bounds`. The field is sampled once in [`MarchingSquares::new`].
pub struct MarchingSquares {
    rects: Vec<SampleRect>,
}

impl MarchingSquares {
    pub fn new<F: Fn(V2) -> f32>(
        bounds: Rect,
        sample_settings: SampleSettings,
        function: F,
    ) -> Self {
        let num = (bounds.size() * sample_settings.points_per_unit).ceil_to_int();
        let mut rects: Vec<SampleRect> =
            Vec::with_capacity((num.x as usize + 1) * (num.y as usize + 1));

        let mut last_row: Vec<Option<SamplePoint>> = vec![None; num.x as usize + 1];
        for y in 0..(num.y as usize + 1) {
            let mut new_row: Vec<Option<SamplePoint>> = vec![None; num.x as usize + 1];
            for x in 0..(num.x as usize + 1) {
                let pos_br = (V2::new(x as f32, y as f32) / V2::new(num.x as f32, num.y as f32))
                    * bounds.size()
                    + bounds.bl();
                let br = SamplePoint {
                    pos: pos_br,
                    value: function(pos_br),
                };

                if y > 0 && x > 0 {
                    let pos_center = pos_br - V2::new(0.5, 0.5) / sample_settings.points_per_unit;
                    let center = SamplePoint {
                        pos: pos_center,
                        value: function(pos_center),
                    };

                    rects.push(SampleRect::new(
                        last_row[x - 1].unwrap(),
                        last_row[x].unwrap(),
                        br,
                        new_row[x - 1].unwrap(),
                        center,
                    ));
                }

                new_row[x] = Some(br);
            }
            last_row = new_row;
        }

        Self { rects }
    }

    /// Line segments where the field crosses `target_value`, unordered.
    pub fn lines(&self, target_value: f32) -> Vec<Line> {
        let mut lines = Vec::with_capacity(self.rects.len() * 4);
        for rect in &self.rects {
            for triangle in &rect.triangles {
                if let Some(line) = triangle.get_intersection_line(target_value) {
                    lines.push(line);
                }
            }
        }
        lines
    }

    /// Iso-lines at `target_value` joined into continuous paths.
    #[must_use]
    pub fn paths(&self, target_value: f32) -> Layer {
        Layer::new_from(self.lines(target_value)).combine_shapes_flat(None)
    }
}