use crossterm::cursor::MoveTo; use crossterm::style::Attribute::Dim; use crossterm::style::Print; use crossterm::terminal::{Clear, ClearType}; use crossterm::{execute, QueueableCommand}; use ndarray::{arr1, arr2, Array1, Array2}; use std::io::{stdout, Write}; use std::sync::Mutex; use std::thread; use std::time::Duration; const X_DRAW_SCALING_FACTOR: f32 = 2.8; enum Dimension { X, Y, Z, } struct Mesh { points: Vec>, edges: Vec<[usize; 2]>, } #[inline] #[rustfmt::skip] fn rotation_matrix(angle: f32, dimension: Dimension) -> ndarray::Array2 { let sin = angle.to_radians().sin(); let cos = angle.to_radians().cos(); match dimension { Dimension::X => arr2(&[ [1., 0., 0.], [0., cos, -sin], [0., sin, cos] ]), Dimension::Y => arr2(&[ [cos, 0., -sin], [0., 1., 0.], [sin, 0., cos] ]), Dimension::Z => arr2(&[ [cos, -sin, 0.], [sin, cos, 0.], [0., 0., 1.] ]) } } #[inline] #[rustfmt::skip] fn scale(x: f32, y: f32, z: f32) -> ndarray::Array2 { arr2(&[ [x, 0., 0.], [0., y, 0.], [0., 0., z], ]) } #[inline] #[rustfmt::skip] fn ortho_matrix() -> ndarray::Array2 { arr2(&[ [1., 0., 0.], [0., 1., 0.], ]) } fn plot_line( origin: &[i32; 2], destination: &[i32; 2], out: &mut impl Write, ) -> anyhow::Result<()> { assert!(origin[0] > 0 && destination[0] > 0); let (origin, destination) = if origin[0] < destination[0] { (origin, destination) } else { (destination, origin) }; let (xdiff, ydiff) = ( destination[0].abs_diff(origin[0]), destination[1].abs_diff(origin[1]), ); if ydiff > xdiff { plot_line_vertical(origin, destination, out) } else { plot_line_horizontal(origin, destination, out) } } /// # CONTRACT /// * origin is *left of* destination (`origin[0] <= destination[0]`) /// * inclination from origin to destination fulfills `-1 <= incl <= 1` /// * line is inside (u16::MAX x u16::MAX) space fn plot_line_horizontal( origin: &[i32; 2], destination: &[i32; 2], out: &mut impl Write, ) -> anyhow::Result<()> { // assert!(destination[0] > origin[0]); let incl = (destination[1] - origin[1]) as f32 / (destination[0] - origin[0]) as f32; // assert!(((-1.)..=1.).contains(&incl)); for i in 0..(destination[0] - origin[0]) { // CONTRACT: i is in u16 space let x = (i + origin[0]) as u16; // CONTRACT: line points are in u16 space let y = ((incl * i as f32).round() + origin[1] as f32) as u16; out.queue(MoveTo(x, y))? .queue(match (incl < -0.4, incl < 0.4) { (false, true) => Print("-"), (false, false) => Print("\\"), (true, true) => Print("/"), _ => unreachable!(), })?; } Ok(()) } /// # CONTRACT /// * origin is *left of* destination (`origin[0] <= destination[0]`) /// * inclination from origin to destination fulfills `incl <= -1` or `incl >= 1` /// * line is inside (u16::MAX x u16::MAX) space fn plot_line_vertical( origin: &[i32; 2], destination: &[i32; 2], out: &mut impl Write, ) -> anyhow::Result<()> { let (origin, destination) = if origin[1] < destination[1] { (origin, destination) } else { (destination, origin) }; let incl = (destination[0] - origin[0]) as f32 / (destination[1] - origin[1]) as f32; // assert!((..=(-1.)).contains(&incl) || (1.0..).contains(&incl)); for i in 0..destination[1] - origin[1] { // CONTRACT: i is in u16 space let y = (i + origin[1]) as u16; // CONTRACT: line points are in u16 space let x = ((incl * i as f32).round() + origin[0] as f32) as u16; out.queue(MoveTo(x, y))? .queue(match (incl > -2. && incl < 2., incl > 0.) { (true, false) => Print("/"), (true, true) => Print("\\"), _ => Print("|"), })?; } Ok(()) } static ANGLE: Mutex = Mutex::new(0.); fn draw_rotating_mesh(meshes: Vec) -> anyhow::Result<()> { let mut angle = 0.; loop { // rotation and matmul let rot_matrix = rotation_matrix(angle, Dimension::Z); let cam_matrix = ortho_matrix(); let mut stdout = stdout(); stdout.queue(Clear(ClearType::All))?; for mesh in &meshes { let points = &mesh.points; let edges = &mesh.edges; let projected_points = points .iter() .map(|pt| scale(1.6, 1.6, 1.6).dot(pt)) .map(|pt| rotation_matrix((angle * 10.) % 360., Dimension::X).dot(&pt)) .map(|pt| rotation_matrix((angle * 2.) % 360., Dimension::Y).dot(&pt)) .map(|pt| rot_matrix.dot(&pt)) .map(|pt| cam_matrix.dot(&pt)) .map(|pt| pt + arr1(&[30., 30.])) // draw shift .collect::>>(); for edge in edges.iter() { let origin = &projected_points[edge[0]]; let dest = &projected_points[edge[1]]; plot_line( &[ (origin[0] * X_DRAW_SCALING_FACTOR).round() as i32, origin[1] as i32, ], &[(dest[0] * X_DRAW_SCALING_FACTOR) as i32, dest[1] as i32], &mut stdout, )?; } for pt in &projected_points { let pt = pt; stdout .queue(MoveTo((pt[0] * X_DRAW_SCALING_FACTOR) as u16, pt[1] as u16))? .queue(Print("■"))?; } } stdout.flush()?; angle += 0.1; angle %= 360.; thread::sleep(Duration::from_millis(10)); } Ok(()) } fn main() -> anyhow::Result<()> { let cube = vec![ arr1(&[-10., -10., 10.]), arr1(&[-10., 10., 10.]), arr1(&[-10., -10., -10.]), arr1(&[-10., 10., -10.]), arr1(&[10., -10., 10.]), arr1(&[10., 10., 10.]), arr1(&[10., -10., -10.]), arr1(&[10., 10., -10.]), arr1(&[-6., 0., 0.]), arr1(&[6., 0., 0.]), ]; let edges = vec![ // left face [0usize, 1], [0, 2], [1, 3], [2, 3], // right face [4, 5], [4, 6], [5, 7], [6, 7], // center edges [0, 8], [3, 8], [4, 9], [7, 9], ]; let thing = Mesh { points: vec![ arr1(&[0., 0., 6.]), arr1(&[0., 0., -6.]), arr1(&[0., 6., 0.]), arr1(&[0., -6., 0.]), arr1(&[6., 0., 0.]), arr1(&[-6., 0., 0.]), ], edges: vec![ [0, 2], [0, 3], [0, 4], [0, 5], [1, 2], [1, 3], [1, 4], [1, 5], [3, 4], [3, 5], [2, 4], [2, 5], ], }; draw_rotating_mesh(vec![ Mesh { points: cube, edges, }, thing, ])?; loop {} unreachable!() }