1#![allow(clippy::needless_range_loop, clippy::too_many_arguments)]
6#[allow(unused_imports)]
7use super::functions::*;
8use std::f64::consts::PI;
9
10#[derive(Debug, Clone)]
12pub struct Material {
13 pub mat_type: MaterialType,
15 pub albedo: [f64; 3],
17 pub roughness: f64,
19 pub metallic: f64,
21 pub ior: f64,
23 pub emission: [f64; 3],
25 pub shininess: f64,
27 pub ao: f64,
29}
30impl Material {
31 pub fn diffuse(albedo: [f64; 3]) -> Self {
33 Self {
34 mat_type: MaterialType::Diffuse,
35 albedo,
36 roughness: 1.0,
37 metallic: 0.0,
38 ior: 1.0,
39 emission: [0.0; 3],
40 shininess: 0.0,
41 ao: 1.0,
42 }
43 }
44 pub fn metal(albedo: [f64; 3], roughness: f64) -> Self {
46 Self {
47 mat_type: MaterialType::Metal,
48 albedo,
49 roughness: roughness.clamp(0.0, 1.0),
50 metallic: 1.0,
51 ior: 1.0,
52 emission: [0.0; 3],
53 shininess: 0.0,
54 ao: 1.0,
55 }
56 }
57 pub fn glass(ior: f64) -> Self {
59 Self {
60 mat_type: MaterialType::Dielectric,
61 albedo: [1.0; 3],
62 roughness: 0.0,
63 metallic: 0.0,
64 ior,
65 emission: [0.0; 3],
66 shininess: 0.0,
67 ao: 1.0,
68 }
69 }
70 pub fn emissive(color: [f64; 3], strength: f64) -> Self {
72 Self {
73 mat_type: MaterialType::Emissive,
74 albedo: color,
75 roughness: 1.0,
76 metallic: 0.0,
77 ior: 1.0,
78 emission: scale3(color, strength),
79 shininess: 0.0,
80 ao: 1.0,
81 }
82 }
83 pub fn pbr(albedo: [f64; 3], metallic: f64, roughness: f64, ao: f64) -> Self {
85 Self {
86 mat_type: MaterialType::Pbr,
87 albedo,
88 roughness: roughness.clamp(0.0, 1.0),
89 metallic: metallic.clamp(0.0, 1.0),
90 ior: 1.5,
91 emission: [0.0; 3],
92 shininess: 0.0,
93 ao,
94 }
95 }
96}
97#[derive(Debug, Clone)]
99pub struct PathState {
100 pub ray: Ray,
102 pub throughput: [f64; 3],
104 pub radiance: [f64; 3],
106 pub depth: u32,
108 pub max_depth: u32,
110}
111impl PathState {
112 pub fn new(ray: Ray, max_depth: u32) -> Self {
114 Self {
115 ray,
116 throughput: [1.0; 3],
117 radiance: [0.0; 3],
118 depth: 0,
119 max_depth,
120 }
121 }
122 pub fn should_continue(&self) -> bool {
124 self.depth < self.max_depth
125 && (self.throughput[0] + self.throughput[1] + self.throughput[2]) > 1e-6
126 }
127 pub fn russian_roulette(&mut self, survival_prob: f64) -> bool {
129 if survival_prob >= 1.0 {
130 return true;
131 }
132 let luminance =
133 0.2126 * self.throughput[0] + 0.7152 * self.throughput[1] + 0.0722 * self.throughput[2];
134 if luminance < survival_prob {
135 return false;
136 }
137 self.throughput = scale3(self.throughput, 1.0 / survival_prob);
138 true
139 }
140}
141#[derive(Debug, Clone, Copy)]
143pub struct HitRecord {
144 pub t: f64,
146 pub position: [f64; 3],
148 pub normal: [f64; 3],
150 pub uv: [f64; 2],
152 pub prim_id: u32,
154 pub front_face: bool,
156 pub material_id: u32,
158}
159impl HitRecord {
160 pub fn new(
162 t: f64,
163 position: [f64; 3],
164 outward_normal: [f64; 3],
165 ray_dir: [f64; 3],
166 uv: [f64; 2],
167 prim_id: u32,
168 material_id: u32,
169 ) -> Self {
170 let front_face = dot3(ray_dir, outward_normal) < 0.0;
171 let normal = if front_face {
172 outward_normal
173 } else {
174 scale3(outward_normal, -1.0)
175 };
176 Self {
177 t,
178 position,
179 normal,
180 uv,
181 prim_id,
182 front_face,
183 material_id,
184 }
185 }
186}
187#[derive(Debug, Clone)]
189pub struct BvhNode {
190 pub bounds: Aabb,
192 pub left_or_first: u32,
194 pub prim_count: u32,
196}
197impl BvhNode {
198 pub fn is_leaf(&self) -> bool {
200 self.prim_count > 0
201 }
202}
203#[derive(Debug, Clone, Default)]
205pub struct Scene {
206 pub triangles: Vec<Triangle>,
208 pub materials: Vec<Material>,
210 pub lights: Vec<PointLight>,
212 pub area_lights: Vec<AreaLight>,
214 pub bvh: Option<Bvh>,
216}
217impl Scene {
218 pub fn new() -> Self {
220 Self::default()
221 }
222 pub fn add_material(&mut self, mat: Material) -> u32 {
224 let idx = self.materials.len() as u32;
225 self.materials.push(mat);
226 idx
227 }
228 pub fn add_triangle(&mut self, tri: Triangle) {
230 self.triangles.push(tri);
231 }
232 pub fn add_light(&mut self, light: PointLight) {
234 self.lights.push(light);
235 }
236 pub fn build_bvh(&mut self) {
238 self.bvh = Some(Bvh::build(&self.triangles));
239 }
240 pub fn add_box(&mut self, center: [f64; 3], hs: [f64; 3], material_id: u32) {
242 let [cx, cy, cz] = center;
243 let [hx, hy, hz] = hs;
244 let v = [
245 [cx - hx, cy - hy, cz - hz],
246 [cx + hx, cy - hy, cz - hz],
247 [cx + hx, cy + hy, cz - hz],
248 [cx - hx, cy + hy, cz - hz],
249 [cx - hx, cy - hy, cz + hz],
250 [cx + hx, cy - hy, cz + hz],
251 [cx + hx, cy + hy, cz + hz],
252 [cx - hx, cy + hy, cz + hz],
253 ];
254 let normals = [
255 [0.0f64, 0.0, -1.0],
256 [0.0, 0.0, 1.0],
257 [-1.0, 0.0, 0.0],
258 [1.0, 0.0, 0.0],
259 [0.0, -1.0, 0.0],
260 [0.0, 1.0, 0.0],
261 ];
262 let faces: [[usize; 4]; 6] = [
263 [0, 1, 2, 3],
264 [5, 4, 7, 6],
265 [4, 0, 3, 7],
266 [1, 5, 6, 2],
267 [4, 5, 1, 0],
268 [3, 2, 6, 7],
269 ];
270 let uv_quad = [[0.0f64; 2], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
271 for (fi, face) in faces.iter().enumerate() {
272 let n = normals[fi];
273 let t0 = Triangle::new(
274 v[face[0]],
275 v[face[1]],
276 v[face[2]],
277 n,
278 n,
279 n,
280 uv_quad[0],
281 uv_quad[1],
282 uv_quad[2],
283 material_id,
284 );
285 let t1 = Triangle::new(
286 v[face[0]],
287 v[face[2]],
288 v[face[3]],
289 n,
290 n,
291 n,
292 uv_quad[0],
293 uv_quad[2],
294 uv_quad[3],
295 material_id,
296 );
297 self.add_triangle(t0);
298 self.add_triangle(t1);
299 }
300 }
301 pub fn add_quad(
303 &mut self,
304 v0: [f64; 3],
305 v1: [f64; 3],
306 v2: [f64; 3],
307 v3: [f64; 3],
308 material_id: u32,
309 ) {
310 let n = normalize3(cross3(sub3(v1, v0), sub3(v2, v0)));
311 let t0 = Triangle::new(
312 v0,
313 v1,
314 v2,
315 n,
316 n,
317 n,
318 [0.0, 0.0],
319 [1.0, 0.0],
320 [1.0, 1.0],
321 material_id,
322 );
323 let t1 = Triangle::new(
324 v0,
325 v2,
326 v3,
327 n,
328 n,
329 n,
330 [0.0, 0.0],
331 [1.0, 1.0],
332 [0.0, 1.0],
333 material_id,
334 );
335 self.add_triangle(t0);
336 self.add_triangle(t1);
337 }
338 pub fn intersect<'a>(&'a self, ray: &Ray) -> Option<(HitRecord, &'a Triangle)> {
340 match &self.bvh {
341 Some(bvh) => bvh.intersect(ray, &self.triangles),
342 None => {
343 let mut best_t = ray.t_max;
344 let mut best: Option<(HitRecord, usize)> = None;
345 for (i, tri) in self.triangles.iter().enumerate() {
346 if let Some((t, u, v)) = tri.intersect_full(ray)
347 && t < best_t
348 {
349 best_t = t;
350 let pos = ray.at(t);
351 let norm = tri.interpolate_normal(u, v);
352 let uv = tri.interpolate_uv(u, v);
353 let hit = HitRecord::new(
354 t,
355 pos,
356 norm,
357 ray.direction,
358 uv,
359 i as u32,
360 tri.material_id,
361 );
362 best = Some((hit, i));
363 }
364 }
365 best.map(|(hit, i)| (hit, &self.triangles[i]))
366 }
367 }
368 }
369}
370#[derive(Debug, Clone, Copy)]
372pub struct Triangle {
373 pub v: [[f64; 3]; 3],
375 pub n: [[f64; 3]; 3],
377 pub uv: [[f64; 2]; 3],
379 pub material_id: u32,
381}
382impl Triangle {
383 pub fn new(
385 v0: [f64; 3],
386 v1: [f64; 3],
387 v2: [f64; 3],
388 n0: [f64; 3],
389 n1: [f64; 3],
390 n2: [f64; 3],
391 uv0: [f64; 2],
392 uv1: [f64; 2],
393 uv2: [f64; 2],
394 material_id: u32,
395 ) -> Self {
396 Self {
397 v: [v0, v1, v2],
398 n: [n0, n1, n2],
399 uv: [uv0, uv1, uv2],
400 material_id,
401 }
402 }
403 pub fn geometric_normal(&self) -> [f64; 3] {
405 let e1 = sub3(self.v[1], self.v[0]);
406 let e2 = sub3(self.v[2], self.v[0]);
407 normalize3(cross3(e1, e2))
408 }
409 pub fn intersect(&self, ray: &Ray) -> Option<f64> {
413 let e1 = sub3(self.v[1], self.v[0]);
414 let e2 = sub3(self.v[2], self.v[0]);
415 let h = cross3(ray.direction, e2);
416 let a = dot3(e1, h);
417 if a.abs() < 1e-15 {
418 return None;
419 }
420 let f = 1.0 / a;
421 let s = sub3(ray.origin, self.v[0]);
422 let u = f * dot3(s, h);
423 if !(0.0..=1.0).contains(&u) {
424 return None;
425 }
426 let q = cross3(s, e1);
427 let v = f * dot3(ray.direction, q);
428 if v < 0.0 || u + v > 1.0 {
429 return None;
430 }
431 let t = f * dot3(e2, q);
432 if t >= ray.t_min && t <= ray.t_max {
433 Some(t)
434 } else {
435 None
436 }
437 }
438 pub fn intersect_full(&self, ray: &Ray) -> Option<(f64, f64, f64)> {
440 let e1 = sub3(self.v[1], self.v[0]);
441 let e2 = sub3(self.v[2], self.v[0]);
442 let h = cross3(ray.direction, e2);
443 let a = dot3(e1, h);
444 if a.abs() < 1e-15 {
445 return None;
446 }
447 let f = 1.0 / a;
448 let s = sub3(ray.origin, self.v[0]);
449 let u = f * dot3(s, h);
450 if !(0.0..=1.0).contains(&u) {
451 return None;
452 }
453 let q = cross3(s, e1);
454 let v = f * dot3(ray.direction, q);
455 if v < 0.0 || u + v > 1.0 {
456 return None;
457 }
458 let t = f * dot3(e2, q);
459 if t >= ray.t_min && t <= ray.t_max {
460 Some((t, u, v))
461 } else {
462 None
463 }
464 }
465 pub fn interpolate_normal(&self, u: f64, v: f64) -> [f64; 3] {
467 let w = 1.0 - u - v;
468 let n = [
469 w * self.n[0][0] + u * self.n[1][0] + v * self.n[2][0],
470 w * self.n[0][1] + u * self.n[1][1] + v * self.n[2][1],
471 w * self.n[0][2] + u * self.n[1][2] + v * self.n[2][2],
472 ];
473 normalize3(n)
474 }
475 pub fn interpolate_uv(&self, u: f64, v: f64) -> [f64; 2] {
477 let w = 1.0 - u - v;
478 [
479 w * self.uv[0][0] + u * self.uv[1][0] + v * self.uv[2][0],
480 w * self.uv[0][1] + u * self.uv[1][1] + v * self.uv[2][1],
481 ]
482 }
483 pub fn aabb(&self) -> Aabb {
485 let min_x = self.v[0][0].min(self.v[1][0]).min(self.v[2][0]);
486 let min_y = self.v[0][1].min(self.v[1][1]).min(self.v[2][1]);
487 let min_z = self.v[0][2].min(self.v[1][2]).min(self.v[2][2]);
488 let max_x = self.v[0][0].max(self.v[1][0]).max(self.v[2][0]);
489 let max_y = self.v[0][1].max(self.v[1][1]).max(self.v[2][1]);
490 let max_z = self.v[0][2].max(self.v[1][2]).max(self.v[2][2]);
491 Aabb {
492 min: [min_x, min_y, min_z],
493 max: [max_x, max_y, max_z],
494 }
495 }
496 pub fn centroid(&self) -> [f64; 3] {
498 [
499 (self.v[0][0] + self.v[1][0] + self.v[2][0]) / 3.0,
500 (self.v[0][1] + self.v[1][1] + self.v[2][1]) / 3.0,
501 (self.v[0][2] + self.v[1][2] + self.v[2][2]) / 3.0,
502 ]
503 }
504 pub fn area(&self) -> f64 {
506 let e1 = sub3(self.v[1], self.v[0]);
507 let e2 = sub3(self.v[2], self.v[0]);
508 length3(cross3(e1, e2)) * 0.5
509 }
510}
511#[derive(Debug, Clone, Copy)]
513pub struct Aabb {
514 pub min: [f64; 3],
516 pub max: [f64; 3],
518}
519impl Aabb {
520 pub fn new(min: [f64; 3], max: [f64; 3]) -> Self {
522 Self { min, max }
523 }
524 pub fn empty() -> Self {
526 Self {
527 min: [f64::INFINITY; 3],
528 max: [f64::NEG_INFINITY; 3],
529 }
530 }
531 pub fn expand_point(&self, p: [f64; 3]) -> Self {
533 Self {
534 min: [
535 self.min[0].min(p[0]),
536 self.min[1].min(p[1]),
537 self.min[2].min(p[2]),
538 ],
539 max: [
540 self.max[0].max(p[0]),
541 self.max[1].max(p[1]),
542 self.max[2].max(p[2]),
543 ],
544 }
545 }
546 pub fn merge(&self, other: &Self) -> Self {
548 Self {
549 min: [
550 self.min[0].min(other.min[0]),
551 self.min[1].min(other.min[1]),
552 self.min[2].min(other.min[2]),
553 ],
554 max: [
555 self.max[0].max(other.max[0]),
556 self.max[1].max(other.max[1]),
557 self.max[2].max(other.max[2]),
558 ],
559 }
560 }
561 pub fn centroid(&self) -> [f64; 3] {
563 [
564 (self.min[0] + self.max[0]) * 0.5,
565 (self.min[1] + self.max[1]) * 0.5,
566 (self.min[2] + self.max[2]) * 0.5,
567 ]
568 }
569 pub fn surface_area(&self) -> f64 {
571 let d = [
572 self.max[0] - self.min[0],
573 self.max[1] - self.min[1],
574 self.max[2] - self.min[2],
575 ];
576 2.0 * (d[0] * d[1] + d[1] * d[2] + d[2] * d[0])
577 }
578 pub fn longest_axis(&self) -> usize {
580 let d = [
581 self.max[0] - self.min[0],
582 self.max[1] - self.min[1],
583 self.max[2] - self.min[2],
584 ];
585 if d[0] >= d[1] && d[0] >= d[2] {
586 0
587 } else if d[1] >= d[2] {
588 1
589 } else {
590 2
591 }
592 }
593 pub fn intersect_ray(&self, ray: &Ray) -> Option<(f64, f64)> {
595 let mut t_near = ray.t_min;
596 let mut t_far = ray.t_max;
597 for i in 0..3 {
598 let inv_d = if ray.direction[i].abs() < 1e-15 {
599 f64::INFINITY
600 } else {
601 1.0 / ray.direction[i]
602 };
603 let t0 = (self.min[i] - ray.origin[i]) * inv_d;
604 let t1 = (self.max[i] - ray.origin[i]) * inv_d;
605 let (t0, t1) = if inv_d < 0.0 { (t1, t0) } else { (t0, t1) };
606 t_near = t_near.max(t0);
607 t_far = t_far.min(t1);
608 if t_far < t_near {
609 return None;
610 }
611 }
612 Some((t_near, t_far))
613 }
614}
615#[derive(Debug, Clone)]
617pub struct Bvh {
618 pub nodes: Vec<BvhNode>,
620 pub prim_indices: Vec<u32>,
622 pub prim_count: usize,
624}
625impl Bvh {
626 pub fn build(triangles: &[Triangle]) -> Self {
628 let n = triangles.len();
629 if n == 0 {
630 return Self {
631 nodes: Vec::new(),
632 prim_indices: Vec::new(),
633 prim_count: 0,
634 };
635 }
636 let mut prim_indices: Vec<u32> = (0..n as u32).collect();
637 let centroids: Vec<[f64; 3]> = triangles.iter().map(|t| t.centroid()).collect();
638 let aabbs: Vec<Aabb> = triangles.iter().map(|t| t.aabb()).collect();
639 let mut nodes = Vec::with_capacity(2 * n);
640 let root_bounds = aabbs.iter().fold(Aabb::empty(), |acc, b| acc.merge(b));
641 nodes.push(BvhNode {
642 bounds: root_bounds,
643 left_or_first: 0,
644 prim_count: n as u32,
645 });
646 let mut stack = vec![0usize];
647 while let Some(node_idx) = stack.pop() {
648 let first = nodes[node_idx].left_or_first as usize;
649 let count = nodes[node_idx].prim_count as usize;
650 if count <= 4 {
651 continue;
652 }
653 let parent_sa = nodes[node_idx].bounds.surface_area();
654 let mut best_cost = f64::INFINITY;
655 let mut best_axis = 0usize;
656 let mut best_split = 0.0f64;
657 for axis in 0..3 {
658 let slice = &mut prim_indices[first..first + count];
659 slice.sort_unstable_by(|&a, &b| {
660 centroids[a as usize][axis]
661 .partial_cmp(¢roids[b as usize][axis])
662 .expect("operation should succeed")
663 });
664 let mut left_bounds = Aabb::empty();
665 let mut left_areas = Vec::with_capacity(count);
666 for i in 0..count - 1 {
667 left_bounds = left_bounds.merge(&aabbs[slice[i] as usize]);
668 left_areas.push(left_bounds.surface_area());
669 }
670 let mut right_bounds = Aabb::empty();
671 for i in (1..count).rev() {
672 right_bounds = right_bounds.merge(&aabbs[slice[i] as usize]);
673 let left_count = i;
674 let right_count = count - i;
675 let cost = (left_areas[i - 1] * left_count as f64
676 + right_bounds.surface_area() * right_count as f64)
677 / parent_sa;
678 if cost < best_cost {
679 best_cost = cost;
680 best_axis = axis;
681 best_split = centroids[slice[i] as usize][axis];
682 }
683 }
684 }
685 let slice = &mut prim_indices[first..first + count];
686 slice.sort_unstable_by(|&a, &b| {
687 centroids[a as usize][best_axis]
688 .partial_cmp(¢roids[b as usize][best_axis])
689 .expect("operation should succeed")
690 });
691 let split_pos =
692 slice.partition_point(|&idx| centroids[idx as usize][best_axis] < best_split);
693 let split_pos = split_pos.clamp(1, count - 1);
694 let left_count = split_pos;
695 let right_count = count - split_pos;
696 let left_bounds = prim_indices[first..first + left_count]
697 .iter()
698 .fold(Aabb::empty(), |acc, &i| acc.merge(&aabbs[i as usize]));
699 let right_bounds = prim_indices[first + left_count..first + count]
700 .iter()
701 .fold(Aabb::empty(), |acc, &i| acc.merge(&aabbs[i as usize]));
702 let left_child_idx = nodes.len();
703 nodes.push(BvhNode {
704 bounds: left_bounds,
705 left_or_first: first as u32,
706 prim_count: left_count as u32,
707 });
708 let right_child_idx = nodes.len();
709 nodes.push(BvhNode {
710 bounds: right_bounds,
711 left_or_first: (first + left_count) as u32,
712 prim_count: right_count as u32,
713 });
714 nodes[node_idx].left_or_first = left_child_idx as u32;
715 nodes[node_idx].prim_count = 0;
716 stack.push(left_child_idx);
717 stack.push(right_child_idx);
718 }
719 Self {
720 nodes,
721 prim_indices,
722 prim_count: n,
723 }
724 }
725 pub fn intersect<'a>(
727 &self,
728 ray: &Ray,
729 triangles: &'a [Triangle],
730 ) -> Option<(HitRecord, &'a Triangle)> {
731 if self.nodes.is_empty() {
732 return None;
733 }
734 let mut stack = Vec::with_capacity(64);
735 stack.push(0usize);
736 let mut best_t = ray.t_max;
737 let mut best_hit: Option<(HitRecord, usize)> = None;
738 while let Some(node_idx) = stack.pop() {
739 let node = &self.nodes[node_idx];
740 let mut test_ray = *ray;
741 test_ray.t_max = best_t;
742 if node.bounds.intersect_ray(&test_ray).is_none() {
743 continue;
744 }
745 if node.is_leaf() {
746 let first = node.left_or_first as usize;
747 let count = node.prim_count as usize;
748 for i in first..first + count {
749 let tri_idx = self.prim_indices[i] as usize;
750 let tri = &triangles[tri_idx];
751 if let Some((t, u, v)) = tri.intersect_full(ray)
752 && t < best_t
753 {
754 best_t = t;
755 let pos = ray.at(t);
756 let norm = tri.interpolate_normal(u, v);
757 let uv = tri.interpolate_uv(u, v);
758 let hit = HitRecord::new(
759 t,
760 pos,
761 norm,
762 ray.direction,
763 uv,
764 tri_idx as u32,
765 tri.material_id,
766 );
767 best_hit = Some((hit, tri_idx));
768 }
769 }
770 } else {
771 let left = node.left_or_first as usize;
772 let right = left + 1;
773 stack.push(left);
774 stack.push(right);
775 }
776 }
777 best_hit.map(|(hit, tri_idx)| (hit, &triangles[tri_idx]))
778 }
779 pub fn intersect_any(&self, ray: &Ray, triangles: &[Triangle]) -> bool {
781 if self.nodes.is_empty() {
782 return false;
783 }
784 let mut stack = Vec::with_capacity(64);
785 stack.push(0usize);
786 while let Some(node_idx) = stack.pop() {
787 let node = &self.nodes[node_idx];
788 if node.bounds.intersect_ray(ray).is_none() {
789 continue;
790 }
791 if node.is_leaf() {
792 let first = node.left_or_first as usize;
793 let count = node.prim_count as usize;
794 for i in first..first + count {
795 let tri_idx = self.prim_indices[i] as usize;
796 if triangles[tri_idx].intersect(ray).is_some() {
797 return true;
798 }
799 }
800 } else {
801 let left = node.left_or_first as usize;
802 let right = left + 1;
803 stack.push(left);
804 stack.push(right);
805 }
806 }
807 false
808 }
809}
810#[derive(Debug, Clone, Copy)]
812pub struct AreaLight {
813 pub position: [f64; 3],
815 pub color: [f64; 3],
817 pub intensity: f64,
819 pub u_axis: [f64; 3],
821 pub v_axis: [f64; 3],
823}
824impl AreaLight {
825 pub fn new(
827 position: [f64; 3],
828 color: [f64; 3],
829 intensity: f64,
830 u_axis: [f64; 3],
831 v_axis: [f64; 3],
832 ) -> Self {
833 Self {
834 position,
835 color,
836 intensity,
837 u_axis,
838 v_axis,
839 }
840 }
841 pub fn sample_point(&self, su: f64, sv: f64) -> [f64; 3] {
843 add3(
844 add3(self.position, scale3(self.u_axis, su)),
845 scale3(self.v_axis, sv),
846 )
847 }
848}
849#[derive(Debug, Clone, Copy)]
851pub struct Ray {
852 pub origin: [f64; 3],
854 pub direction: [f64; 3],
856 pub t_min: f64,
858 pub t_max: f64,
860}
861impl Ray {
862 pub fn new(origin: [f64; 3], direction: [f64; 3]) -> Self {
864 Self {
865 origin,
866 direction: normalize3(direction),
867 t_min: 1e-4,
868 t_max: f64::INFINITY,
869 }
870 }
871 pub fn at(&self, t: f64) -> [f64; 3] {
873 add3(self.origin, scale3(self.direction, t))
874 }
875}
876#[derive(Debug, Clone)]
878pub struct Camera {
879 pub position: [f64; 3],
881 pub forward: [f64; 3],
883 pub right: [f64; 3],
885 pub up: [f64; 3],
887 pub fov_y: f64,
889 pub aspect: f64,
891 pub near: f64,
893 pub aperture: f64,
895 pub focus_dist: f64,
897}
898impl Camera {
899 pub fn look_at(
901 eye: [f64; 3],
902 target: [f64; 3],
903 world_up: [f64; 3],
904 fov_y_deg: f64,
905 aspect: f64,
906 aperture: f64,
907 focus_dist: f64,
908 ) -> Self {
909 let forward = normalize3(sub3(target, eye));
910 let right = normalize3(cross3(forward, world_up));
911 let up = cross3(right, forward);
912 Self {
913 position: eye,
914 forward,
915 right,
916 up,
917 fov_y: fov_y_deg * PI / 180.0,
918 aspect,
919 near: 0.001,
920 aperture,
921 focus_dist,
922 }
923 }
924 pub fn generate_ray(&self, px: f64, py: f64, width: f64, height: f64) -> Ray {
926 let half_h = (self.fov_y * 0.5).tan();
927 let half_w = self.aspect * half_h;
928 let ndc_x = (2.0 * (px + 0.5) / width - 1.0) * half_w;
929 let ndc_y = (1.0 - 2.0 * (py + 0.5) / height) * half_h;
930 let dir = normalize3(add3(
931 add3(self.forward, scale3(self.right, ndc_x)),
932 scale3(self.up, ndc_y),
933 ));
934 Ray::new(self.position, dir)
935 }
936 pub fn generate_dof_ray(
938 &self,
939 px: f64,
940 py: f64,
941 width: f64,
942 height: f64,
943 lens_u: f64,
944 lens_v: f64,
945 ) -> Ray {
946 let half_h = (self.fov_y * 0.5).tan();
947 let half_w = self.aspect * half_h;
948 let ndc_x = (2.0 * (px + 0.5) / width - 1.0) * half_w;
949 let ndc_y = (1.0 - 2.0 * (py + 0.5) / height) * half_h;
950 let focus_dir = normalize3(add3(
951 add3(self.forward, scale3(self.right, ndc_x)),
952 scale3(self.up, ndc_y),
953 ));
954 let focus_point = add3(self.position, scale3(focus_dir, self.focus_dist));
955 let lens_offset = add3(
956 scale3(self.right, lens_u * self.aperture),
957 scale3(self.up, lens_v * self.aperture),
958 );
959 let origin = add3(self.position, lens_offset);
960 let direction = normalize3(sub3(focus_point, origin));
961 Ray::new(origin, direction)
962 }
963}
964#[derive(Debug, Clone, Copy, PartialEq)]
966pub enum MaterialType {
967 Diffuse,
969 Metal,
971 Dielectric,
973 Emissive,
975 Pbr,
977}
978#[derive(Debug, Clone, Copy)]
980pub struct PointLight {
981 pub position: [f64; 3],
983 pub color: [f64; 3],
985 pub intensity: f64,
987 pub attenuation: [f64; 3],
989}
990impl PointLight {
991 pub fn new(position: [f64; 3], color: [f64; 3], intensity: f64) -> Self {
993 Self {
994 position,
995 color,
996 intensity,
997 attenuation: [1.0, 0.0, 0.1],
998 }
999 }
1000 pub fn attenuate(&self, d: f64) -> f64 {
1002 let [c, l, q] = self.attenuation;
1003 1.0 / (c + l * d + q * d * d)
1004 }
1005}
1006#[derive(Debug, Clone)]
1008pub struct RenderConfig {
1009 pub width: usize,
1011 pub height: usize,
1013 pub spp: u32,
1015 pub max_depth: u32,
1017 pub soft_shadows: bool,
1019 pub ambient_occlusion: bool,
1021 pub depth_of_field: bool,
1023 pub ao_samples: u32,
1025 pub shadow_samples: u32,
1027 pub background: [f64; 3],
1029 pub ambient: [f64; 3],
1031 pub tonemap: u32,
1033}