From 6011ee68b4b55b4651de9f5c32ea12418fd46c69 Mon Sep 17 00:00:00 2001 From: Yandrik Date: Fri, 30 Dec 2022 17:27:47 +0100 Subject: [PATCH] feat: implemented renderer --- .gitignore | 1 + .idea/.gitignore | 8 ++ .idea/modules.xml | 8 ++ .idea/term-render.iml | 11 ++ .idea/vcs.xml | 6 + Cargo.lock | 313 ++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 11 ++ src/main.rs | 285 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 643 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/modules.xml create mode 100644 .idea/term-render.iml create mode 100644 .idea/vcs.xml create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..35f98d1 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/term-render.iml b/.idea/term-render.iml new file mode 100644 index 0000000..c254557 --- /dev/null +++ b/.idea/term-render.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..4b22c90 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,313 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crossterm" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" +dependencies = [ + "bitflags", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" +dependencies = [ + "winapi", +] + +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matrixmultiply" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add85d4dd35074e6fedc608f8c8f513a3548619a9024b751949ef0e8e45a4d84" +dependencies = [ + "rawpointer", +] + +[[package]] +name = "mio" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "ndarray" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "rawpointer", +] + +[[package]] +name = "num-complex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "signal-hook" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "term-render" +version = "0.1.0" +dependencies = [ + "anyhow", + "crossterm", + "ndarray", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9648d47 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "term-render" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.68" +crossterm = "0.25.0" +ndarray = "0.15.6" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..8e35003 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,285 @@ +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!() +}