Skip to content

Instantly share code, notes, and snippets.

@subnomo
Created October 6, 2018 11:44
Show Gist options
  • Save subnomo/53ee02e0dd94c155b68eca0b30148572 to your computer and use it in GitHub Desktop.
Save subnomo/53ee02e0dd94c155b68eca0b30148572 to your computer and use it in GitHub Desktop.
Donut - donut.c ported to Rust
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)
}
}
[package]
name = "donut"
version = "0.1.0"
authors = ["Alex Taylor <[email protected]>"]
[dependencies]
time = "0.1.37"
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