/* ddd::rig The functions declared in this section of the module relate to rigging apparatuses, such as cameras and lighting. -- Copyright (C) 2017 Davis Remmel This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ use std::io::{self, Write}; use super::Point; use std::cmp::Ordering; #[derive(Default, Copy, Clone)] pub struct Pixel { r: u8, g: u8, b: u8, is_virtual: bool } impl Pixel { pub fn new_rgb(r: u8, g: u8, b: u8) -> Pixel { Pixel { r: r, g: g, b: b, is_virtual: false } } // This returns a virtual pixel pub fn new_virtual() -> Pixel { Pixel { r: 255, g: 255, b: 255, is_virtual: true } } // pub fn invert_rgb(&mut self) { // self.r = ::max_value() - self.r; // self.g = ::max_value() - self.g; // self.b = ::max_value() - self.b; // } } #[derive(Copy, Clone)] struct Coord { x: i64, y: i64 } impl Coord { fn min_value() -> Coord { Coord { x: ::min_value(), y: ::min_value() } } fn max_value() -> Coord { Coord { x: ::max_value(), y: ::max_value() } } } pub struct Sensor { pub pixels: Vec>, pub width: f64, // Centered around origin pub height: f64 // Centered around origin } impl Sensor { pub fn new(pix_wide: usize, pix_high: usize, width: f64, height: f64) -> Sensor { // The sensor is accessed as pixels[y][x] let mut rows: Vec> = vec![]; for _ in 0..pix_high { let mut col: Vec = vec![]; for _ in 0..pix_wide { col.push(Pixel::new_rgb(255, 255, 255)); } rows.push(col); } Sensor { pixels: rows, width: width, height: height } } pub fn pix_center(&self) -> (u16, u16) { ((self.pixels[0].len() / 2) as u16, (self.pixels.len() / 2) as u16) } pub fn pix_bounds(&self) -> ((u16, u16), (u16, u16)) { ((0 as u16, 0 as u16), (self.pixels[0].len() as u16, self.pixels.len() as u16)) } pub fn pix_ratio(&self) -> f64 { self.pixels.len() as f64 / self.width } pub fn read_out(&mut self) { let mut buffer: Vec = vec![]; for y in 0..self.pixels.len() { for x in 0..self.pixels[y].len() { // Write color buffer.push(self.pixels[y][x].r); buffer.push(self.pixels[y][x].g); buffer.push(self.pixels[y][x].b); // Write greyscale // buffer.push(self.pixels[y][x].grey()); // Clear the pixel so the screen will start anew self.pixels[y][x] = Pixel::new_rgb(255, 255, 255); } } let _ = io::stdout().write(&buffer); } } // This is like a pinhole camera, where the focal point sits between the // scene and the sensor. pub struct Camera { pub sensor: Sensor, pub focal_point: Point, pub sensor_distance_z: f64 } impl Camera { pub fn rasterize_faces(&mut self, faces: Vec>) { // First, sort the faces by their average Z index. This is so we // can draw them in the correct order. let mut sorted_faces = faces.clone(); sorted_faces.sort_by(|a, b| { let mut a_avg: Point = Point::new(0.0, 0.0, 0.0); let mut b_avg: Point = Point::new(0.0, 0.0, 0.0); for point in a { a_avg.x = a_avg.x + point.x / a.len() as f64; a_avg.y = a_avg.y + point.y / a.len() as f64; a_avg.z = a_avg.z + point.z / a.len() as f64; } for point in b { b_avg.x = b_avg.x + point.x / a.len() as f64; b_avg.y = b_avg.y + point.y / a.len() as f64; b_avg.z = b_avg.z + point.z / a.len() as f64; } // Get hypotenuse let a_x_diff = a_avg.x - self.focal_point.x; let a_y_diff = a_avg.y - self.focal_point.y; let a_z_diff = a_avg.z - self.focal_point.z; let a_hyp = a_z_diff.hypot(a_x_diff).hypot(a_y_diff); let b_x_diff = b_avg.x - self.focal_point.x; let b_y_diff = b_avg.y - self.focal_point.y; let b_z_diff = b_avg.z - self.focal_point.z; let b_hyp = b_z_diff.hypot(b_x_diff).hypot(b_y_diff); if b_hyp < a_hyp { return Ordering::Less } else if b_hyp > a_hyp { return Ordering::Greater } else { return Ordering::Equal } }); for face in sorted_faces { let mut pix_coords: Vec = vec![]; for point in face { let dist_x = point.x - self.focal_point.x; let dist_y = point.y - self.focal_point.y; let dist_z = point.z - self.focal_point.z; // Re-scale to the distance between the focal point and the sensor let s_dist_x = self.sensor_distance_z / (dist_z / dist_x); let s_dist_y = self.sensor_distance_z / (dist_z / dist_y); // Find the corresponding pixel (from the center origin) let pix_x = (s_dist_x * self.sensor.pix_ratio()).round() as i64 + self.sensor.pix_center().0 as i64; let pix_y = (s_dist_y * self.sensor.pix_ratio()).round() as i64 + self.sensor.pix_center().1 as i64; pix_coords.push(Coord {x: pix_x, y: pix_y}); } fn get_line_pix(p0: Coord, p1: Coord) -> Vec { // This line drawing technique draws the line twice: once // iterating horizontally, and once iterating vertically. If // a line has a very large/small slope, it may be sparse when // iterating in only one dimension. This ensures we have a solid // line. let mut pix: Vec = vec![]; // Order the points so p_a is on the left. let pa = if p0.x < p1.x { p0 } else { p1 }; let pb = if p0.x >= p1.x { p0 } else { p1 }; // Walk the line for x in pa.x..pb.x { // let y = (x as f64 * slope).round() as i64; if 0 != pb.x - pa.x { let y = pa.y + (pb.y - pa.y) * (x - pa.x) / (pb.x - pa.x); pix.push(Coord {x: x, y: y}); } } let pa = if p0.y < p1.y { p0 } else { p1 }; let pb = if p0.y >= p1.y { p0 } else { p1 }; for y in pa.y..pb.y { // let y = (x as f64 * slope).round() as i64; if 1 != pb.y - pa.y { let x = pa.x + (pb.x - pa.x) * (y - pa.y) / (pb.y - pa.y); pix.push(Coord {x: x, y: y}); } } pix } // This will hold the (x,y) coordinates of each pixel that should // be filled in with the component of a line. let mut fill_pix: Vec = vec![]; for i in 0..pix_coords.len() { let p0 = pix_coords[i]; let p1 = pix_coords[(i + 1) % pix_coords.len()]; fill_pix.append(&mut get_line_pix(p0, p1)); } fill_pix.append(&mut pix_coords); // With the coordinates of each pixel, we're going to do a // transformation into a better data type -- a vector that holds // bitmap data. By doing this, we can traverse it to fill the // faces in with a color. // Note: This should eventually become the native storage format // for the pix_coords variable. let mut flat_coords: Vec> = vec![]; // The size of this array is going to be dependent on the maximums // of the pixels in our store. let mut min_fill_bound = Coord::max_value(); let mut max_fill_bound = Coord::min_value(); for coord in &fill_pix { if coord.x < min_fill_bound.x { min_fill_bound.x = coord.x } if coord.y < min_fill_bound.y { min_fill_bound.y = coord.y } if max_fill_bound.x < coord.x { max_fill_bound.x = coord.x } if max_fill_bound.y < coord.y { max_fill_bound.y = coord.y } } // Fill in the flat_coords with blank pixels let fill_width = max_fill_bound.x - min_fill_bound.x; let fill_height = max_fill_bound.y - min_fill_bound.y; for _ in 0..fill_height + 1 { let mut row: Vec = vec![]; for _ in 0..fill_width + 1 { row.push(Pixel::new_virtual()); } flat_coords.push(row); } // Let's put our existing pixel data from the lines into here. for coord in &fill_pix { flat_coords[(coord.y - min_fill_bound.y) as usize][(coord.x - min_fill_bound.x) as usize] = Pixel::new_rgb(0, 0, 0); } // The following will fill the faces (inside the lines) with // pixels that block anything that's behind. for y in 0..flat_coords.len() { let row = &mut flat_coords[y]; // Find the minimum and maximum black pixels in this row let mut left: i64 = -1; let mut right: i64 = -1; let mut passed_left = false; for x in 0..row.len() { let pixel = row[x]; if !passed_left { if !pixel.is_virtual { left = x as i64; } else { if -1 != left { passed_left = true; } } } else { if !pixel.is_virtual { right = x as i64; break; } } } // Did we get both? if -1 != left && -1 != right && 0 < right - left { // Fill between the left and right for i in left as usize + 1..right as usize { row[i] = Pixel::new_rgb(255, 255, 255); } } } // Fill these pixels into the sensor for y in 0..flat_coords.len() { for x in 0..flat_coords[y].len() { // let r_x = min_fill_bound.x as usize + x; // let r_y = min_fill_bound.y as usize + y; let p = Coord { x: min_fill_bound.x + x as i64, y: min_fill_bound.y + y as i64 }; if p.x < (self.sensor.pix_bounds().0).0 as i64 || (self.sensor.pix_bounds().1).0 as i64 <= p.x || p.y < (self.sensor.pix_bounds().0).1 as i64 || (self.sensor.pix_bounds().1).1 as i64 <= p.y { continue } let pixel = flat_coords[y][x]; if !pixel.is_virtual { self.sensor.pixels[p.y as usize][p.x as usize] = pixel; } } } /* for p in fill_pix { // If either of those are larger than our sensor, then we cannot // draw this pixel. if p.x < (self.sensor.pix_bounds().0).0 as i64 || (self.sensor.pix_bounds().1).0 as i64 <= p.x || p.y < (self.sensor.pix_bounds().0).1 as i64 || (self.sensor.pix_bounds().1).1 as i64 <= p.y { return } // Looks good -- put this pixel onto the sensor self.sensor.pixels[p.y as usize][p.x as usize] = Pixel::new(0, 0, 0); } */ } } }