1use crate::{geom::Point, ray::RayResult};
8
9use std::mem::swap;
10
11use super::{ExportImage, RenderImage};
12
13pub struct Image {
20 width: usize,
21 height: usize,
22 pixels: Vec<(f32, f32, f32)>,
23 lightpower: f32,
24}
25
26impl Image {
27 pub fn new(width: usize, height: usize) -> Self {
29 let len = width * height;
30 let pixels: Vec<(f32, f32, f32)> = vec![(0.0, 0.0, 0.0); len];
31 Image {
32 width,
33 height,
34 pixels,
35 lightpower: 0.0,
36 }
37 }
38
39 #[inline(always)]
40 fn plot(&self, colour: (f32, f32, f32), pixel: usize, intensity: f32) {
41 if pixel >= self.pixels.len() {
42 return;
43 };
44
45 unsafe {
46 let s = (self as *const Self) as *mut Self;
55 (*s).pixels[pixel].0 += colour.0 * intensity;
56 (*s).pixels[pixel].1 += colour.1 * intensity;
57 (*s).pixels[pixel].2 += colour.2 * intensity;
58 }
59 }
60
61 #[inline(always)]
62 fn inner_draw_line(&self, ray: &RayResult) {
63 let bounds =
74 Self::check_bounds(ray.origin, ray.termination, self.width - 1, self.height + 2);
75 if bounds.is_none() {
76 return;
77 }
78
79 let colour = ray.color();
80
81 let (p0, p1) = bounds.unwrap();
82
83 let mut hx: i64 = 1;
84 let mut hy: i64 = self.width as i64;
85
86 let mut x0 = p0.x;
87 let mut y0 = p0.y;
88 let mut x1 = p1.x;
89 let mut y1 = p1.y;
90
91 let dx = (x1 - x0).abs();
92 let dy = (y1 - y0).abs();
93
94 if dy > dx {
96 swap(&mut x0, &mut y0);
97 swap(&mut x1, &mut y1);
98 swap(&mut hx, &mut hy);
99 }
100
101 if x0 > x1 {
103 swap(&mut x0, &mut x1);
104 swap(&mut y0, &mut y1);
105 }
106
107 let dx = x1 - x0;
109 let dy = y1 - y0;
110 let gradient = if dx == 0.0 { 1.0 } else { dy / dx };
111
112 const SHIFT: f64 = 0.25;
113
114 x0 -= SHIFT;
115 x1 += SHIFT;
116 y0 -= gradient * SHIFT;
117 y1 += gradient * SHIFT;
118
119 let br = 128.0 * f64::sqrt(dx * dx + dy * dy) / dx;
121
122 let xend = x0.round();
126 let yend = y0 + gradient * (xend - x0);
127 let xpxl1: i64 = xend as i64;
128 let ypxl1: i64 = yend.floor() as i64;
129
130 let xgap = br * (1.0 - (x0 + 0.5) + xend); let ygap = yend - yend.floor(); self.plot(
134 colour,
135 (xpxl1 * hx + ypxl1 * hy) as usize,
136 (xgap * (1.0 - ygap)) as f32,
137 );
138 self.plot(
139 colour,
140 (xpxl1 * hx + (ypxl1 + 1) * hy) as usize,
141 (xgap * ygap) as f32,
142 );
143
144 let mut intery = yend + gradient;
145
146 let xend = x1.round();
148 let yend = y1 + gradient * (xend - x1);
149 let xpxl2: i64 = xend as i64;
150 let ypxl2: i64 = yend.floor() as i64;
151
152 let xgap = br * (1.0 - (x1 + 0.5) + xend); let ygap = yend - yend.floor(); self.plot(
156 colour,
157 (xpxl2 * hx + ypxl2 * hy) as usize,
158 (xgap * (1.0 - ygap)) as f32,
159 );
160 self.plot(
161 colour,
162 (xpxl2 * hx + (ypxl2 + 1) * hy) as usize,
163 (xgap * ygap) as f32,
164 );
165
166 for x in xpxl1 + 1..xpxl2 {
168 let iy: i64 = intery.floor() as i64;
169 let fy: f64 = intery - intery.floor(); self.plot(
172 colour,
173 (x * hx + iy * hy) as usize,
174 (br * (1.0 - fy)) as f32,
175 );
176 self.plot(colour, (x * hx + (iy + 1) * hy) as usize, (br * fy) as f32);
177
178 intery += gradient;
179 }
180 }
181
182 #[inline(always)]
183 fn check_bounds(
184 mut p0: Point,
185 mut p1: Point,
186 width: usize,
187 height: usize,
188 ) -> Option<(Point, Point)> {
189 let width = (width) as f64;
190 let height = (height) as f64;
191
192 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)
196 {
198 return None;
199 }
200
201 if p0.x > p1.x {
202 swap(&mut p0, &mut p1);
203 }
204
205 let d = (p1 - p0).normalized();
206
207 if d.x == 0.0 {
208 p0.y = p0.y.clamp(0.0, height);
209 p1.y = p1.y.clamp(0.0, height);
210 } else {
211 if p0.x < 0.0 {
212 p0 = p0 - (d * (p0.x / d.x));
213 }
214 if p1.x > width {
215 p1 = p1 - d * ((p1.x - width) / d.x);
216 }
217 if p0.y < 0.0 {
218 p0 = p0 - (d * (p0.y / d.y));
219 } else {
220 if p0.y > height {
221 p0 = p0 - d * ((p0.y - height) / d.y);
222 }
223 }
224 if p1.y < 0.0 {
225 p1 = p1 - (d * (p1.y / d.y));
226 } else {
227 if p1.y > height {
228 p1 = p1 - d * ((p1.y - height) / d.y);
229 }
230 }
231 }
232 Some((p0, p1))
233 }
234}
235
236impl RenderImage for Image {
237 fn draw_line(&self, ray: RayResult) {
238 self.inner_draw_line(&ray);
239 }
240
241 fn prepare_render(&mut self, lightpower: f32) {
242 self.lightpower = lightpower;
243 }
244}
245
246impl ExportImage for Image {
247 fn get_lightpower(&self) -> f32 {
248 self.lightpower
249 }
250
251 fn get_size(&self) -> (usize, usize) {
252 (self.width, self.height)
253 }
254
255 fn to_rgbaf32(&self) -> Vec<f32> {
256 self.pixels
257 .clone()
258 .into_iter()
259 .map(|x| [x.0, x.1, x.2, 1.0f32])
260 .flatten()
261 .collect()
262 }
263}
264
265#[cfg(test)]
266mod tests {
267 use super::Image;
268 use crate::image::{ExportImage, RenderImage};
269 use crate::prelude::Point;
270 use crate::ray::RayResult;
271
272 #[test]
273 fn traced_ray_is_not_black() {
274 let i = Image::new(100, 100);
275 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;
279 let mut g_count = 0.0;
280 let mut b_count = 0.0;
281 for (r, g, b) in i.pixels.iter() {
282 r_count += r;
283 g_count += g;
284 b_count += b;
285 }
286 assert_ne!(r_count, 0.0);
287 assert_ne!(g_count, 0.0);
288 assert_ne!(b_count, 0.0);
289 }
290
291 #[test]
292 fn empty_image_is_black() {
293 let mut i = Image::new(1920, 1080);
294 i.prepare_render(1.0);
295 let v = i.to_rgba8(0, 1.0, 1.0);
296 for i in v.chunks(4) {
297 assert_eq!(i[0], 0u8);
298 assert_eq!(i[1], 0u8);
299 assert_eq!(i[2], 0u8);
300 assert_eq!(i[3], 255u8);
301 }
302 }
303
304 #[test]
305 fn output_len_u8() {
306 let i = Image::new(1920, 1080);
307 let v = i.to_rgba8(0, 1.0, 1.0);
308 assert_eq!(v.len(), 1920 * 1080 * 4);
309 }
310
311 #[test]
312 fn output_len_f32() {
313 let i = Image::new(1920, 1080);
314 let v = i.to_rgbaf32();
315 assert_eq!(v.len(), 1920 * 1080 * 4);
316 }
317
318 #[test]
319 fn contigious_lines() {
320 let i = Image::new(400, 400);
321 i.draw_line(RayResult::new((10.0, 10.0), (100.0, 100.0), 0.0));
322 i.draw_line(RayResult::new((100.0, 100.0), (200.0, 200.0), 0.0));
323 i.draw_line(RayResult::new((200.0, 200.0), (300.0, 300.0), 0.0));
324 i.draw_line(RayResult::new((300.0, 300.0), (390.0, 390.0), 0.0));
325
326 for x in 0..10 {
327 for y in 0..400 {
328 let p = i.pixels[(y * 400) + x];
329 assert_eq!(p.0, 0.0);
330 assert_eq!(p.1, 0.0);
331 assert_eq!(p.2, 0.0);
332 }
333 }
334
335 for x in 10..=390 {
336 for y in 0..400 {
337 let p = i.pixels[(y * 400) + x];
338 if x == y {
339 assert!(p.0 > 0.0);
340 assert!(p.1 > 0.0);
341 assert!(p.2 > 0.0);
342 } else {
343 assert_eq!(p.0, 0.0);
344 assert_eq!(p.1, 0.0);
345 assert_eq!(p.2, 0.0);
346 }
347 }
348 }
349
350 for x in 391..400 {
351 for y in 0..400 {
352 let p = i.pixels[(y * 400) + x];
353 assert_eq!(p.0, 0.0);
354 assert_eq!(p.1, 0.0);
355 assert_eq!(p.2, 0.0);
356 }
357 }
358 }
359
360 #[test]
361 fn line_correct() {
362 let i = Image::new(100, 100);
363 i.draw_line(RayResult::new((0.0, 0.0), (100.0, 100.0), 0.0)); for x in 0..100 {
365 for y in 0..100 {
366 let p = i.pixels[(y * 100) + x];
367 if x == y {
368 assert!(p.0 > 0.0);
369 assert!(p.1 > 0.0);
370 assert!(p.2 > 0.0);
371 } else {
372 assert_eq!(p.0, 0.0);
373 assert_eq!(p.1, 0.0);
374 assert_eq!(p.2, 0.0);
375 }
376 }
377 }
378 }
379
380 #[test]
381 fn bounds_check_left1() {
382 let p0 = Point::new(-10.0, 0.0);
383 let p1 = Point::new(10.0, 20.0);
384
385 let (p0, p1) = Image::check_bounds(p0, p1, 50, 50).unwrap();
386
387 assert_eq!(p0, (0.0, 10.0).into());
388 assert_eq!(p1, (10.0, 20.0).into());
389 }
390
391 #[test]
392 fn bounds_check_left2() {
393 let p1 = Point::new(-10.0, 0.0);
394 let p0 = Point::new(10.0, 20.0);
395
396 let (p0, p1) = Image::check_bounds(p0, p1, 50, 50).unwrap();
397
398 assert_eq!(p0, (0.0, 10.0).into());
399 assert_eq!(p1, (10.0, 20.0).into());
400 }
401
402 #[test]
403 fn bounds_check_right1() {
404 let p0 = Point::new(60.0, 0.0);
405 let p1 = Point::new(40.0, 20.0);
406
407 let (p0, p1) = Image::check_bounds(p0, p1, 50, 50).unwrap();
408
409 assert_eq!(p1, (50.0, 10.0).into());
410 assert_eq!(p0, (40.0, 20.0).into());
411 }
412
413 #[test]
414 fn bounds_check_right2() {
415 let p1 = Point::new(60.0, 0.0);
416 let p0 = Point::new(40.0, 20.0);
417
418 let (p0, p1) = Image::check_bounds(p0, p1, 50, 50).unwrap();
419
420 assert_eq!(p1, (50.0, 10.0).into());
421 assert_eq!(p0, (40.0, 20.0).into());
422 }
423
424 #[test]
425 fn bounds_check_top1() {
426 let p0 = Point::new(20.0, -10.0);
427 let p1 = Point::new(40.0, 10.0);
428
429 let (p0, p1) = Image::check_bounds(p0, p1, 50, 50).unwrap();
430
431 assert_eq!(p0, (30.0, 0.0).into());
432 assert_eq!(p1, (40.0, 10.0).into());
433 }
434
435 #[test]
436 fn bounds_check_top2() {
437 let p1 = Point::new(20.0, -10.0);
438 let p0 = Point::new(40.0, 10.0);
439
440 let (p0, p1) = Image::check_bounds(p0, p1, 50, 50).unwrap();
441
442 assert_eq!(p0, (30.0, 0.0).into());
443 assert_eq!(p1, (40.0, 10.0).into());
444 }
445
446 #[test]
447 fn bounds_check_bottom1() {
448 let p0 = Point::new(10.0, 60.0);
449 let p1 = Point::new(40.0, 30.0);
450
451 let (p0, p1) = Image::check_bounds(p0, p1, 50, 50).unwrap();
452
453 assert_eq!(p0, (20.0, 50.0).into());
454 assert_eq!(p1, (40.0, 30.0).into());
455 }
456
457 #[test]
458 fn bounds_check_bottom2() {
459 let p1 = Point::new(10.0, 60.0);
460 let p0 = Point::new(40.0, 30.0);
461
462 let (p0, p1) = Image::check_bounds(p0, p1, 50, 50).unwrap();
463
464 assert_eq!(p0, (20.0, 50.0).into());
465 assert_eq!(p1, (40.0, 30.0).into());
466 }
467}