1use std::collections::BTreeMap;
27
28pub mod flags {
30 pub const FORCE_OPAQUE: u32 = 0x1;
32 pub const FORCE_NON_OPAQUE: u32 = 0x2;
34 pub const ACCEPT_FIRST_HIT: u32 = 0x4;
36 pub const SKIP_CLOSEST_HIT: u32 = 0x8;
38 pub const CULL_BACK_FACING: u32 = 0x10;
40 pub const CULL_FRONT_FACING: u32 = 0x20;
42 pub const CULL_OPAQUE: u32 = 0x40;
44 pub const CULL_NON_OPAQUE: u32 = 0x80;
46 pub const SKIP_TRIANGLES: u32 = 0x100;
48 pub const SKIP_PROCEDURAL: u32 = 0x200;
50}
51
52#[derive(Clone, Debug)]
55pub struct Geometry {
56 pub positions: Vec<[f32; 3]>,
58 pub indices: Option<Vec<u32>>,
60 pub boxes: Vec<[[f32; 3]; 2]>,
62 pub opaque: bool,
64}
65
66impl Geometry {
67 fn procedural(&self) -> bool {
68 !self.boxes.is_empty()
69 }
70 fn primitives(&self) -> usize {
72 if self.procedural() {
73 self.boxes.len()
74 } else {
75 self.triangles()
76 }
77 }
78 fn triangles(&self) -> usize {
79 self.indices.as_ref().map_or(self.positions.len(), Vec::len) / 3
80 }
81 fn triangle(&self, k: usize) -> Option<[[f32; 3]; 3]> {
82 let at = |j: usize| k.saturating_mul(3).saturating_add(j);
83 let vertex = |j: usize| match &self.indices {
84 Some(i) => self.positions.get(*i.get(at(j))? as usize).copied(),
85 None => self.positions.get(at(j)).copied(),
86 };
87 Some([vertex(0)?, vertex(1)?, vertex(2)?])
88 }
89}
90
91#[derive(Clone, Copy, Debug, Default)]
93pub struct InstanceFlags {
94 pub cull_disable: bool,
96 pub front_counter_clockwise: bool,
98 pub force_opaque: bool,
100 pub force_non_opaque: bool,
102}
103
104#[derive(Clone, Debug)]
106pub struct Instance {
107 pub bottom: usize,
109 pub transform: [[f32; 4]; 3],
111 pub id: u32,
113 pub mask: u8,
115 pub contribution: u32,
117 pub flags: InstanceFlags,
119}
120
121#[derive(Clone, Debug, Default)]
123pub struct Scene {
124 pub bottoms: Vec<Vec<Geometry>>,
126 pub instances: Vec<Instance>,
128}
129
130type Vec3 = [f32; 3];
131
132#[derive(Clone, Copy, Debug)]
133struct Aabb {
134 min: Vec3,
135 max: Vec3,
136}
137
138impl Aabb {
139 const EMPTY: Aabb = Aabb {
140 min: [f32::INFINITY; 3],
141 max: [f32::NEG_INFINITY; 3],
142 };
143 #[allow(clippy::indexing_slicing)] fn grow(&mut self, p: Vec3) {
145 for (k, &x) in p.iter().enumerate() {
146 self.min[k] = self.min[k].min(x);
147 self.max[k] = self.max[k].max(x);
148 }
149 }
150 fn union(&mut self, other: &Aabb) {
151 self.grow(other.min);
152 self.grow(other.max);
153 }
154 #[allow(clippy::indexing_slicing)] fn padded(self) -> Aabb {
157 let mut b = self;
158 for k in 0..3 {
159 let pad = (b.min[k].abs().max(b.max[k].abs()) * 1e-5).max(1e-20);
160 b.min[k] -= pad;
161 b.max[k] += pad;
162 }
163 b
164 }
165}
166
167#[derive(Clone, Debug)]
170struct Node {
171 bounds: Aabb,
172 first: u32,
173 count: u32,
174}
175
176#[derive(Clone, Debug, Default)]
177struct Bvh {
178 nodes: Vec<Node>,
179 order: Vec<u32>,
180}
181
182impl Bvh {
183 fn build(bounds: &[Aabb]) -> Bvh {
185 let mut bvh = Bvh::default();
186 if bounds.is_empty() {
187 return bvh;
188 }
189 bvh.order = (0..u32::try_from(bounds.len()).unwrap_or(u32::MAX)).collect();
190 bvh.nodes.push(Node {
191 bounds: Aabb::EMPTY,
192 first: 0,
193 count: 0,
194 });
195 bvh.split(0, 0, bounds.len(), bounds);
196 bvh
197 }
198
199 #[allow(
200 clippy::arithmetic_side_effects,
201 clippy::cast_possible_truncation,
202 clippy::indexing_slicing
203 )] fn split(&mut self, node: usize, lo: usize, hi: usize, bounds: &[Aabb]) {
205 let mut total = Aabb::EMPTY;
206 let mut centroids = Aabb::EMPTY;
207 for &p in &self.order[lo..hi] {
208 let b = &bounds[p as usize];
209 total.union(b);
210 centroids.grow(std::array::from_fn(|k| 0.5 * (b.min[k] + b.max[k])));
211 }
212 self.nodes[node].bounds = total.padded();
213 if hi - lo <= 4 {
214 self.nodes[node].first = lo as u32;
215 self.nodes[node].count = (hi - lo) as u32;
216 return;
217 }
218 let axis = (0..3)
219 .max_by(|&a, &b| {
220 (centroids.max[a] - centroids.min[a]).total_cmp(&(centroids.max[b] - centroids.min[b]))
221 })
222 .unwrap_or(0);
223 self.order[lo..hi].sort_by(|&a, &b| {
224 let (ca, cb) = (&bounds[a as usize], &bounds[b as usize]);
225 (ca.min[axis] + ca.max[axis]).total_cmp(&(cb.min[axis] + cb.max[axis]))
226 });
227 let mid = lo + (hi - lo) / 2;
228 let left = self.nodes.len();
229 for _ in 0..2 {
230 self.nodes.push(Node {
231 bounds: Aabb::EMPTY,
232 first: 0,
233 count: 0,
234 });
235 }
236 self.nodes[node].first = left as u32;
237 self.nodes[node].count = 0;
238 self.split(left, lo, mid, bounds);
239 self.split(left + 1, mid, hi, bounds);
240 }
241}
242
243#[allow(clippy::indexing_slicing)] fn crosses(b: &Aabb, origin: Vec3, inv: Vec3, tmin: f32, tmax: f32) -> bool {
246 let (mut lo, mut hi) = (tmin, tmax);
247 for k in 0..3 {
248 let (a, c) = ((b.min[k] - origin[k]) * inv[k], (b.max[k] - origin[k]) * inv[k]);
249 lo = lo.max(a.min(c));
251 hi = hi.min(a.max(c));
252 }
253 lo <= hi
254}
255
256#[derive(Clone, Debug)]
257struct BuiltBottom {
258 geometries: Vec<Geometry>,
259 primitives: Vec<(u32, u32)>,
261 bvh: Bvh,
262}
263
264#[derive(Clone, Debug)]
265struct BuiltInstance {
266 instance: Instance,
267 inverse: [[f32; 4]; 3],
269}
270
271#[derive(Clone, Debug)]
273pub struct Built {
274 bottoms: Vec<BuiltBottom>,
275 instances: Vec<BuiltInstance>,
276 top: Bvh,
277}
278
279#[allow(clippy::indexing_slicing)] pub(super) fn transform_point(m: &[[f32; 4]; 3], p: Vec3) -> Vec3 {
281 std::array::from_fn(|r| m[r][0] * p[0] + m[r][1] * p[1] + m[r][2] * p[2] + m[r][3])
282}
283#[allow(clippy::indexing_slicing)] pub(super) fn transform_vector(m: &[[f32; 4]; 3], v: Vec3) -> Vec3 {
285 std::array::from_fn(|r| m[r][0] * v[0] + m[r][1] * v[1] + m[r][2] * v[2])
286}
287
288#[allow(clippy::cast_possible_truncation, clippy::indexing_slicing)] fn invert(m: &[[f32; 4]; 3]) -> Option<[[f32; 4]; 3]> {
291 let a = |r: usize, c: usize| f64::from(m[r][c]);
292 let det = a(0, 0) * (a(1, 1) * a(2, 2) - a(1, 2) * a(2, 1))
293 - a(0, 1) * (a(1, 0) * a(2, 2) - a(1, 2) * a(2, 0))
294 + a(0, 2) * (a(1, 0) * a(2, 1) - a(1, 1) * a(2, 0));
295 if det == 0.0 || !det.is_finite() {
296 return None;
297 }
298 let inv = [
299 [
300 (a(1, 1) * a(2, 2) - a(1, 2) * a(2, 1)) / det,
301 (a(0, 2) * a(2, 1) - a(0, 1) * a(2, 2)) / det,
302 (a(0, 1) * a(1, 2) - a(0, 2) * a(1, 1)) / det,
303 ],
304 [
305 (a(1, 2) * a(2, 0) - a(1, 0) * a(2, 2)) / det,
306 (a(0, 0) * a(2, 2) - a(0, 2) * a(2, 0)) / det,
307 (a(0, 2) * a(1, 0) - a(0, 0) * a(1, 2)) / det,
308 ],
309 [
310 (a(1, 0) * a(2, 1) - a(1, 1) * a(2, 0)) / det,
311 (a(0, 1) * a(2, 0) - a(0, 0) * a(2, 1)) / det,
312 (a(0, 0) * a(1, 1) - a(0, 1) * a(1, 0)) / det,
313 ],
314 ];
315 let t = [a(0, 3), a(1, 3), a(2, 3)];
316 let mut out = [[0f32; 4]; 3];
317 for r in 0..3 {
318 for c in 0..3 {
319 out[r][c] = inv[r][c] as f32;
320 }
321 out[r][3] = -(inv[r][0] * t[0] + inv[r][1] * t[1] + inv[r][2] * t[2]) as f32;
322 }
323 Some(out)
324}
325
326impl Built {
327 #[allow(clippy::indexing_slicing)] pub(super) fn instance(&self, i: u32) -> &Instance {
330 &self.instances[i as usize].instance
331 }
332 #[allow(clippy::indexing_slicing)] pub(super) fn inverse(&self, i: u32) -> &[[f32; 4]; 3] {
335 &self.instances[i as usize].inverse
336 }
337
338 #[allow(clippy::indexing_slicing)] pub fn new(scene: &Scene) -> Result<Built, String> {
342 let mut bottoms = Vec::new();
343 for geometries in &scene.bottoms {
344 let mut primitives = Vec::new();
345 let mut bounds = Vec::new();
346 for (g, geometry) in geometries.iter().enumerate() {
347 for k in 0..geometry.primitives() {
348 let mut b = Aabb::EMPTY;
349 if geometry.procedural() {
350 let corners = geometry
351 .boxes
352 .get(k)
353 .ok_or_else(|| format!("geometry {g}: box {k} is past its boxes"))?;
354 corners.iter().for_each(|&p| b.grow(p));
355 } else {
356 let tri = geometry.triangle(k).ok_or_else(|| {
357 format!("geometry {g}: triangle {k} names a vertex that does not exist")
358 })?;
359 tri.iter().for_each(|&p| b.grow(p));
360 }
361 primitives.push((
362 u32::try_from(g).map_err(|_| "more than 4294967295 geometries")?,
363 u32::try_from(k).map_err(|_| "more than 4294967295 primitives")?,
364 ));
365 bounds.push(b);
366 }
367 }
368 let bvh = Bvh::build(&bounds);
369 bottoms.push(BuiltBottom {
370 geometries: geometries.clone(),
371 primitives,
372 bvh,
373 });
374 }
375 let mut instances = Vec::new();
376 let mut bounds = Vec::new();
377 for (i, instance) in scene.instances.iter().enumerate() {
378 let bottom = bottoms.get(instance.bottom).ok_or_else(|| {
379 format!(
380 "instance {i} is of bottom level {}, which does not exist",
381 instance.bottom
382 )
383 })?;
384 let inverse =
385 invert(&instance.transform).ok_or_else(|| format!("instance {i}: a singular transform"))?;
386 let mut b = Aabb::EMPTY;
387 if let Some(root) = bottom.bvh.nodes.first() {
388 for corner in 0..8 {
389 let c: Vec3 = std::array::from_fn(|k| {
390 if corner >> k & 1 == 0 {
391 root.bounds.min[k]
392 } else {
393 root.bounds.max[k]
394 }
395 });
396 b.grow(transform_point(&instance.transform, c));
397 }
398 }
399 bounds.push(b);
400 instances.push(BuiltInstance {
401 instance: instance.clone(),
402 inverse,
403 });
404 }
405 let top = Bvh::build(&bounds);
406 Ok(Built {
407 bottoms,
408 instances,
409 top,
410 })
411 }
412}
413
414#[derive(Clone, Copy, Debug)]
416pub(super) struct Hit {
417 pub kind: u32,
419 pub t: f32,
420 pub barycentrics: [f32; 2],
421 pub front: bool,
422 pub instance: u32,
423 pub geometry: u32,
424 pub primitive: u32,
425 pub opaque: bool,
427}
428
429#[derive(Clone, Copy, Debug)]
430enum Frame {
431 Top(u32),
432 Instance(u32),
433 Bottom(u32),
434 Triangle(u32, u32),
435}
436
437#[derive(Clone, Debug)]
439pub(super) struct Query {
440 scene: iridia::ResourceId,
441 pub flags: u32,
442 mask: u32,
443 pub tmin: f32,
444 tmax: f32,
445 pub origin: Vec3,
446 pub direction: Vec3,
447 stack: Vec<Frame>,
448 started: bool,
449 done: bool,
450 inside: Option<(u32, Vec3, Vec3)>,
452 pub candidate: Option<Hit>,
453 pub committed: Option<Hit>,
454}
455
456fn inverse_direction(d: Vec3) -> Vec3 {
457 d.map(|x| 1.0 / x)
458}
459
460impl Query {
461 pub(super) fn new(
462 scene: iridia::ResourceId,
463 flags: u32,
464 mask: u32,
465 tmin: f32,
466 tmax: f32,
467 origin: Vec3,
468 direction: Vec3,
469 ) -> Query {
470 Query {
471 scene,
472 flags,
473 mask,
474 tmin,
475 tmax,
476 origin,
477 direction,
478 stack: Vec::new(),
479 started: false,
480 done: false,
481 inside: None,
482 candidate: None,
483 committed: None,
484 }
485 }
486
487 pub(super) fn scene(&self) -> iridia::ResourceId {
488 self.scene
489 }
490
491 pub(super) fn tmax(&self) -> f32 {
493 self.tmax
494 }
495
496 pub(super) fn terminate(&mut self) {
498 self.done = true;
499 self.stack.clear();
500 }
501
502 pub(super) fn confirm(&mut self) -> Result<(), String> {
504 let Some(hit) = self.candidate.take() else {
505 return Err("a candidate hit committed when there is none".into());
506 };
507 self.tmax = hit.t;
508 self.committed = Some(hit);
509 if self.flags & flags::ACCEPT_FIRST_HIT != 0 {
510 self.terminate();
511 }
512 Ok(())
513 }
514
515 fn opaque_of(&self, geometry: &Geometry, f: InstanceFlags) -> bool {
518 if self.flags & flags::FORCE_OPAQUE != 0 {
519 true
520 } else if self.flags & flags::FORCE_NON_OPAQUE != 0 {
521 false
522 } else if f.force_opaque {
523 true
524 } else if f.force_non_opaque {
525 false
526 } else {
527 geometry.opaque
528 }
529 }
530
531 pub(super) fn generate(&mut self, t: f32) -> Result<(), String> {
544 let Some(mut hit) = self.candidate else {
545 return Err("an intersection generated when there is no candidate".into());
546 };
547 if hit.kind != 3 {
548 return Err(
549 "an intersection generated for a candidate that is not a procedural primitive".into(),
550 );
551 }
552 if !(self.tmin <= t && t <= self.tmax) {
553 return Ok(());
554 }
555 hit.kind = 2;
556 hit.t = t;
557 self.tmax = t;
559 self.committed = Some(hit);
560 if self.flags & flags::ACCEPT_FIRST_HIT != 0 {
561 self.terminate();
562 }
563 Ok(())
564 }
565
566 #[allow(clippy::arithmetic_side_effects, clippy::indexing_slicing)] pub(super) fn proceed(&mut self, scene: &Built) -> bool {
569 self.candidate = None;
570 if self.done {
571 return false;
572 }
573 if !self.started {
574 self.started = true;
575 if !scene.top.nodes.is_empty() {
576 self.stack.push(Frame::Top(0));
577 }
578 }
579 while let Some(frame) = self.stack.pop() {
580 match frame {
581 Frame::Top(n) => {
582 let node = &scene.top.nodes[n as usize];
583 let inv = inverse_direction(self.direction);
584 if !crosses(&node.bounds, self.origin, inv, self.tmin, self.tmax) {
585 continue;
586 }
587 if node.count == 0 {
588 self.stack.push(Frame::Top(node.first + 1));
589 self.stack.push(Frame::Top(node.first));
590 } else {
591 for k in (node.first..node.first + node.count).rev() {
592 self.stack.push(Frame::Instance(scene.top.order[k as usize]));
593 }
594 }
595 }
596 Frame::Instance(i) => {
597 let instance = &scene.instances[i as usize];
598 if self.mask & u32::from(instance.instance.mask) == 0 {
599 continue;
600 }
601 let bottom = &scene.bottoms[instance.instance.bottom];
602 if bottom.bvh.nodes.is_empty() {
603 continue;
604 }
605 self.inside = Some((
606 i,
607 transform_point(&instance.inverse, self.origin),
608 transform_vector(&instance.inverse, self.direction),
609 ));
610 self.stack.push(Frame::Bottom(0));
611 }
612 Frame::Bottom(n) => {
613 let (i, origin, direction) = self.inside.expect("inside an instance");
614 let bottom = &scene.bottoms[scene.instances[i as usize].instance.bottom];
615 let node = &bottom.bvh.nodes[n as usize];
616 if !crosses(
617 &node.bounds,
618 origin,
619 inverse_direction(direction),
620 self.tmin,
621 self.tmax,
622 ) {
623 continue;
624 }
625 if node.count == 0 {
626 self.stack.push(Frame::Bottom(node.first + 1));
627 self.stack.push(Frame::Bottom(node.first));
628 } else {
629 for k in (node.first..node.first + node.count).rev() {
630 let (g, t) = bottom.primitives[bottom.bvh.order[k as usize] as usize];
631 self.stack.push(Frame::Triangle(g, t));
632 }
633 }
634 }
635 Frame::Triangle(g, k) => {
636 let (i, origin, direction) = self.inside.expect("inside an instance");
637 let built = &scene.instances[i as usize];
638 let geometry = &scene.bottoms[built.instance.bottom].geometries[g as usize];
639 if geometry.procedural() {
640 if self.flags & flags::SKIP_PROCEDURAL != 0 {
641 continue;
642 }
643 let [min, max] = geometry.boxes[k as usize];
644 let boxed = Aabb { min, max };
645 if !crosses(&boxed, origin, inverse_direction(direction), self.tmin, self.tmax) {
646 continue;
647 }
648 let f = built.instance.flags;
649 let opaque = self.opaque_of(geometry, f);
650 if (self.flags & flags::CULL_OPAQUE != 0 && opaque)
651 || (self.flags & flags::CULL_NON_OPAQUE != 0 && !opaque)
652 {
653 continue;
654 }
655 self.candidate = Some(Hit {
656 kind: 3,
657 t: self.tmax,
658 barycentrics: [0.0; 2],
659 front: false,
660 instance: i,
661 geometry: g,
662 primitive: k,
663 opaque,
664 });
665 return true;
666 }
667 if self.flags & flags::SKIP_TRIANGLES != 0 {
668 continue;
669 }
670 let Some(tri) = geometry.triangle(k as usize) else {
671 continue;
672 };
673 let Some((t, b1, b2, front_clockwise)) = intersect(&tri, origin, direction) else {
674 continue;
675 };
676 if !(self.tmin < t && t < self.tmax) {
678 continue;
679 }
680 let f = built.instance.flags;
681 let front = front_clockwise != f.front_counter_clockwise;
683 let opaque = self.opaque_of(geometry, f);
684 if (self.flags & flags::CULL_OPAQUE != 0 && opaque)
685 || (self.flags & flags::CULL_NON_OPAQUE != 0 && !opaque)
686 {
687 continue;
688 }
689 if !f.cull_disable
690 && ((self.flags & flags::CULL_BACK_FACING != 0 && !front)
691 || (self.flags & flags::CULL_FRONT_FACING != 0 && front))
692 {
693 continue;
694 }
695 let hit = Hit {
696 kind: 1,
697 t,
698 barycentrics: [b1, b2],
699 front,
700 instance: i,
701 geometry: g,
702 primitive: k,
703 opaque,
704 };
705 if opaque {
706 self.committed = Some(hit);
707 self.tmax = t;
708 if self.flags & flags::ACCEPT_FIRST_HIT != 0 {
709 self.terminate();
710 return false;
711 }
712 } else {
713 self.candidate = Some(hit);
714 return true;
715 }
716 }
717 }
718 }
719 self.done = true;
720 false
721 }
722}
723
724#[allow(
727 clippy::arithmetic_side_effects,
728 clippy::cast_possible_truncation,
729 clippy::indexing_slicing
730)] fn intersect(tri: &[Vec3; 3], origin: Vec3, direction: Vec3) -> Option<(f32, f32, f32, bool)> {
732 let abs = direction.map(f32::abs);
734 let kz = if abs[0] >= abs[1] && abs[0] >= abs[2] {
735 0
736 } else if abs[1] >= abs[2] {
737 1
738 } else {
739 2
740 };
741 let (mut kx, mut ky) = ((kz + 1) % 3, (kz + 2) % 3);
742 if direction[kz] < 0.0 {
743 std::mem::swap(&mut kx, &mut ky);
744 }
745 let s = [
746 direction[kx] / direction[kz],
747 direction[ky] / direction[kz],
748 1.0 / direction[kz],
749 ];
750 let rel: [Vec3; 3] = std::array::from_fn(|j| std::array::from_fn(|k| tri[j][k] - origin[k]));
751 let shear = |p: &Vec3| -> [f32; 2] { [p[kx] - s[0] * p[kz], p[ky] - s[1] * p[kz]] };
752 let (a, b, c) = (shear(&rel[0]), shear(&rel[1]), shear(&rel[2]));
753 let mut u = c[0] * b[1] - c[1] * b[0];
754 let mut v = a[0] * c[1] - a[1] * c[0];
755 let mut w = b[0] * a[1] - b[1] * a[0];
756 if u == 0.0 || v == 0.0 || w == 0.0 {
757 let (c0, c1, b0, b1, a0, a1) = (
759 f64::from(c[0]),
760 f64::from(c[1]),
761 f64::from(b[0]),
762 f64::from(b[1]),
763 f64::from(a[0]),
764 f64::from(a[1]),
765 );
766 u = (c0 * b1 - c1 * b0) as f32;
767 v = (a0 * c1 - a1 * c0) as f32;
768 w = (b0 * a1 - b1 * a0) as f32;
769 }
770 if (u < 0.0 || v < 0.0 || w < 0.0) && (u > 0.0 || v > 0.0 || w > 0.0) {
771 return None;
772 }
773 let det = u + v + w;
774 if det == 0.0 {
775 return None;
776 }
777 let (az, bz, cz) = (s[2] * rel[0][kz], s[2] * rel[1][kz], s[2] * rel[2][kz]);
778 let t = (u * az + v * bz + w * cz) / det;
779 let e1: Vec3 = std::array::from_fn(|k| tri[1][k] - tri[0][k]);
781 let e2: Vec3 = std::array::from_fn(|k| tri[2][k] - tri[0][k]);
782 let normal = [
783 e1[1] * e2[2] - e1[2] * e2[1],
784 e1[2] * e2[0] - e1[0] * e2[2],
785 e1[0] * e2[1] - e1[1] * e2[0],
786 ];
787 let along: f32 = (0..3).map(|k| normal[k] * direction[k]).sum();
788 Some((t, v / det, w / det, along < 0.0))
789}
790
791pub(super) struct Reported {
793 pub kind: u32,
794 pub t: f32,
795 pub custom: u32,
796 pub instance: u32,
797 pub sbt: u32,
798 pub geometry: u32,
799 pub primitive: u32,
800 pub barycentrics: [f32; 2],
801 pub front: bool,
802 pub object_to_world: [Vec3; 4],
804 pub world_to_object: [Vec3; 4],
805}
806
807impl Reported {
808 pub(super) fn none() -> Reported {
809 Reported {
810 kind: 0,
811 t: 0.0,
812 custom: 0,
813 instance: 0,
814 sbt: 0,
815 geometry: 0,
816 primitive: 0,
817 barycentrics: [0.0; 2],
818 front: false,
819 object_to_world: [[0.0; 3]; 4],
820 world_to_object: [[0.0; 3]; 4],
821 }
822 }
823
824 #[allow(clippy::indexing_slicing)] pub(super) fn of(hit: &Hit, scene: &Built) -> Reported {
826 let built = &scene.instances[hit.instance as usize];
827 let columns =
828 |m: &[[f32; 4]; 3]| -> [Vec3; 4] { std::array::from_fn(|c| std::array::from_fn(|r| m[r][c])) };
829 Reported {
830 kind: hit.kind,
831 t: hit.t,
832 custom: built.instance.id & 0x00ff_ffff,
833 instance: hit.instance,
834 sbt: built.instance.contribution & 0x00ff_ffff,
835 geometry: hit.geometry,
836 primitive: hit.primitive,
837 barycentrics: hit.barycentrics,
838 front: hit.front,
839 object_to_world: columns(&built.instance.transform),
840 world_to_object: columns(&built.inverse),
841 }
842 }
843}
844
845pub(super) type Scenes = BTreeMap<iridia::ResourceId, std::sync::Arc<Built>>;