Skip to content

Instantly share code, notes, and snippets.

@Mroik
Last active April 25, 2023 17:30
Show Gist options
  • Save Mroik/54eb2f32ba39ed3fe98a2f0b4ab99066 to your computer and use it in GitHub Desktop.
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.
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