Last active
April 25, 2023 17:30
-
-
Save Mroik/54eb2f32ba39ed3fe98a2f0b4ab99066 to your computer and use it in GitHub Desktop.
An attempt to 3D rendering on the terminal using ASCII characters. The rendering works just fine, I don't understand why it isn't centered tho. It looks like I'm looking at the cube from the side.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::iter; | |
type Coordinates = (f64, f64, f64); | |
fn get_vector(a: Coordinates, b: Coordinates) -> Coordinates { | |
return (b.0 - a.0, b.1 - a.1, b.2 - a.2); | |
} | |
fn scale(point: Coordinates, scalar: f64) -> Coordinates { | |
return (point.0 * scalar, point.1 * scalar, point.2 * scalar); | |
} | |
fn sum_vect(origin: Coordinates, vector: Coordinates) -> Coordinates { | |
return ( | |
origin.0 + vector.0, | |
origin.1 + vector.1, | |
origin.2 + vector.2, | |
); | |
} | |
struct Renderer { | |
screen_width: usize, | |
screen_height: usize, | |
screen_distance: usize, | |
items: Vec<Box<dyn Item>>, | |
} | |
impl Renderer { | |
fn new(screen_width: usize, screen_height: usize, screen_distance: usize) -> Self { | |
Renderer { | |
screen_width, | |
screen_height, | |
screen_distance, | |
items: vec![], | |
} | |
} | |
fn convert(&self, point: Coordinates) -> Coordinates { | |
let (_, _, z) = point; | |
// Items ideally are between 800 and 0, the screen is 1/10 | |
let center = ( | |
(self.screen_width * 10 / 2) as f64, | |
(self.screen_height * 10 / 2) as f64, | |
0 as f64 | |
); | |
let scalar = self.screen_distance as f64 / z; | |
let vector = get_vector(center, point); | |
return scale(vector, scalar); | |
} | |
fn draw(&self) { | |
let mut z_buffer: Vec<(f64, char)> = iter::repeat((f64::MAX, ' ')) | |
.take(self.screen_width * self.screen_height) | |
.collect(); | |
for item in self.items.as_slice() { | |
let points: Vec<Coordinates> = item.generate_points() | |
.iter() | |
.copied() | |
.map(|point| self.convert(point)) | |
.collect(); | |
for point in points { | |
let x = point.0 as usize; | |
let y = point.1 as usize; | |
let z = point.2; | |
if z < z_buffer[y * self.screen_width + x].0 { | |
z_buffer[y * self.screen_width + x] = (z, item.char()); | |
} | |
} | |
} | |
for y in 0..self.screen_height { | |
for x in 0..self.screen_width { | |
print!("{}", z_buffer[y * self.screen_width + x].1); | |
} | |
println!(); | |
} | |
} | |
fn add_item(&mut self, item: Box<dyn Item>) { | |
self.items.push(item); | |
} | |
} | |
trait Item { | |
fn generate_points(&self) -> Vec<Coordinates>; | |
fn char(&self) -> char; | |
} | |
struct Cube { | |
// Had I used trigonometry we'd only need to store 3 vertex | |
vertex: [Coordinates; 8], | |
char: char, | |
} | |
impl Cube { | |
fn new(vertex: [Coordinates; 8], char: char) -> Self { | |
Cube { | |
vertex, | |
char, | |
} | |
} | |
} | |
impl Item for Cube { | |
fn generate_points(&self) -> Vec<Coordinates> { | |
let mut points: Vec<Coordinates> = vec![]; | |
for x in 0..self.vertex.len() { | |
for y in 0..self.vertex.len() { | |
if x == y { | |
continue; | |
} | |
let mut vector = get_vector(self.vertex[x], self.vertex[y]); | |
vector = scale(vector, 0.01); | |
for mult in 0..100 { | |
points.push(sum_vect(self.vertex[x], scale(vector, mult as f64))); | |
} | |
} | |
} | |
return points; | |
} | |
fn char(&self) -> char { | |
return self.char; | |
} | |
} | |
fn main() { | |
let mut renderer = Renderer::new(80, 60, 10); | |
let offset = (200.0, 100.0, 0.0); | |
let points: [Coordinates; 8] = [ | |
(300.0 + offset.0, 200.0 + offset.1, 40.0 + offset.2), | |
(300.0 + offset.0, 400.0 + offset.1, 40.0 + offset.2), | |
(500.0 + offset.0, 200.0 + offset.1, 40.0 + offset.2), | |
(500.0 + offset.0, 400.0 + offset.1, 40.0 + offset.2), | |
(300.0 + offset.0, 200.0 + offset.1, 240.0 + offset.2), | |
(300.0 + offset.0, 400.0 + offset.1, 240.0 + offset.2), | |
(500.0 + offset.0, 200.0 + offset.1, 240.0 + offset.2), | |
(500.0 + offset.0, 400.0 + offset.1, 240.0 + offset.2), | |
]; | |
renderer.add_item(Box::new(Cube::new(points, '*'))); | |
renderer.draw(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment