Last active
March 5, 2019 21:21
-
-
Save cfsamson/f1fde90e1fbdcbdfe3e1c4309030a14c to your computer and use it in GitHub Desktop.
Line by line port of the C# code
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 lazy_static::lazy_static; | |
use rand::random; | |
use std::io::Write; | |
use std::ops::{Add, Mul, Not, Rem}; | |
#[derive(Debug, Clone, Copy)] | |
struct Vec3 { | |
x: f32, | |
y: f32, | |
z: f32, | |
} | |
impl Vec3 { | |
fn new_abc(a: f32, b: f32, c: f32) -> Self { | |
Vec3 { x: a, y: b, z: c } | |
} | |
fn new_ab(a: f32, b: f32) -> Self { | |
Vec3 { x: a, y: b, z: 0.0 } | |
} | |
} | |
impl Add for Vec3 { | |
type Output = Vec3; | |
fn add(self, other: Vec3) -> Self { | |
Vec3 { | |
x: self.x + other.x, | |
y: self.y + other.y, | |
z: self.z + other.z, | |
} | |
} | |
} | |
impl Mul for Vec3 { | |
type Output = Vec3; | |
fn mul(self, other: Vec3) -> Self { | |
Vec3 { | |
x: self.x * other.x, | |
y: self.y * other.y, | |
z: self.z * other.z, | |
} | |
} | |
} | |
impl Rem for Vec3 { | |
type Output = f32; | |
fn rem(self, other: Vec3) -> f32 { | |
self.x * other.x + self.y * other.y + self.z * other.z | |
} | |
} | |
impl Not for Vec3 { | |
type Output = Vec3; | |
fn not(self) -> Vec3 { | |
self * (1.0 / (self % self).sqrt()).into() | |
} | |
} | |
impl From<f32> for Vec3 { | |
fn from(n: f32) -> Vec3 { | |
Vec3::new_abc(n, n, n) | |
} | |
} | |
fn min(l: f32, r: f32) -> f32 { | |
if l < r { | |
l | |
} else { | |
r | |
} | |
} | |
fn random_val() -> f32 { | |
random() | |
} | |
fn fmodf(x: f32, y: f32) -> f32 { | |
x % y | |
} | |
fn fabsf(x: f32) -> f32 { | |
x.abs() | |
} | |
fn sqrtf(x: f32) -> f32 { | |
x.sqrt() | |
} | |
fn powf(x: f32, y: f32) -> f32 { | |
x.powf(y) | |
} | |
fn cosf(x: f32) -> f32 { | |
x.cos() | |
} | |
fn sinf(x: f32) -> f32 { | |
x.sin() | |
} | |
fn box_test(position: Vec3, lower_left: Vec3, upper_right: Vec3) -> f32 { | |
let lower_left = position + lower_left * Vec3::from(-1.0); | |
let upper_right = upper_right + position * Vec3::from(-1.0); | |
-min( | |
min( | |
min(lower_left.x, upper_right.x), | |
min(lower_left.y, upper_right.y), | |
), | |
min(lower_left.z, upper_right.z), | |
) | |
} | |
const HIT_NONE: u8 = 0; | |
const HIT_LETTER: u8 = 1; | |
const HIT_WALL: u8 = 2; | |
const HIT_SUN: u8 = 3; | |
lazy_static! { | |
static ref LETTERS: Vec<u8> = { | |
let x: String = [ | |
"5O5_", "5W9W", "5_9_", // P (without curve) | |
"AOEO", "COC_", "A_E_", // I | |
"IOQ_", "I_QO", // X | |
"UOY_", "Y_]O", "WW[W", // A | |
"aOa_", "aWeW", "a_e_", "cWiO" // R (without curve) | |
].concat(); | |
x.chars().map(|c| c as u8).collect::<Vec<u8>>() | |
}; | |
static ref CURVES: [Vec3; 2] = { | |
[Vec3::new_abc(-11.0, 6.0, 0.0), Vec3::new_abc(11.0, 6.0, 0.0)] | |
}; | |
} | |
fn query_database(position: Vec3, hit_type: &mut u8) -> f32 { | |
let mut distance = std::f32::MAX; | |
let mut f = position; | |
f.z = 0.0; | |
let letter_count = LETTERS.len(); | |
for i in (0..letter_count).step_by(4) { | |
let begin = | |
Vec3::new_ab((LETTERS[i] - 79) as f32, (LETTERS[i + 1] - 79) as f32) * Vec3::from(0.5); | |
let e = Vec3::new_ab((LETTERS[i + 2] - 79) as f32, (LETTERS[i + 3] - 79) as f32) | |
* Vec3::from(0.5) | |
+ begin * Vec3::from(-1.0); | |
let o_part1 = -min((begin + f * Vec3::from(-1.0)) % e / (e % e), 0.0); | |
let o = f + (begin + e * min(o_part1, 1.0).into()) * Vec3::from(-1.0); | |
distance = min(distance, o % o); | |
} | |
distance = sqrtf(distance); | |
for curve in CURVES.iter().rev() { | |
let o = f + *curve * Vec3::from(-1.0); | |
let temp = if o.x > 0.0 { | |
fabsf(sqrtf(o % o)) - 2.0 | |
} else { | |
sqrtf(o % o) | |
}; | |
distance = min(distance, temp); | |
} | |
distance = powf(distance.powi(8) + position.z.powi(8), 0.125) - 0.5; | |
*hit_type = HIT_LETTER; | |
let room_dist = min( | |
-min( | |
box_test( | |
position, | |
Vec3::new_abc(-30.0, -0.5, -30.0), | |
Vec3::new_abc(30.0, 18.0, 30.0), | |
), | |
box_test( | |
position, | |
Vec3::new_abc(-25.0, 17.0, -25.0), | |
Vec3::new_abc(25.0, 20.0, 25.0), | |
), | |
), | |
box_test( | |
Vec3::new_abc(fmodf(fabsf(position.x), 8.0), position.y, position.z), | |
Vec3::new_abc(1.5, 18.5, -25.0), | |
Vec3::new_abc(6.5, 20.0, 25.0), | |
), | |
); | |
if room_dist < distance { | |
distance = room_dist; | |
*hit_type = HIT_WALL; | |
} | |
let sun = 19.9 - position.y; | |
if sun < distance { | |
distance = sun; | |
*hit_type = HIT_SUN; | |
} | |
distance | |
} | |
fn ray_marching(origin: Vec3, direction: Vec3, hit_pos: &mut Vec3, hit_norm: &mut Vec3) -> u8 { | |
let mut hit_type = HIT_NONE; | |
let mut no_hit_count = 0; | |
let mut total_d = 0.0; | |
while total_d < 100.0 { | |
*hit_pos = origin + direction * total_d.into(); | |
let d = query_database(*hit_pos, &mut hit_type); | |
no_hit_count += 1; | |
if d < 0.01 || no_hit_count > 99 { | |
*hit_norm = !Vec3::new_abc( | |
query_database(*hit_pos + Vec3::new_ab(0.01, 0.0), &mut no_hit_count) - d, | |
query_database(*hit_pos + Vec3::new_ab(0.0, 0.01), &mut no_hit_count) - d, | |
query_database(*hit_pos + Vec3::new_abc(0.0, 0.0, 0.01), &mut no_hit_count) - d, | |
); | |
return hit_type; | |
} | |
total_d += d; | |
} | |
0 | |
} | |
fn trace(mut origin: Vec3, mut direction: Vec3) -> Vec3 { | |
let mut sampled_position = Vec3::from(0.0); | |
let mut normal = Vec3::from(0.0); | |
let mut color = Vec3::from(0.0); | |
let mut attenuation = Vec3::from(1.0); | |
let light_direction = !Vec3::new_abc(0.6, 0.6, 1.0); | |
for _ in (0..3).rev() { | |
let hit_type = ray_marching(origin, direction, &mut sampled_position, &mut normal); | |
if hit_type == HIT_NONE { | |
break; | |
} | |
if hit_type == HIT_LETTER { | |
direction = direction + normal * ((normal % direction) * -2.0).into(); | |
origin = sampled_position + direction * Vec3::from(0.1); | |
attenuation = attenuation * Vec3::from(0.2); | |
} | |
if hit_type == HIT_WALL { | |
let incidence = normal % light_direction; | |
let p = 6.283185 * random_val(); | |
let c = random_val(); | |
let s = sqrtf(1.0 - c); | |
let g = if normal.z < 0.0 { -1.0 } else { 1.0 }; | |
let u = -1.0 / (g + normal.z); | |
let v = normal.x * normal.y * u; | |
direction = Vec3::new_abc(v, g + normal.y * normal.y * u, -normal.y) | |
* cosf(p).into() | |
* s.into() | |
+ Vec3::new_abc(1.0 + g * normal.x * normal.x * u, g * v, -g * normal.x) | |
* (Vec3::from(sinf(p)) * Vec3::from(s)) | |
+ normal * sqrtf(c).into(); | |
origin = sampled_position + direction * Vec3::from(0.1); | |
attenuation = attenuation * Vec3::from(0.2); | |
if incidence > 0.0 | |
&& ray_marching( | |
sampled_position + normal * Vec3::from(0.1), | |
light_direction, | |
&mut sampled_position, | |
&mut normal, | |
) == HIT_SUN | |
{ | |
color = color + attenuation * Vec3::new_abc(500.0, 400.0, 100.0) * incidence.into(); | |
} | |
} | |
if hit_type == HIT_SUN { | |
color = color + attenuation * Vec3::new_abc(50.0, 80.0, 100.0); | |
break; | |
} | |
} | |
color | |
} | |
fn main() { | |
let w = 960.0; | |
let h = 540.0; | |
let samples_count = 2; | |
let position = Vec3::new_abc(-22.0, 5.0, 25.0); | |
let goal = !(Vec3::new_abc(-3.0, 4.0, 0.0) + position * Vec3::from(-1.0)); | |
let left = !Vec3::new_abc(goal.z, 0.0, -goal.x) * (1.0 / w).into(); | |
let up = Vec3::new_abc( | |
goal.y * left.z - goal.z * left.y, | |
goal.z * left.x - goal.x * left.z, | |
goal.x * left.y - goal.y * left.x, | |
); | |
let filename = String::from("output-rust.ppm"); | |
println!( | |
"Width: = {}, Height: = {}, Samples = {}", | |
w, h, samples_count | |
); | |
println!("Writing data to {}", filename); | |
let mut file = std::fs::File::create(filename).unwrap(); | |
let header: String = format!("P6 {} {} 255 ", w, h); | |
file.write_all(header.as_bytes()).unwrap(); | |
for y in (0..h as u64).rev() { | |
for x in (0..w as u64).rev() { | |
let mut color = Vec3::from(0.0); | |
for _ in (0..samples_count).rev() { | |
color = color | |
+ trace( | |
position, | |
!(goal | |
+ left * (x as f32 - w / 2.0 + random_val()).into() | |
+ up * (y as f32 - h / 2.0 + random_val()).into()), | |
); | |
} | |
color = color * (1.0 / samples_count as f32).into() + (14.0 / 241.0).into(); | |
let o: Vec3 = color + Vec3::from(1.0); | |
color = Vec3::new_abc(color.x / o.x, color.y / o.y, color.z / o.z) * Vec3::from(255.0); | |
file.write_all(&[color.x as u8, color.y as u8, color.z as u8]) | |
.unwrap(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment