use crate::{geom::Point, ray::RayResult};
use std::mem::swap;
use super::{ExportImage, RenderImage};
pub struct Image {
width: usize,
height: usize,
pixels: Vec<(f32, f32, f32)>,
lightpower: f32,
}
impl Image {
pub fn new(width: usize, height: usize) -> Self {
let len = width * height;
let pixels: Vec<(f32, f32, f32)> = vec![(0.0, 0.0, 0.0); len];
Image {
width,
height,
pixels,
lightpower: 0.0,
}
}
#[inline(always)]
fn plot(&self, colour: (f32, f32, f32), pixel: usize, intensity: f32) {
if pixel >= self.pixels.len() {
return;
};
unsafe {
let s = (self as *const Self) as *mut Self;
(*s).pixels[pixel].0 += colour.0 * intensity;
(*s).pixels[pixel].1 += colour.1 * intensity;
(*s).pixels[pixel].2 += colour.2 * intensity;
}
}
#[inline(always)]
fn inner_draw_line(&self, ray: &RayResult) {
let bounds =
Self::check_bounds(ray.origin, ray.termination, self.width - 1, self.height + 2);
if bounds.is_none() {
return;
}
let colour = ray.color();
let (p0, p1) = bounds.unwrap();
let mut hx: i64 = 1;
let mut hy: i64 = self.width as i64;
let mut x0 = p0.x;
let mut y0 = p0.y;
let mut x1 = p1.x;
let mut y1 = p1.y;
let dx = (x1 - x0).abs();
let dy = (y1 - y0).abs();
if dy > dx {
swap(&mut x0, &mut y0);
swap(&mut x1, &mut y1);
swap(&mut hx, &mut hy);
}
if x0 > x1 {
swap(&mut x0, &mut x1);
swap(&mut y0, &mut y1);
}
let dx = x1 - x0;
let dy = y1 - y0;
let gradient = if dx == 0.0 { 1.0 } else { dy / dx };
const SHIFT: f64 = 0.25;
x0 -= SHIFT;
x1 += SHIFT;
y0 -= gradient * SHIFT;
y1 += gradient * SHIFT;
let br = 128.0 * f64::sqrt(dx * dx + dy * dy) / dx;
let xend = x0.round();
let yend = y0 + gradient * (xend - x0);
let xpxl1: i64 = xend as i64;
let ypxl1: i64 = yend.floor() as i64;
let xgap = br * (1.0 - (x0 + 0.5) + xend); let ygap = yend - yend.floor();
self.plot(
colour,
(xpxl1 * hx + ypxl1 * hy) as usize,
(xgap * (1.0 - ygap)) as f32,
);
self.plot(
colour,
(xpxl1 * hx + (ypxl1 + 1) * hy) as usize,
(xgap * ygap) as f32,
);
let mut intery = yend + gradient;
let xend = x1.round();
let yend = y1 + gradient * (xend - x1);
let xpxl2: i64 = xend as i64;
let ypxl2: i64 = yend.floor() as i64;
let xgap = br * (1.0 - (x1 + 0.5) + xend); let ygap = yend - yend.floor();
self.plot(
colour,
(xpxl2 * hx + ypxl2 * hy) as usize,
(xgap * (1.0 - ygap)) as f32,
);
self.plot(
colour,
(xpxl2 * hx + (ypxl2 + 1) * hy) as usize,
(xgap * ygap) as f32,
);
for x in xpxl1 + 1..xpxl2 {
let iy: i64 = intery.floor() as i64;
let fy: f64 = intery - intery.floor();
self.plot(
colour,
(x * hx + iy * hy) as usize,
(br * (1.0 - fy)) as f32,
);
self.plot(colour, (x * hx + (iy + 1) * hy) as usize, (br * fy) as f32);
intery += gradient;
}
}
#[inline(always)]
fn check_bounds(
mut p0: Point,
mut p1: Point,
width: usize,
height: usize,
) -> Option<(Point, Point)> {
let width = (width) as f64;
let height = (height) as f64;
if (p0.x < 0.0 && p1.x < 0.0) || (p0.y < 0.0 && p1.y < 0.0) || (p0.x > width && p1.x > width) || (p0.y > height && p1.y > height)
{
return None;
}
if p0.x > p1.x {
swap(&mut p0, &mut p1);
}
let d = (p1 - p0).normalized();
if d.x == 0.0 {
p0.y = p0.y.clamp(0.0, height);
p1.y = p1.y.clamp(0.0, height);
} else {
if p0.x < 0.0 {
p0 = p0 - (d * (p0.x / d.x));
}
if p1.x > width {
p1 = p1 - d * ((p1.x - width) / d.x);
}
if p0.y < 0.0 {
p0 = p0 - (d * (p0.y / d.y));
} else {
if p0.y > height {
p0 = p0 - d * ((p0.y - height) / d.y);
}
}
if p1.y < 0.0 {
p1 = p1 - (d * (p1.y / d.y));
} else {
if p1.y > height {
p1 = p1 - d * ((p1.y - height) / d.y);
}
}
}
Some((p0, p1))
}
}
impl RenderImage for Image {
fn draw_line(&self, ray: RayResult) {
self.inner_draw_line(&ray);
}
fn prepare_render(&mut self, lightpower: f32) {
self.lightpower = lightpower;
}
}
impl ExportImage for Image {
fn get_lightpower(&self) -> f32 {
self.lightpower
}
fn get_size(&self) -> (usize, usize) {
(self.width, self.height)
}
fn to_rgbaf32(&self) -> Vec<f32> {
self.pixels
.clone()
.into_iter()
.map(|x| [x.0, x.1, x.2, 1.0f32])
.flatten()
.collect()
}
}
#[cfg(test)]
mod tests {
use super::Image;
use crate::image::{ExportImage, RenderImage};
use crate::prelude::Point;
use crate::ray::RayResult;
#[test]
fn traced_ray_is_not_black() {
let i = Image::new(100, 100);
i.draw_line(RayResult::new((10.0, 10.0), (90.0, 90.0), 620.0)); i.draw_line(RayResult::new((20.0, 10.0), (90.0, 80.0), 520.0)); i.draw_line(RayResult::new((10.0, 20.0), (80.0, 90.0), 470.0)); let mut r_count = 0.0;
let mut g_count = 0.0;
let mut b_count = 0.0;
for (r, g, b) in i.pixels.iter() {
r_count += r;
g_count += g;
b_count += b;
}
assert_ne!(r_count, 0.0);
assert_ne!(g_count, 0.0);
assert_ne!(b_count, 0.0);
}
#[test]
fn empty_image_is_black() {
let mut i = Image::new(1920, 1080);
i.prepare_render(1.0);
let v = i.to_rgba8(0, 1.0, 1.0);
for i in v.chunks(4) {
assert_eq!(i[0], 0u8);
assert_eq!(i[1], 0u8);
assert_eq!(i[2], 0u8);
assert_eq!(i[3], 255u8);
}
}
#[test]
fn output_len_u8() {
let i = Image::new(1920, 1080);
let v = i.to_rgba8(0, 1.0, 1.0);
assert_eq!(v.len(), 1920 * 1080 * 4);
}
#[test]
fn output_len_f32() {
let i = Image::new(1920, 1080);
let v = i.to_rgbaf32();
assert_eq!(v.len(), 1920 * 1080 * 4);
}
#[test]
fn contigious_lines() {
let i = Image::new(400, 400);
i.draw_line(RayResult::new((10.0, 10.0), (100.0, 100.0), 0.0));
i.draw_line(RayResult::new((100.0, 100.0), (200.0, 200.0), 0.0));
i.draw_line(RayResult::new((200.0, 200.0), (300.0, 300.0), 0.0));
i.draw_line(RayResult::new((300.0, 300.0), (390.0, 390.0), 0.0));
for x in 0..10 {
for y in 0..400 {
let p = i.pixels[(y * 400) + x];
assert_eq!(p.0, 0.0);
assert_eq!(p.1, 0.0);
assert_eq!(p.2, 0.0);
}
}
for x in 10..=390 {
for y in 0..400 {
let p = i.pixels[(y * 400) + x];
if x == y {
assert!(p.0 > 0.0);
assert!(p.1 > 0.0);
assert!(p.2 > 0.0);
} else {
assert_eq!(p.0, 0.0);
assert_eq!(p.1, 0.0);
assert_eq!(p.2, 0.0);
}
}
}
for x in 391..400 {
for y in 0..400 {
let p = i.pixels[(y * 400) + x];
assert_eq!(p.0, 0.0);
assert_eq!(p.1, 0.0);
assert_eq!(p.2, 0.0);
}
}
}
#[test]
fn line_correct() {
let i = Image::new(100, 100);
i.draw_line(RayResult::new((0.0, 0.0), (100.0, 100.0), 0.0)); for x in 0..100 {
for y in 0..100 {
let p = i.pixels[(y * 100) + x];
if x == y {
assert!(p.0 > 0.0);
assert!(p.1 > 0.0);
assert!(p.2 > 0.0);
} else {
assert_eq!(p.0, 0.0);
assert_eq!(p.1, 0.0);
assert_eq!(p.2, 0.0);
}
}
}
}
#[test]
fn bounds_check_left1() {
let p0 = Point::new(-10.0, 0.0);
let p1 = Point::new(10.0, 20.0);
let (p0, p1) = Image::check_bounds(p0, p1, 50, 50).unwrap();
assert_eq!(p0, (0.0, 10.0).into());
assert_eq!(p1, (10.0, 20.0).into());
}
#[test]
fn bounds_check_left2() {
let p1 = Point::new(-10.0, 0.0);
let p0 = Point::new(10.0, 20.0);
let (p0, p1) = Image::check_bounds(p0, p1, 50, 50).unwrap();
assert_eq!(p0, (0.0, 10.0).into());
assert_eq!(p1, (10.0, 20.0).into());
}
#[test]
fn bounds_check_right1() {
let p0 = Point::new(60.0, 0.0);
let p1 = Point::new(40.0, 20.0);
let (p0, p1) = Image::check_bounds(p0, p1, 50, 50).unwrap();
assert_eq!(p1, (50.0, 10.0).into());
assert_eq!(p0, (40.0, 20.0).into());
}
#[test]
fn bounds_check_right2() {
let p1 = Point::new(60.0, 0.0);
let p0 = Point::new(40.0, 20.0);
let (p0, p1) = Image::check_bounds(p0, p1, 50, 50).unwrap();
assert_eq!(p1, (50.0, 10.0).into());
assert_eq!(p0, (40.0, 20.0).into());
}
#[test]
fn bounds_check_top1() {
let p0 = Point::new(20.0, -10.0);
let p1 = Point::new(40.0, 10.0);
let (p0, p1) = Image::check_bounds(p0, p1, 50, 50).unwrap();
assert_eq!(p0, (30.0, 0.0).into());
assert_eq!(p1, (40.0, 10.0).into());
}
#[test]
fn bounds_check_top2() {
let p1 = Point::new(20.0, -10.0);
let p0 = Point::new(40.0, 10.0);
let (p0, p1) = Image::check_bounds(p0, p1, 50, 50).unwrap();
assert_eq!(p0, (30.0, 0.0).into());
assert_eq!(p1, (40.0, 10.0).into());
}
#[test]
fn bounds_check_bottom1() {
let p0 = Point::new(10.0, 60.0);
let p1 = Point::new(40.0, 30.0);
let (p0, p1) = Image::check_bounds(p0, p1, 50, 50).unwrap();
assert_eq!(p0, (20.0, 50.0).into());
assert_eq!(p1, (40.0, 30.0).into());
}
#[test]
fn bounds_check_bottom2() {
let p1 = Point::new(10.0, 60.0);
let p0 = Point::new(40.0, 30.0);
let (p0, p1) = Image::check_bounds(p0, p1, 50, 50).unwrap();
assert_eq!(p0, (20.0, 50.0).into());
assert_eq!(p1, (40.0, 30.0).into());
}
}