Created
October 6, 2018 11:44
-
-
Save subnomo/53ee02e0dd94c155b68eca0b30148572 to your computer and use it in GitHub Desktop.
Donut - donut.c ported to Rust
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::fmt; | |
pub struct Array2D<T> { | |
pub items: Vec<Vec<T>>, | |
length: (usize, usize), | |
} | |
impl<T: Copy> Array2D<T> { | |
pub fn new(value: T, num_rows: usize, num_columns: usize) -> Array2D<T> { | |
let mut items = Vec::new(); | |
for _ in 0..num_rows { | |
let mut v = Vec::new(); | |
for _ in 0..num_columns { | |
v.push(value); | |
} | |
items.push(v); | |
} | |
Array2D { | |
items: items, | |
length: (num_rows, num_columns) | |
} | |
} | |
pub fn get(&self, row: usize, column: usize) -> &T { | |
&self.items[row][column] | |
} | |
pub fn set(&mut self, row: usize, column: usize, value: T) { | |
self.items[row][column] = value; | |
} | |
pub fn len(&self) -> (usize, usize) { | |
self.length | |
} | |
} | |
impl<T: Copy + ToString> fmt::Display for Array2D<T> { | |
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
let mut s = String::new(); | |
for i in 0..self.length.1 { | |
for j in 0..self.length.0 { | |
s.push_str(&self.get(i, j).to_string()); | |
} | |
s.push_str("\n"); | |
} | |
write!(f, "{}", s) | |
} | |
} | |
impl<T: Copy + fmt::Debug> fmt::Debug for Array2D<T> { | |
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
let mut s = String::from("[\n"); | |
for i in 0..self.length.0 { | |
s.push_str(" ["); | |
for j in 0..self.length.1 { | |
s.push_str(&format!("{:?}", &self.get(i, j))); | |
if j < self.length.1 - 1 { | |
s.push_str(", "); | |
} | |
} | |
s.push_str("]"); | |
if i < self.length.0 - 1 { | |
s.push_str(","); | |
} | |
s.push_str("\n"); | |
} | |
s.push_str("]"); | |
write!(f, "{}", s) | |
} | |
} |
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
[package] | |
name = "donut" | |
version = "0.1.0" | |
authors = ["Alex Taylor <[email protected]>"] | |
[dependencies] | |
time = "0.1.37" |
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
extern crate time; | |
use std::thread; | |
use std::time::Duration; | |
use std::f32::consts::{PI}; | |
use std::process::Command; | |
use time::precise_time_ns; | |
mod array2d; | |
use array2d::Array2D; | |
const SCREEN_WIDTH: usize = 35; | |
const SCREEN_HEIGHT: usize = 35; | |
const THETA_SPACING: f32 = 0.07; | |
const PHI_SPACING: f32 = 0.02; | |
const R1: f32 = 1.0; | |
const R2: f32 = 2.0; | |
const K2: f32 = 5.0; | |
// Calculate K1 based on screen size: the maximum x-distance occurs | |
// roughly at the edge of the torus, which is at x = R1 + R2, z = 0. | |
// | |
// We want that to be displaced 3/8ths of the width of the screen, | |
// which is 3/4th of the way from the center to the side of the screen. | |
// (SCREEN_WIDTH * 3) / 8 = K1 * (R1 + R2) / (K2 + 0) | |
// (SCREEN_WIDTH * K2 * 3) / (8 * (R1 + R2)) = K1 | |
const K1: f32 = (SCREEN_WIDTH as f32 * K2 * 3.0) / (8.0 * (R1 + R2)); | |
fn main() { | |
// let size = terminal_size(); | |
let mut width: u16 = 35; | |
let mut height: u16 = 35; | |
/* | |
if let Some((Width(w), Height(h))) = size { | |
// Both use height - for now | |
// width = h; | |
// height = h; | |
// println!("Width: {}, Height: {}", w, h); | |
} | |
*/ | |
if ! cfg!(target_os = "windows") { | |
print!("\x1b[2J"); | |
} | |
let mut A: f32 = 0.0; | |
let mut B: f32 = 0.0; | |
// Only draw 30 times a second | |
const FPS: u16 = 30; | |
const FPNS: u64 = 1000000000 / FPS as u64; | |
let mut origin: u64 = precise_time_ns(); | |
loop { | |
let now: u64 = precise_time_ns(); | |
if now - origin > FPNS { | |
render_frame(A, B); | |
A += 0.04; | |
B += 0.02; | |
origin = now; | |
} | |
} | |
} | |
fn render_frame(A: f32, B: f32) { | |
// Precompute sines and cosines of A and B (the axes of rotation) | |
let cosA: f32 = A.cos(); | |
let sinA: f32 = A.sin(); | |
let cosB: f32 = B.cos(); | |
let sinB: f32 = B.sin(); | |
let mut output: Array2D<char> = Array2D::new(' ', SCREEN_HEIGHT, SCREEN_WIDTH); | |
let mut zbuffer: Array2D<f32> = Array2D::new(0.0, SCREEN_HEIGHT, SCREEN_WIDTH); | |
// theta goes around the cross-sectional circle of a torus | |
let mut theta: f32 = 0.0; | |
while theta < 2.0 * PI { | |
theta += THETA_SPACING; | |
// Precompute sines and cosines of theta | |
let costheta: f32 = theta.cos(); | |
let sintheta: f32 = theta.sin(); | |
// phi goes around the center of revolution of a torus | |
let mut phi: f32 = 0.0; | |
while phi < 2.0 * PI { | |
phi += PHI_SPACING; | |
// Precompute sines and cosines of phi | |
let cosphi: f32 = phi.cos(); | |
let sinphi: f32 = phi.sin(); | |
// The x,y coordinate of the circle, before revolving (factored | |
// out of the above equations) | |
let circlex: f32 = R2 + R1 * costheta; | |
let circley: f32 = R1 * sintheta; | |
// Final 3D (x,y,z) coordinate after rotations, directly from our math above | |
let x: f32 = circlex * (cosB * cosphi + sinA * sinB * sinphi) - circley * cosA * sinB; | |
let y: f32 = circlex * (sinB * cosphi - sinA * cosB * sinphi) + circley * cosA * cosB; | |
let z: f32 = K2 + cosA * circlex * sinphi + circley * sinA; | |
let ooz: f32 = 1.0 / z; // "one over z" | |
// x and y projection. Note that y is negated here, because y | |
// goes up in 3D space but down on 2D displays. | |
let xp: i32 = (SCREEN_WIDTH as i32) / 2 + ((K1 * ooz * x) as i32); | |
let yp: i32 = (SCREEN_HEIGHT as i32) / 2 - ((K1 * ooz * y) as i32); | |
// Calculate luminance. Ugly, but correct. | |
let L: f32 = cosphi * costheta * sinB - cosA * costheta * sinphi - | |
sinA * sintheta + cosB * (cosA * sintheta - costheta * sinA * sinphi); | |
// L ranges from -sqrt(2) to +sqrt(2). If it's < 0, the surface | |
// is pointing away from us, so we won't bother trying to plot it. | |
if L > 0.0 { | |
// Test against the z-buffer. Larger 1/z means the pixel is | |
// closer to the viewer than what's already plotted. | |
if ooz > *zbuffer.get(xp as usize, yp as usize) { | |
zbuffer.set(xp as usize, yp as usize, ooz); | |
let luminance_index: f32 = L * 8.0; | |
// luminance_index is now in the range 0..11 (8 * sqrt(2) = 11.3) | |
// now we lookup the character corresponding to the | |
// luminance and plot it in our output: | |
let symbols: &'static str = ".,-~:;=!*#$@"; | |
if let Some(symbol) = symbols.chars().nth(luminance_index as usize) { | |
output.set(xp as usize, yp as usize, symbol); | |
} | |
} | |
} | |
} | |
} | |
if cfg!(target_os = "windows") { | |
Command::new("cmd") | |
.args(&["/C", "cls"]) | |
.status() | |
.unwrap_or_else(|e| { | |
panic!("Could not clear screen! Error: {}", e); | |
}); | |
} else { | |
print!("\x1b[H"); | |
} | |
println!("{}", output); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment