roast2d_internal 0.4.0

Roast2D internal crate
Documentation
//! Polygon geometry utilities for regular polygon rendering.

use std::f32::consts::TAU;

use glam::Vec2;

use crate::sprite::Sprite;

/// Polygon geometry builder with automatic bounds calculation and UV mapping.
///
/// This struct consolidates polygon vertex generation, bounds calculation,
/// and UV coordinate mapping into a single reusable type.
pub(crate) struct PolygonBuilder {
    vertices: Vec<Vec2>,
    min: Vec2,
    max: Vec2,
}

impl PolygonBuilder {
    /// Create a regular polygon with the given number of sides.
    ///
    /// # Arguments
    /// * `center` - Center point of the polygon
    /// * `radius` - Distance from center to each vertex
    /// * `sides` - Number of sides (must be >= 3)
    /// * `rotation` - Rotation angle in radians
    pub fn regular(center: Vec2, radius: f32, sides: u32, rotation: f32) -> Self {
        let step = TAU / sides as f32;
        let vertices: Vec<Vec2> = (0..sides)
            .map(|i| {
                let angle = rotation + step * i as f32;
                center + Vec2::new(angle.cos(), angle.sin()) * radius
            })
            .collect();

        debug_assert!(vertices.len() >= 3);
        let (min, max) = Self::compute_bounds(&vertices);
        Self { vertices, min, max }
    }

    fn compute_bounds(vertices: &[Vec2]) -> (Vec2, Vec2) {
        let mut min = vertices[0];
        let mut max = vertices[0];
        for v in &vertices[1..] {
            min = min.min(*v);
            max = max.max(*v);
        }
        (min, max)
    }

    /// Get the polygon vertices.
    pub fn vertices(&self) -> &[Vec2] {
        &self.vertices
    }

    /// Get the minimum bounds corner.
    pub fn min(&self) -> Vec2 {
        self.min
    }

    /// Get the maximum bounds corner.
    pub fn max(&self) -> Vec2 {
        self.max
    }

    /// Get the bounding box size.
    pub fn size(&self) -> Vec2 {
        self.max - self.min
    }

    /// Compute UV coordinates for texturing the polygon with a sprite.
    ///
    /// Maps each vertex to UV coordinates based on its position within
    /// the polygon's bounding box, respecting sprite source rect and flip flags.
    pub fn uvs_for_sprite(&self, sprite: &Sprite) -> Vec<Vec2> {
        let tex_size = sprite.size.as_vec2().max(Vec2::splat(1.0));
        let (tex_min, tex_max) = sprite
            .src
            .as_ref()
            .map(|rect| (rect.min, rect.max))
            .unwrap_or((Vec2::ZERO, tex_size));
        let tex_extent = tex_max - tex_min;
        let size = self.size();

        self.vertices
            .iter()
            .map(|v| {
                let rel_x = if size.x.abs() <= f32::EPSILON {
                    0.5
                } else {
                    (v.x - self.min.x) / size.x
                };
                let rel_y = if size.y.abs() <= f32::EPSILON {
                    0.5
                } else {
                    (v.y - self.min.y) / size.y
                };
                let mut rel = Vec2::new(rel_x, rel_y);
                if sprite.flip_x {
                    rel.x = 1.0 - rel.x;
                }
                if sprite.flip_y {
                    rel.y = 1.0 - rel.y;
                }
                let uv_pixels = tex_min + tex_extent * rel;
                uv_pixels / tex_size
            })
            .collect()
    }
}