use std::collections::HashMap;
use crate::scene::{BoundingBox, MaterialId};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Topology {
Triangles,
TriangleStrip,
TriangleFan,
Lines,
LineStrip,
LineLoop,
Points,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Indices {
U16(Vec<u16>),
U32(Vec<u32>),
}
impl Indices {
pub fn len(&self) -> usize {
match self {
Self::U16(v) => v.len(),
Self::U32(v) => v.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct MorphTarget {
pub position: Option<Vec<[f32; 3]>>,
pub normal: Option<Vec<[f32; 3]>>,
pub tangent: Option<Vec<[f32; 3]>>,
}
impl MorphTarget {
pub fn new() -> Self {
Self::default()
}
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct Primitive {
pub topology: Topology,
pub positions: Vec<[f32; 3]>,
pub normals: Option<Vec<[f32; 3]>>,
pub tangents: Option<Vec<[f32; 4]>>,
pub uvs: Vec<Vec<[f32; 2]>>,
pub colors: Vec<Vec<[f32; 4]>>,
pub joints: Option<Vec<[u16; 4]>>,
pub weights: Option<Vec<[f32; 4]>>,
pub indices: Option<Indices>,
pub material: Option<MaterialId>,
pub targets: Vec<MorphTarget>,
pub extras: HashMap<String, serde_json::Value>,
}
impl Primitive {
pub fn new(topology: Topology) -> Self {
Self {
topology,
positions: Vec::new(),
normals: None,
tangents: None,
uvs: Vec::new(),
colors: Vec::new(),
joints: None,
weights: None,
indices: None,
material: None,
targets: Vec::new(),
extras: HashMap::new(),
}
}
pub fn triangle_count(&self) -> usize {
let n = self
.indices
.as_ref()
.map(|i| i.len())
.unwrap_or(self.positions.len());
match self.topology {
Topology::Triangles => n / 3,
Topology::TriangleStrip | Topology::TriangleFan => n.saturating_sub(2),
_ => 0,
}
}
pub fn bounding_box(&self) -> Option<BoundingBox> {
BoundingBox::from_points(self.positions.iter().copied())
}
pub fn triangle_indices(&self) -> Vec<[u32; 3]> {
let seq: Vec<u32> = match &self.indices {
Some(Indices::U16(v)) => v.iter().map(|&i| i as u32).collect(),
Some(Indices::U32(v)) => v.clone(),
None => (0..self.positions.len() as u32).collect(),
};
let n = seq.len();
match self.topology {
Topology::Triangles => {
let tris = n / 3;
let mut out = Vec::with_capacity(tris);
for t in 0..tris {
out.push([seq[3 * t], seq[3 * t + 1], seq[3 * t + 2]]);
}
out
}
Topology::TriangleStrip => {
if n < 3 {
return Vec::new();
}
let mut out = Vec::with_capacity(n - 2);
for i in 0..(n - 2) {
if i % 2 == 0 {
out.push([seq[i], seq[i + 1], seq[i + 2]]);
} else {
out.push([seq[i], seq[i + 2], seq[i + 1]]);
}
}
out
}
Topology::TriangleFan => {
if n < 3 {
return Vec::new();
}
let anchor = seq[0];
let mut out = Vec::with_capacity(n - 2);
for i in 1..(n - 1) {
out.push([anchor, seq[i], seq[i + 1]]);
}
out
}
_ => Vec::new(),
}
}
pub fn to_triangle_list(&self) -> Primitive {
let tris = self.triangle_indices();
let mut flat: Vec<u32> = Vec::with_capacity(tris.len() * 3);
for t in &tris {
flat.extend_from_slice(t);
}
let mut out = self.clone();
out.topology = Topology::Triangles;
out.indices = Some(Indices::U32(flat));
out
}
pub fn weld_vertices(&self) -> Primitive {
fn key(x: f32) -> u32 {
if x == 0.0 {
0 } else if x.is_nan() {
0x7fc0_0000 } else {
x.to_bits()
}
}
let n = self.positions.len();
let build_key = |i: usize| -> Vec<u32> {
let mut k = Vec::new();
let p = self.positions[i];
k.extend([key(p[0]), key(p[1]), key(p[2])]);
if let Some(ns) = &self.normals {
if let Some(v) = ns.get(i) {
k.extend([key(v[0]), key(v[1]), key(v[2])]);
}
}
if let Some(ts) = &self.tangents {
if let Some(v) = ts.get(i) {
k.extend([key(v[0]), key(v[1]), key(v[2]), key(v[3])]);
}
}
for set in &self.uvs {
if let Some(v) = set.get(i) {
k.extend([key(v[0]), key(v[1])]);
}
}
for set in &self.colors {
if let Some(v) = set.get(i) {
k.extend([key(v[0]), key(v[1]), key(v[2]), key(v[3])]);
}
}
if let Some(js) = &self.joints {
if let Some(v) = js.get(i) {
k.extend([v[0] as u32, v[1] as u32, v[2] as u32, v[3] as u32]);
}
}
if let Some(ws) = &self.weights {
if let Some(v) = ws.get(i) {
k.extend([key(v[0]), key(v[1]), key(v[2]), key(v[3])]);
}
}
for t in &self.targets {
if let Some(d) = &t.position {
if let Some(v) = d.get(i) {
k.extend([key(v[0]), key(v[1]), key(v[2])]);
}
}
if let Some(d) = &t.normal {
if let Some(v) = d.get(i) {
k.extend([key(v[0]), key(v[1]), key(v[2])]);
}
}
if let Some(d) = &t.tangent {
if let Some(v) = d.get(i) {
k.extend([key(v[0]), key(v[1]), key(v[2])]);
}
}
}
k
};
let mut dedup: HashMap<Vec<u32>, u32> = HashMap::new();
let mut remap: Vec<u32> = Vec::with_capacity(n);
let mut sources: Vec<usize> = Vec::new();
for i in 0..n {
let k = build_key(i);
let slot = *dedup.entry(k).or_insert_with(|| {
let id = sources.len() as u32;
sources.push(i);
id
});
remap.push(slot);
}
let gather3 =
|src: &Vec<[f32; 3]>| -> Vec<[f32; 3]> { sources.iter().map(|&i| src[i]).collect() };
let positions = gather3(&self.positions);
let normals = self.normals.as_ref().map(|s| {
sources
.iter()
.map(|&i| s.get(i).copied().unwrap_or([0.0; 3]))
.collect()
});
let tangents = self.tangents.as_ref().map(|s| {
sources
.iter()
.map(|&i| s.get(i).copied().unwrap_or([0.0; 4]))
.collect()
});
let uvs = self
.uvs
.iter()
.map(|set| {
sources
.iter()
.map(|&i| set.get(i).copied().unwrap_or([0.0; 2]))
.collect()
})
.collect();
let colors = self
.colors
.iter()
.map(|set| {
sources
.iter()
.map(|&i| set.get(i).copied().unwrap_or([0.0; 4]))
.collect()
})
.collect();
let joints = self.joints.as_ref().map(|s| {
sources
.iter()
.map(|&i| s.get(i).copied().unwrap_or([0; 4]))
.collect()
});
let weights = self.weights.as_ref().map(|s| {
sources
.iter()
.map(|&i| s.get(i).copied().unwrap_or([0.0; 4]))
.collect()
});
let targets = self
.targets
.iter()
.map(|t| MorphTarget {
position: t.position.as_ref().map(|d| {
sources
.iter()
.map(|&i| d.get(i).copied().unwrap_or([0.0; 3]))
.collect()
}),
normal: t.normal.as_ref().map(|d| {
sources
.iter()
.map(|&i| d.get(i).copied().unwrap_or([0.0; 3]))
.collect()
}),
tangent: t.tangent.as_ref().map(|d| {
sources
.iter()
.map(|&i| d.get(i).copied().unwrap_or([0.0; 3]))
.collect()
}),
})
.collect();
let new_indices: Vec<u32> = match &self.indices {
Some(Indices::U16(v)) => v
.iter()
.filter_map(|&i| remap.get(i as usize).copied())
.collect(),
Some(Indices::U32(v)) => v
.iter()
.filter_map(|&i| remap.get(i as usize).copied())
.collect(),
None => remap.clone(),
};
let indices = if sources.len() <= u16::MAX as usize + 1 {
Indices::U16(new_indices.iter().map(|&i| i as u16).collect())
} else {
Indices::U32(new_indices)
};
Primitive {
topology: self.topology,
positions,
normals,
tangents,
uvs,
colors,
joints,
weights,
indices: Some(indices),
material: self.material,
targets,
extras: self.extras.clone(),
}
}
pub fn compute_normals(&self) -> Vec<[f32; 3]> {
const FALLBACK: [f32; 3] = [0.0, 0.0, 1.0];
let n = self.positions.len();
let mut acc = vec![[0.0f32; 3]; n];
for [ia, ib, ic] in self.triangle_indices() {
let (ia, ib, ic) = (ia as usize, ib as usize, ic as usize);
if ia >= n || ib >= n || ic >= n {
continue;
}
let pa = self.positions[ia];
let pb = self.positions[ib];
let pc = self.positions[ic];
let u = [pb[0] - pa[0], pb[1] - pa[1], pb[2] - pa[2]];
let v = [pc[0] - pa[0], pc[1] - pa[1], pc[2] - pa[2]];
let fn_ = [
u[1] * v[2] - u[2] * v[1],
u[2] * v[0] - u[0] * v[2],
u[0] * v[1] - u[1] * v[0],
];
if !fn_[0].is_finite() || !fn_[1].is_finite() || !fn_[2].is_finite() {
continue;
}
for &i in &[ia, ib, ic] {
acc[i][0] += fn_[0];
acc[i][1] += fn_[1];
acc[i][2] += fn_[2];
}
}
for a in acc.iter_mut() {
let len = (a[0] * a[0] + a[1] * a[1] + a[2] * a[2]).sqrt();
if len.is_finite() && len > 0.0 {
a[0] /= len;
a[1] /= len;
a[2] /= len;
} else {
*a = FALLBACK;
}
}
acc
}
pub fn surface_area(&self) -> f64 {
let n = self.positions.len();
let mut total = 0.0_f64;
for [ia, ib, ic] in self.triangle_indices() {
let (ia, ib, ic) = (ia as usize, ib as usize, ic as usize);
if ia >= n || ib >= n || ic >= n {
continue;
}
let pa = self.positions[ia];
let pb = self.positions[ib];
let pc = self.positions[ic];
let ux = pb[0] as f64 - pa[0] as f64;
let uy = pb[1] as f64 - pa[1] as f64;
let uz = pb[2] as f64 - pa[2] as f64;
let vx = pc[0] as f64 - pa[0] as f64;
let vy = pc[1] as f64 - pa[1] as f64;
let vz = pc[2] as f64 - pa[2] as f64;
let cx = uy * vz - uz * vy;
let cy = uz * vx - ux * vz;
let cz = ux * vy - uy * vx;
if !cx.is_finite() || !cy.is_finite() || !cz.is_finite() {
continue;
}
let m2 = cx * cx + cy * cy + cz * cz;
if !m2.is_finite() {
continue;
}
total += m2.sqrt() * 0.5;
}
total
}
pub fn world_surface_area(&self, world: [[f32; 4]; 4]) -> f64 {
let n = self.positions.len();
let mut total = 0.0_f64;
let m00 = world[0][0] as f64;
let m01 = world[0][1] as f64;
let m02 = world[0][2] as f64;
let m03 = world[0][3] as f64;
let m10 = world[1][0] as f64;
let m11 = world[1][1] as f64;
let m12 = world[1][2] as f64;
let m13 = world[1][3] as f64;
let m20 = world[2][0] as f64;
let m21 = world[2][1] as f64;
let m22 = world[2][2] as f64;
let m23 = world[2][3] as f64;
let xform = |p: [f32; 3]| {
let x = p[0] as f64;
let y = p[1] as f64;
let z = p[2] as f64;
[
m00 * x + m01 * y + m02 * z + m03,
m10 * x + m11 * y + m12 * z + m13,
m20 * x + m21 * y + m22 * z + m23,
]
};
for [ia, ib, ic] in self.triangle_indices() {
let (ia, ib, ic) = (ia as usize, ib as usize, ic as usize);
if ia >= n || ib >= n || ic >= n {
continue;
}
let pa = xform(self.positions[ia]);
let pb = xform(self.positions[ib]);
let pc = xform(self.positions[ic]);
let ux = pb[0] - pa[0];
let uy = pb[1] - pa[1];
let uz = pb[2] - pa[2];
let vx = pc[0] - pa[0];
let vy = pc[1] - pa[1];
let vz = pc[2] - pa[2];
let cx = uy * vz - uz * vy;
let cy = uz * vx - ux * vz;
let cz = ux * vy - uy * vx;
if !cx.is_finite() || !cy.is_finite() || !cz.is_finite() {
continue;
}
let m2 = cx * cx + cy * cy + cz * cz;
if !m2.is_finite() {
continue;
}
total += m2.sqrt() * 0.5;
}
total
}
pub fn signed_volume(&self) -> f64 {
let n = self.positions.len();
let mut total = 0.0_f64;
for [ia, ib, ic] in self.triangle_indices() {
let (ia, ib, ic) = (ia as usize, ib as usize, ic as usize);
if ia >= n || ib >= n || ic >= n {
continue;
}
let pa = self.positions[ia];
let pb = self.positions[ib];
let pc = self.positions[ic];
let ax = pa[0] as f64;
let ay = pa[1] as f64;
let az = pa[2] as f64;
let bx = pb[0] as f64;
let by = pb[1] as f64;
let bz = pb[2] as f64;
let cx = pc[0] as f64;
let cy = pc[1] as f64;
let cz = pc[2] as f64;
let crx = by * cz - bz * cy;
let cry = bz * cx - bx * cz;
let crz = bx * cy - by * cx;
if !crx.is_finite() || !cry.is_finite() || !crz.is_finite() {
continue;
}
let tri = ax * crx + ay * cry + az * crz;
if !tri.is_finite() {
continue;
}
total += tri;
}
total / 6.0
}
pub fn volume(&self) -> f64 {
self.signed_volume().abs()
}
pub fn compute_tangents(&self, uv_set: usize) -> Option<Vec<[f32; 4]>> {
const FALLBACK: [f32; 4] = [1.0, 0.0, 0.0, 1.0];
let n = self.positions.len();
if n == 0 {
return None;
}
let normals = self.normals.as_ref()?;
if normals.len() != n {
return None;
}
let uvs = self.uvs.get(uv_set)?;
if uvs.len() != n {
return None;
}
let mut t_acc = vec![[0.0f32; 3]; n];
let mut b_acc = vec![[0.0f32; 3]; n];
for [ia, ib, ic] in self.triangle_indices() {
let (ia, ib, ic) = (ia as usize, ib as usize, ic as usize);
if ia >= n || ib >= n || ic >= n {
continue;
}
let pa = self.positions[ia];
let pb = self.positions[ib];
let pc = self.positions[ic];
let qa = uvs[ia];
let qb = uvs[ib];
let qc = uvs[ic];
let e1 = [pb[0] - pa[0], pb[1] - pa[1], pb[2] - pa[2]];
let e2 = [pc[0] - pa[0], pc[1] - pa[1], pc[2] - pa[2]];
let du1 = qb[0] - qa[0];
let dv1 = qb[1] - qa[1];
let du2 = qc[0] - qa[0];
let dv2 = qc[1] - qa[1];
let det = du1 * dv2 - du2 * dv1;
if !det.is_finite() || det == 0.0 {
continue;
}
let sgn = if det > 0.0 { 1.0 } else { -1.0 };
let t_tri = [
sgn * (dv2 * e1[0] - dv1 * e2[0]),
sgn * (dv2 * e1[1] - dv1 * e2[1]),
sgn * (dv2 * e1[2] - dv1 * e2[2]),
];
let b_tri = [
sgn * (-du2 * e1[0] + du1 * e2[0]),
sgn * (-du2 * e1[1] + du1 * e2[1]),
sgn * (-du2 * e1[2] + du1 * e2[2]),
];
if !t_tri[0].is_finite()
|| !t_tri[1].is_finite()
|| !t_tri[2].is_finite()
|| !b_tri[0].is_finite()
|| !b_tri[1].is_finite()
|| !b_tri[2].is_finite()
{
continue;
}
for &i in &[ia, ib, ic] {
t_acc[i][0] += t_tri[0];
t_acc[i][1] += t_tri[1];
t_acc[i][2] += t_tri[2];
b_acc[i][0] += b_tri[0];
b_acc[i][1] += b_tri[1];
b_acc[i][2] += b_tri[2];
}
}
let mut out = vec![FALLBACK; n];
for i in 0..n {
let n_v = normals[i];
let t_sum = t_acc[i];
let b_sum = b_acc[i];
let tlen2 = t_sum[0] * t_sum[0] + t_sum[1] * t_sum[1] + t_sum[2] * t_sum[2];
if !tlen2.is_finite() || tlen2 == 0.0 {
continue;
}
let nlen2 = n_v[0] * n_v[0] + n_v[1] * n_v[1] + n_v[2] * n_v[2];
if !nlen2.is_finite() || nlen2 == 0.0 {
continue;
}
let dot_tn = t_sum[0] * n_v[0] + t_sum[1] * n_v[1] + t_sum[2] * n_v[2];
let coef = dot_tn / nlen2;
let mut t = [
t_sum[0] - coef * n_v[0],
t_sum[1] - coef * n_v[1],
t_sum[2] - coef * n_v[2],
];
let len = (t[0] * t[0] + t[1] * t[1] + t[2] * t[2]).sqrt();
if !len.is_finite() || len == 0.0 {
continue;
}
t[0] /= len;
t[1] /= len;
t[2] /= len;
let cross = [
n_v[1] * t[2] - n_v[2] * t[1],
n_v[2] * t[0] - n_v[0] * t[2],
n_v[0] * t[1] - n_v[1] * t[0],
];
let dot_cb = cross[0] * b_sum[0] + cross[1] * b_sum[1] + cross[2] * b_sum[2];
let w = if dot_cb < 0.0 { -1.0 } else { 1.0 };
out[i] = [t[0], t[1], t[2], w];
}
Some(out)
}
pub fn apply_morph_weights(&self, weights: &[f32]) -> MorphedAttributes {
let n = self.positions.len();
let mut positions = self.positions.clone();
let mut normals = self.normals.clone();
let mut tangents = self.tangents.clone();
let t_max = self.targets.len().min(weights.len());
for (target, &w) in self.targets.iter().zip(weights.iter()).take(t_max) {
if w == 0.0 {
continue; }
if let Some(d) = &target.position {
let lim = n.min(d.len());
for k in 0..lim {
positions[k][0] += w * d[k][0];
positions[k][1] += w * d[k][1];
positions[k][2] += w * d[k][2];
}
}
if let (Some(base), Some(d)) = (normals.as_mut(), target.normal.as_ref()) {
let lim = base.len().min(d.len());
for k in 0..lim {
base[k][0] += w * d[k][0];
base[k][1] += w * d[k][1];
base[k][2] += w * d[k][2];
}
}
if let (Some(base), Some(d)) = (tangents.as_mut(), target.tangent.as_ref()) {
let lim = base.len().min(d.len());
for k in 0..lim {
base[k][0] += w * d[k][0];
base[k][1] += w * d[k][1];
base[k][2] += w * d[k][2];
}
}
}
MorphedAttributes {
positions,
normals,
tangents,
}
}
pub fn degenerate_triangles(&self) -> Vec<usize> {
let n = self.positions.len();
let mut out = Vec::new();
for (t, [ia, ib, ic]) in self.triangle_indices().into_iter().enumerate() {
let (ia, ib, ic) = (ia as usize, ib as usize, ic as usize);
if ia >= n || ib >= n || ic >= n {
out.push(t);
continue;
}
let pa = self.positions[ia];
let pb = self.positions[ib];
let pc = self.positions[ic];
let u = [pb[0] - pa[0], pb[1] - pa[1], pb[2] - pa[2]];
let v = [pc[0] - pa[0], pc[1] - pa[1], pc[2] - pa[2]];
let cx = u[1] * v[2] - u[2] * v[1];
let cy = u[2] * v[0] - u[0] * v[2];
let cz = u[0] * v[1] - u[1] * v[0];
if !cx.is_finite() || !cy.is_finite() || !cz.is_finite() {
out.push(t);
continue;
}
if cx == 0.0 && cy == 0.0 && cz == 0.0 {
out.push(t);
}
}
out
}
pub fn edge_manifold_report(&self) -> EdgeManifoldReport {
let n = self.positions.len();
let mut edge_uses: HashMap<(u32, u32), u32> = HashMap::new();
for [ia, ib, ic] in self.triangle_indices() {
if (ia as usize) >= n || (ib as usize) >= n || (ic as usize) >= n {
continue;
}
if ia == ib || ib == ic || ia == ic {
continue;
}
for (a, b) in [(ia, ib), (ib, ic), (ic, ia)] {
let key = if a < b { (a, b) } else { (b, a) };
*edge_uses.entry(key).or_insert(0) += 1;
}
}
let mut boundary = 0usize;
let mut interior = 0usize;
let mut non_manifold = 0usize;
let mut max_use = 0u32;
for &count in edge_uses.values() {
match count {
0 => unreachable!("HashMap entry is always >= 1"),
1 => boundary += 1,
2 => interior += 1,
_ => non_manifold += 1,
}
if count > max_use {
max_use = count;
}
}
EdgeManifoldReport {
total_edge_count: edge_uses.len(),
boundary_edge_count: boundary,
manifold_interior_edge_count: interior,
non_manifold_edge_count: non_manifold,
max_edge_use: max_use,
}
}
pub fn intersect_ray(&self, ray: crate::ray::Ray, t_max: f32) -> Option<crate::ray::RayHit> {
let n = self.positions.len();
let mut closest: Option<crate::ray::RayHit> = None;
let mut best_t = t_max;
for (tri_idx, [ia, ib, ic]) in self.triangle_indices().into_iter().enumerate() {
if (ia as usize) >= n || (ib as usize) >= n || (ic as usize) >= n {
continue;
}
let p0 = self.positions[ia as usize];
let p1 = self.positions[ib as usize];
let p2 = self.positions[ic as usize];
if let Some((t, u, v, front)) = crate::ray::intersect_triangle(ray, p0, p1, p2, best_t)
{
let w = 1.0 - u - v;
closest = Some(crate::ray::RayHit {
t,
triangle_index: tri_idx,
barycentric: [w, u, v],
front_face: front,
});
best_t = t;
}
}
closest
}
pub fn any_ray_intersection(&self, ray: crate::ray::Ray, t_max: f32) -> bool {
let n = self.positions.len();
for [ia, ib, ic] in self.triangle_indices() {
if (ia as usize) >= n || (ib as usize) >= n || (ic as usize) >= n {
continue;
}
let p0 = self.positions[ia as usize];
let p1 = self.positions[ib as usize];
let p2 = self.positions[ic as usize];
if crate::ray::intersect_triangle(ray, p0, p1, p2, t_max).is_some() {
return true;
}
}
false
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct EdgeManifoldReport {
pub total_edge_count: usize,
pub boundary_edge_count: usize,
pub manifold_interior_edge_count: usize,
pub non_manifold_edge_count: usize,
pub max_edge_use: u32,
}
impl EdgeManifoldReport {
pub fn is_closed_manifold(&self) -> bool {
self.total_edge_count > 0
&& self.boundary_edge_count == 0
&& self.non_manifold_edge_count == 0
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct MorphedAttributes {
pub positions: Vec<[f32; 3]>,
pub normals: Option<Vec<[f32; 3]>>,
pub tangents: Option<Vec<[f32; 4]>>,
}
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct Mesh {
pub name: Option<String>,
pub primitives: Vec<Primitive>,
pub weights: Vec<f32>,
}
impl Mesh {
pub fn new(name: impl Into<Option<String>>) -> Self {
Self {
name: name.into(),
primitives: Vec::new(),
weights: Vec::new(),
}
}
pub fn with_primitive(mut self, primitive: Primitive) -> Self {
self.primitives.push(primitive);
self
}
pub fn with_weights(mut self, weights: impl Into<Vec<f32>>) -> Self {
self.weights = weights.into();
self
}
pub fn bounding_box(&self) -> Option<BoundingBox> {
self.primitives
.iter()
.filter_map(|p| p.bounding_box())
.reduce(BoundingBox::union)
}
pub fn surface_area(&self) -> f64 {
self.primitives.iter().map(|p| p.surface_area()).sum()
}
pub fn signed_volume(&self) -> f64 {
self.primitives.iter().map(|p| p.signed_volume()).sum()
}
pub fn volume(&self) -> f64 {
self.signed_volume().abs()
}
pub fn intersect_ray(
&self,
ray: crate::ray::Ray,
t_max: f32,
) -> Option<(usize, crate::ray::RayHit)> {
let mut best: Option<(usize, crate::ray::RayHit)> = None;
let mut best_t = t_max;
for (idx, prim) in self.primitives.iter().enumerate() {
if let Some(hit) = prim.intersect_ray(ray, best_t) {
best_t = hit.t;
best = Some((idx, hit));
}
}
best
}
}