extern crate rand; use std::cmp::max; use std::f32::consts::PI; use std::io::Cursor; use std::ops; use std::ops::Rem; use image::ImageFormat; use image::io::Reader as ImageReader; use rand::prelude::*; struct Circle { x: i32, y: i32, r: i32, } impl Circle {} fn createCircle() -> Circle { Circle { x: 0, y: 0, r: 0, } } #[derive(Clone, Copy, Debug, Default)] struct Vector2f { x: f32, y: f32 } #[derive(Clone, Copy, Debug, Default)] struct Vector2i { x: i32, y: i32 } #[derive(Clone, Copy, Debug, Default)] struct Vector3f { x: f32, y: f32, z: f32 } #[derive(Clone, Copy, Debug, Default)] struct Vector4f { x: f32, y: f32, z: f32, w: f32 } fn dot(vec_a: Vector3f, vec_b: Vector3f) -> f32 { vec_a.x * vec_b.x + vec_a.y * vec_b.y + vec_a.z * vec_b.z } fn mult(vec: Vector3f, scalar: f32) -> Vector3f { Vector3f { x: vec.x * scalar, y: vec.y * scalar, z: vec.z * scalar, } } fn sub(vec_a: Vector3f, vec_b: Vector3f) -> Vector3f { Vector3f { x: vec_a.x - vec_b.x, y: vec_a.y - vec_b.y, z: vec_a.z - vec_b.z, } } fn add(vec_a: Vector3f, vec_b: Vector3f) -> Vector3f { Vector3f { x: vec_a.x + vec_b.x, y: vec_a.y + vec_b.y, z: vec_a.z + vec_b.z, } } fn mix(a: Vector3f, b: Vector3f, mixValue: f32) -> Vector3f { add(mult(a, (1.0 - mixValue)), mult(b, mixValue)) } fn normalize(ray: Vector3f) -> Vector3f { let multiplier = (ray.x * ray.x + ray.y * ray.y + ray.z * ray.z).sqrt(); Vector3f { x: ray.x / multiplier, y: ray.y / multiplier, z: ray.z / multiplier, } } fn intersect(ray_orig: Vector3f, ray_dir: Vector3f, sphere_center: Vector3f, sphere_radius: f32) -> Option { let mut t0 = 0.0; let mut t1 = 0.0; let L = sub(ray_orig, sphere_center); let a = dot(ray_dir, ray_dir); let b = 2.0 * dot(L, ray_dir); let c = dot(L, L) - sphere_radius; solve_quadratic(a, b, c, t0, t1) } fn solve_quadratic(a: f32, b: f32, c: f32, mut x0: f32, mut x1: f32) -> Option { let discr: f32 = b * b - 4.0 * a * c; if (discr < 0.0) { return None; } else if (discr == 0.0) { x0 = -0.5 * b / a; x1 = x0; } else { let q = if b > 0.0 { -0.5 * (b + discr.sqrt()) } else { -0.5 * (b - discr.sqrt()) }; x0 = q / a; x1 = c / q; } match f32::max(x0, x1) > 0.0 { true => { Some(f32::max(x0, x1)) } false => { None } } } fn get_surface_data(phit: Vector3f, nhit: Vector3f, sphere_center: Vector3f) -> Vector2f { let nhit = sub(phit, sphere_center); let nhit = normalize(nhit); // In this particular case, the normal is simular to a point on a unit sphere // centred around the origin. We can thus use the normal coordinates to compute // the spherical coordinates of Phit. // atan2 returns a value in the range [-pi, pi] and we need to remap it to range [0, 1] // acosf returns a value in the range [0, pi] and we also need to remap it to the range [0, 1] Vector2f { x: (1.0 + (nhit.x).atan2(nhit.z) / PI) * 0.5, y: (nhit.y).acos() / PI, } } fn main() { let mut rng = rand::thread_rng(); let view_res = Vector2i { x: 800, y: 800 }; let mut circles = Vec::new(); for i in 0..100 { circles.push(Circle { x: (rng.gen::() * view_res.x as f32) as i32, y: (rng.gen::() * view_res.y as f32) as i32, r: 10, }); } let mut viewport_matrix = vec![Vector3f { x: 0.0, y: 0.0, z: 0.0, }; (view_res.x as usize * view_res.y as usize * 4)]; for y in (-view_res.y / 2)..(view_res.y / 2) { for x in (-view_res.x / 2)..(view_res.x / 2) { let ray = Vector3f { z: -800.0, x: x as f32, y: y as f32 }; let ray = Vector3f { x: (ray.z * (1.57 as f32).sin() + ray.x * (1.57 as f32).cos()), y: (ray.y), z: (ray.z * (1.57 as f32).cos() - ray.x * (1.57 as f32).sin()), }; let ray = normalize(ray); let index = ((x + view_res.x / 2) + view_res.x * (y + view_res.y / 2)) as usize; viewport_matrix[index] = Vector3f { x: ray.x, y: ray.y, z: ray.z, }; } } // Create a new ImgBuf with width: imgx and height: imgy let mut imgbuf = image::ImageBuffer::new(view_res.x as u32, view_res.y as u32); let sphere_center = Vector3f { x: -70.0, y: 20.0, z: -10.0 }; let sphere_color = Vector3f { x: 78.0, y: 22.0, z: 125.0, }; let radius = 10.0; // Iterate over the coordinates and pixels of the image for (x, y, pixel) in imgbuf.enumerate_pixels_mut() { let index = (x + view_res.x as u32 * y) as usize; let orig = Vector3f { x: 0.0, y: 0.0, z: 0.0, }; let dir = viewport_matrix[index]; match intersect(orig, dir, sphere_center, radius) { None => {} Some(t) => { let phit = add(orig, mult(dir, t)); let nhit = Vector3f::default(); let tex = get_surface_data(phit, nhit, sphere_center); let scale = 4.0; let pattern = match ((tex.x * scale).rem(1.0) > 0.5) ^ ((tex.y * scale).rem(1.0) > 0.5) { true => { 1.0 } false => { 0.0 } }; let ndir = Vector3f { x: -dir.x, y: -dir.y, z: -dir.z, }; let hit_color = mult(mix(sphere_color, mult(sphere_color, 0.8), f32::max(0.0, dot(nhit, ndir))), pattern); *pixel = image::Rgb([hit_color.x as u8, hit_color.y as u8, hit_color.z as u8]); } }; } // A redundant loop to demonstrate reading image data // for x in 0..view_res.x { // for y in 0..view_res.y { // let pixel = imgbuf.get_pixel_mut(x as u32, y as u32); // let data: image::Rgb = *pixel; // read the prexisting data if needed // *pixel = image::Rgb([0, 0, 255]); // } // } // Save the image as “fractal.png”, the format is deduced from the path imgbuf.save("fractal.png").unwrap(); }