2022-12-30 17:27:47 +01:00
|
|
|
use crossterm::cursor::MoveTo;
|
2023-01-02 13:34:55 +01:00
|
|
|
use crossterm::event::{poll, read, Event, KeyCode};
|
2023-01-02 13:21:56 +01:00
|
|
|
use crossterm::style::{Color, Print, SetForegroundColor};
|
2023-01-02 13:34:55 +01:00
|
|
|
use crossterm::terminal::{enable_raw_mode, Clear, ClearType};
|
|
|
|
use crossterm::{QueueableCommand};
|
|
|
|
use ndarray::{arr1, arr2, Array1};
|
2022-12-30 17:27:47 +01:00
|
|
|
use std::io::{stdout, Write};
|
2022-12-30 17:57:41 +01:00
|
|
|
use std::process::exit;
|
2022-12-30 17:27:47 +01:00
|
|
|
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]>,
|
2023-01-02 13:21:56 +01:00
|
|
|
line_color: Option<Color>,
|
|
|
|
node_color: Option<Color>,
|
2022-12-30 17:27:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[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))?
|
2022-12-30 17:57:41 +01:00
|
|
|
.queue(match (incl > -0.5 && incl < 0.5, incl > 0.) {
|
|
|
|
(true, _) => Print("|"),
|
|
|
|
(false, false) => Print("/"),
|
|
|
|
(false, true) => Print("\\"),
|
2022-12-30 17:27:47 +01:00
|
|
|
})?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn draw_rotating_mesh(meshes: Vec<Mesh>) -> anyhow::Result<()> {
|
2022-12-30 17:57:41 +01:00
|
|
|
let mut angle = (0., 0., 0.);
|
2023-01-02 13:21:56 +01:00
|
|
|
let mut zoom = 1.;
|
2022-12-30 17:27:47 +01:00
|
|
|
|
|
|
|
loop {
|
2022-12-30 17:57:41 +01:00
|
|
|
if poll(Duration::from_millis(10))? {
|
|
|
|
if let Event::Key(key) = read()? {
|
|
|
|
match key.code {
|
|
|
|
KeyCode::Char('w') => angle.0 += 6.,
|
|
|
|
KeyCode::Char('s') => angle.0 -= 6.,
|
|
|
|
KeyCode::Char('a') => angle.2 += 6.,
|
|
|
|
KeyCode::Char('d') => angle.2 -= 6.,
|
|
|
|
KeyCode::Char('q') => angle.1 += 6.,
|
|
|
|
KeyCode::Char('e') => angle.1 -= 6.,
|
2023-01-02 13:21:56 +01:00
|
|
|
KeyCode::Char(',') => zoom = 0.2f32.max(zoom - 0.02),
|
|
|
|
KeyCode::Char('.') => zoom = 1f32.min(zoom + 0.02),
|
2022-12-30 17:57:41 +01:00
|
|
|
KeyCode::Esc | KeyCode::Backspace | KeyCode::Char('c') => exit(0),
|
2023-01-02 13:34:55 +01:00
|
|
|
_ => {}
|
2022-12-30 17:57:41 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
angle.1 += 0.1;
|
|
|
|
}
|
|
|
|
angle.1 %= 360.;
|
|
|
|
|
2022-12-30 17:27:47 +01:00
|
|
|
// rotation and matmul
|
2022-12-30 17:57:41 +01:00
|
|
|
let x_matrix = rotation_matrix(angle.0, Dimension::X);
|
|
|
|
let y_matrix = rotation_matrix(angle.1, Dimension::Y);
|
|
|
|
let z_matrix = rotation_matrix(angle.2, Dimension::Z);
|
2023-01-02 13:21:56 +01:00
|
|
|
let zoom_matrix = scale(zoom, zoom, zoom);
|
2022-12-30 17:27:47 +01:00
|
|
|
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))
|
2022-12-30 17:57:41 +01:00
|
|
|
.map(|pt| x_matrix.dot(&pt))
|
|
|
|
.map(|pt| y_matrix.dot(&pt))
|
|
|
|
.map(|pt| z_matrix.dot(&pt))
|
2023-01-02 13:21:56 +01:00
|
|
|
.map(|pt| zoom_matrix.dot(&pt))
|
2022-12-30 17:27:47 +01:00
|
|
|
.map(|pt| cam_matrix.dot(&pt))
|
|
|
|
.map(|pt| pt + arr1(&[30., 30.])) // draw shift
|
|
|
|
.collect::<Vec<Array1<_>>>();
|
|
|
|
|
2023-01-02 13:21:56 +01:00
|
|
|
// set edge color before edge drawing
|
|
|
|
stdout.queue(SetForegroundColor(mesh.line_color.unwrap_or(Color::White)))?;
|
|
|
|
|
2022-12-30 17:27:47 +01:00
|
|
|
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,
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
|
2023-01-02 13:21:56 +01:00
|
|
|
// set mesh point color
|
|
|
|
stdout.queue(SetForegroundColor(mesh.node_color.unwrap_or(Color::White)))?;
|
|
|
|
|
2022-12-30 17:27:47 +01:00
|
|
|
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()?;
|
|
|
|
|
|
|
|
thread::sleep(Duration::from_millis(10));
|
|
|
|
}
|
|
|
|
|
2022-12-30 18:04:59 +01:00
|
|
|
unreachable!()
|
2022-12-30 17:27:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
fn main() -> anyhow::Result<()> {
|
2022-12-30 17:57:41 +01:00
|
|
|
enable_raw_mode()?;
|
|
|
|
|
2023-01-02 13:21:56 +01:00
|
|
|
let random_rectangle = Mesh {
|
|
|
|
points: vec![
|
|
|
|
arr1(&[-10., 0., 0.]),
|
|
|
|
arr1(&[0., 10., 0.]),
|
|
|
|
arr1(&[10., 0., 0.]),
|
|
|
|
arr1(&[0., -10., 0.]),
|
|
|
|
],
|
2023-01-02 13:34:55 +01:00
|
|
|
edges: vec![[0, 1], [1, 2], [2, 3], [3, 0]],
|
2023-01-02 13:21:56 +01:00
|
|
|
line_color: Some(Color::Red),
|
2023-01-02 13:34:55 +01:00
|
|
|
node_color: Some(Color::DarkRed),
|
2023-01-02 13:21:56 +01:00
|
|
|
};
|
|
|
|
|
2022-12-30 17:27:47 +01:00
|
|
|
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],
|
|
|
|
],
|
2023-01-02 13:21:56 +01:00
|
|
|
line_color: Some(Color::Blue),
|
|
|
|
node_color: Some(Color::White),
|
2022-12-30 17:27:47 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
draw_rotating_mesh(vec![
|
2023-01-02 13:21:56 +01:00
|
|
|
random_rectangle,
|
|
|
|
thing,
|
2022-12-30 17:27:47 +01:00
|
|
|
Mesh {
|
|
|
|
points: cube,
|
|
|
|
edges,
|
2023-01-02 13:21:56 +01:00
|
|
|
line_color: Some(Color::Green),
|
|
|
|
node_color: Some(Color::White),
|
2022-12-30 17:27:47 +01:00
|
|
|
},
|
|
|
|
])?;
|
|
|
|
|
|
|
|
unreachable!()
|
|
|
|
}
|