term-render/src/main.rs

286 lines
7.2 KiB
Rust

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<Array1<f32>>,
edges: Vec<[usize; 2]>,
}
#[inline]
#[rustfmt::skip]
fn rotation_matrix(angle: f32, dimension: Dimension) -> ndarray::Array2<f32> {
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<f32> {
arr2(&[
[x, 0., 0.],
[0., y, 0.],
[0., 0., z],
])
}
#[inline]
#[rustfmt::skip]
fn ortho_matrix() -> ndarray::Array2<f32> {
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<f32> = Mutex::new(0.);
fn draw_rotating_mesh(meshes: Vec<Mesh>) -> 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::<Vec<Array1<_>>>();
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!()
}