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