use crate::{
X_SCALE, shapes::{Orientation, Shape, inside_triangle, line::Line, pixel::{Pixel, flush_pixels}}, types::{pos2::Pos2, vec2::Vec2}
};
use crossterm::style::Color;
use crossterm::terminal;
use std::f32::consts::{FRAC_PI_2, PI};
pub struct Triangle {
pub base_vertices: TriangleVertices,
pub vertices: TriangleVertices,
pub orientation: Orientation,
pub local_center: Pos2,
pub center: Pos2,
pub z_index: i32,
pub color: Color,
lines: Vec<Line>,
pub children: Vec<Box<dyn Shape>>,
}
#[derive(Clone, Copy, Debug)]
pub struct TriangleVertices {
pub top_left: Vec2<f32>,
pub bottom_left: Vec2<f32>,
pub bottom_right: Vec2<f32>,
}
impl TriangleVertices {
pub fn to_arr(&self) -> [Vec2<f32>; 3] {
[self.top_left, self.bottom_left, self.bottom_right]
}
}
impl From<&[Vec2<f32>; 3]> for TriangleVertices {
fn from(value: &[Vec2<f32>; 3]) -> Self {
Self {
top_left: value[0],
bottom_left: value[1],
bottom_right: value[2],
}
}
}
impl Triangle {
pub fn new(center: Pos2, orientation: Orientation, size: Vec2<f32>, color: Color) -> Self {
let p1 = Vec2::new(-1.0, 1.0) * size; let p2 = Vec2::new(-1.0, -1.0) * size; let p3 = Vec2::new(1.0, -1.0) * size; let base_vertices = TriangleVertices::from(&[p1, p2, p3]);
Self {
base_vertices,
vertices: base_vertices,
orientation,
local_center: center,
center,
lines: vec![
Line::new(p1, p2, color),
Line::new(p2, p3, color),
Line::new(p3, p1, color),
],
color,
z_index: 0,
children: vec![],
}
}
pub fn push(&mut self, child: Box<dyn Shape>) {
self.children.push(child);
}
fn rasterize(&self, out: &mut Vec<Pixel>, term_size: (u16, u16)) {
let (term_width, term_height) = (term_size.0 as i32, term_size.1 as i32);
let vertices = self.vertices.to_arr();
let min_x = vertices.iter().map(|v| v.x as i32).min().unwrap();
let max_x = vertices.iter().map(|v| v.x as i32).max().unwrap();
let min_y = vertices.iter().map(|v| v.y as i32).min().unwrap();
let max_y = vertices.iter().map(|v| v.y as i32).max().unwrap();
out.reserve(((max_x - min_x + 1) * (max_y - min_y + 1)) as usize);
for py in min_y..=max_y {
for px in min_x..=max_x {
let p = Vec2::new(px as f32, py as f32);
if inside_triangle(vertices[0], vertices[1], vertices[2], p) {
if px >= 0 && py >= 0 && px < term_width && py < term_height {
out.push(Pixel::new(
px as u16,
py as u16,
'â–ˆ',
self.color,
self.z_index,
));
}
}
}
}
}
fn update_geometry(&mut self) {
let rad = self.rad();
let top_left = self.base_vertices.top_left; let bottom_left = self.base_vertices.bottom_left; let bottom_right = self.base_vertices.bottom_right;
let mut rp1 = top_left.rotate(rad);
let mut rp2 = bottom_left.rotate(rad);
let mut rp3 = bottom_right.rotate(rad);
rp1.x *= X_SCALE;
rp2.x *= X_SCALE;
rp3.x *= X_SCALE;
let center_vec: Vec2<f32> = self.center.into();
let sp1 = Self::to_screen_coords(rp1, center_vec);
let sp2 = Self::to_screen_coords(rp2, center_vec);
let sp3 = Self::to_screen_coords(rp3, center_vec);
self.vertices.top_left = sp1;
self.vertices.bottom_left = sp2;
self.vertices.bottom_right = sp3;
self.lines = vec![
Line::new(sp1, sp2, self.color),
Line::new(sp2, sp3, self.color),
Line::new(sp3, sp1, self.color),
];
self.lines.sort_by_key(|line| {
let line_pos1: Vec2<f32> = line.pos1.into();
let line_pos2: Vec2<f32> = line.pos2.into();
(line_pos1.y + line_pos2.y) as i32
});
}
pub fn rad(&self) -> f32 {
match self.orientation {
Orientation::Up => 0.0,
Orientation::Right => PI / 2.0, Orientation::Down => PI, Orientation::Left => 3.0 * FRAC_PI_2, Orientation::Custom(v) => v,
}
}
fn to_screen_coords(v: Vec2<f32>, center: Vec2<f32>) -> Vec2<f32> {
Vec2::new(v.x + center.x, -v.y + center.y)
}
}
impl Shape for Triangle {
fn draw(&self) {
let (w, h) = terminal::size().unwrap();
let mut pixels: Vec<Pixel> = Vec::with_capacity(1024);
self.rasterize(&mut pixels, (w, h));
let mut out = std::io::stdout().lock();
flush_pixels(&mut out, &mut pixels);
for line in &self.lines {
line.draw();
}
for child in &self.children {
child.draw();
}
}
fn update(&mut self) {
self.update_geometry();
self.lines.sort_by_key(|line| {
let line_pos1: Vec2<f32> = line.pos1.into();
let line_pos2: Vec2<f32> = line.pos2.into();
(line_pos1.y + line_pos2.y) as i32
});
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 pos(&self) -> Pos2 {
self.local_center
}
fn set_pos(&mut self, pos: Pos2) {
self.local_center = pos;
self.center = pos;
}
fn z_index(&self) -> i32 {
self.z_index
}
fn box_clone(&self) -> Box<dyn Shape> {
Box::new(self.clone())
}
fn collides_with(&self, other: &dyn Shape) -> bool {
let p = other.pos();
let verts = self.vertices.to_arr();
inside_triangle(verts[0], verts[1], verts[2], p.into())
}
}
impl Clone for Triangle {
fn clone(&self) -> Self {
let verts = self.vertices.to_arr();
Self {
base_vertices: self.base_vertices,
vertices: self.vertices,
orientation: self.orientation,
local_center: self.local_center,
center: self.center,
z_index: self.z_index,
color: self.color,
lines: vec![
Line::new(verts[0], verts[1], self.color),
Line::new(verts[1], verts[2], self.color),
Line::new(verts[2], verts[0], self.color),
],
children: self.children.iter().map(|c| c.clone()).collect(),
}
}
}