rastor 0.1.13

A terminal-based game engine
Documentation
use std::f32::consts::PI;

use crossterm::style::Color;

use crate::{
    shapes::{Orientation, Shape, triangle::Triangle},
    types::{pos2::Pos2, vec2::Vec2},
};

pub struct Circle {
    pub center: Pos2,
    pub radius: f32,
    pub orientation: Orientation,
    pub color: Color,
    pub z_index: i32,
    triangles: Vec<Triangle>,
    pub children: Vec<Box<dyn Shape>>,
}

impl Circle {
    pub fn new(center: Pos2, radius: f32, n_sectors: usize, color: Color) -> Self {
        let mut triangles = Vec::new();

        let angle_per_triangle = (PI * 2.0) / n_sectors as f32;
        let base_length = 2.0 * radius * (angle_per_triangle / 2.0).sin();

        for i in 0..n_sectors {
            let theta = i as f32 * (2.0 * std::f32::consts::PI) / n_sectors as f32;

            let triangle = Triangle::new(
                center,
                Orientation::Custom(theta),
                Vec2::new(radius, base_length),
                color,
            );
            triangles.push(triangle);
        }

        // Ensure triangles are ordered by their z_index so rendering order is stable.
        triangles.sort_by_key(|t| t.z_index);

        Self {
            center,
            radius,
            orientation: Orientation::Custom(0.0),
            color,
            z_index: 0,
            triangles,
            children: vec![],
        }
    }

    pub fn push(&mut self, child: Box<dyn Shape>) {
        self.children.push(child);
    }

    /// Getter for z_index as an inherent method.
    pub fn z_index(&self) -> i32 {
        self.z_index
    }
}

impl Clone for Circle {
    fn clone(&self) -> Self {
        // Triangles (and their stdout locks) are not directly cloneable.
        // Recreate the triangles array using the same radius and sector count.
        let n_sectors = self.triangles.len();
        let mut c = Circle::new(self.center, self.radius, n_sectors, self.color);
        c.orientation = self.orientation;
        c.z_index = self.z_index;
        c.children = self.children.iter().map(|c| c.clone()).collect();
        c
    }
}

impl Shape for Circle {
    fn draw(&self) {
        for triangle in &self.triangles {
            triangle.draw();
        }

        for child in &self.children {
            child.draw();
        }
    }

    fn update(&mut self) {
        // Update geometry/state of each triangle first.
        for triangle in &mut self.triangles {
            triangle.update();
        }

        // After updates, re-sort by z_index so rendering order respects z values.
        self.triangles.sort_by_key(|t| t.z_index);
        self.children.sort_by_key(|child| child.z_index());

        let parent_pos: Vec2<f32> = self.pos().into();
        for child in &mut self.children {
            let relative_pos = child.pos().to_relative(parent_pos);

            if let Pos2::Relative(_) = relative_pos {
                child.set_pos(relative_pos);
            }

            child.update();
        }
    }

    fn set_orientation(&mut self, orientation: Orientation) {
        self.orientation = orientation;
    }

    fn orientation(&self) -> Orientation {
        self.orientation
    }

    fn z_index(&self) -> i32 {
        self.z_index
    }

    fn pos(&self) -> Pos2 {
        // The logical position for a circle is its center.
        self.center
    }

    fn set_pos(&mut self, pos: Pos2) {
        // Set the logical position (center) of the circle.
        self.center = pos;
    }

    fn box_clone(&self) -> Box<dyn Shape> {
        Box::new(self.clone())
    }

    fn collides_with(&self, other: &dyn Shape) -> bool {
        // Point-to-circle collision: check whether the other's position lies within
        // this circle's radius. We use squared distance to avoid an expensive sqrt.
        let other_pos: Vec2<f32> = other.pos().into();
        let center: Vec2<f32> = self.center.into();
        let dx = other_pos.x - center.x;
        let dy = other_pos.y - center.y;
        (dx * dx + dy * dy) <= (self.radius * self.radius)
    }
}