Skip to main content

phonon/scene/
static_mesh.rs

1//
2// Copyright 2017-2023 Valve Corporation.
3// Copyright 2024 phonon_rs contributors.
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9//     http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16//
17
18use crate::scene::hit::Hit;
19use crate::scene::material::Material;
20use crate::scene::mesh::Mesh;
21use crate::scene::ray::Ray;
22use crate::scene::triangle::Triangle;
23use glam::Vec3;
24use ndarray::Array1;
25use parry3d::query::RayCast;
26
27/// A static triangle mesh. The geometry of this mesh is assumed to never change at runtime. It is described in
28/// world-space coordinates. Materials are specified for each triangle.
29#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
30pub struct StaticMesh {
31    mesh: Mesh,
32    material_indices: Array1<usize>,
33    materials: Array1<Material>,
34}
35
36/// An IStaticMesh implementation that uses the built-in ray tracer backend.
37impl StaticMesh {
38    pub fn new(
39        vertices: Vec<Vec3>,
40        triangles: Vec<Triangle>,
41        material_indices: Vec<usize>,
42        materials: Vec<Material>,
43    ) -> Self {
44        Self {
45            mesh: Mesh::new(vertices, triangles),
46            material_indices: material_indices.into(),
47            materials: materials.into(),
48        }
49    }
50
51    pub fn new_static_mesh(
52        vertices: Vec<Vec3>,
53        triangles: Vec<[u32; 3]>,
54        material_indices: Vec<usize>,
55        materials: Vec<Material>,
56    ) -> Self {
57        let triangles = triangles
58            .iter()
59            .map(|triangle| Triangle {
60                indices: [
61                    triangle[0] as usize,
62                    triangle[1] as usize,
63                    triangle[2] as usize,
64                ],
65            })
66            .collect();
67
68        Self {
69            mesh: Mesh::new(vertices, triangles),
70            material_indices: material_indices.into(),
71            materials: materials.into(),
72        }
73    }
74
75    // todo ability to create mesh with a single material
76    pub fn new_from_mesh(
77        mesh: Mesh,
78        //material_indices: Vec<usize>,
79        //materials: Vec<Material>,
80    ) -> Self {
81        let material = Material {
82            absorption: [0.5, 0.3, 0.1],
83            scattering: 0.05,
84            transmission: [0.5, 0.3, 0.1],
85        };
86
87        let num_triangles = mesh.mesh.num_triangles();
88        let materials = vec![material];
89
90        Self {
91            mesh,
92            material_indices: Array1::zeros(num_triangles),
93            materials: materials.into(),
94        }
95    }
96
97    pub(crate) fn closest_hit(
98        &self,
99        ray: &Ray,
100        min_distance: f32,
101        max_distance: f32,
102    ) -> Option<Hit> {
103        let ray = Ray::new(ray.point_at_distance(min_distance), ray.direction());
104
105        // todo: What does `solid` need to be?
106        let info = self
107            .mesh
108            .mesh
109            .cast_local_ray_and_get_normal(&ray.0, max_distance, false);
110
111        return if let Some(hit) = info {
112            let mut triangle_index = hit.feature.unwrap_face() as usize;
113
114            if triangle_index >= self.mesh.mesh.indices().len() {
115                // A backface is hit, which means the index needs to be put back into bounds.
116                // See source code of `cast_local_ray_and_get_normal`.
117                triangle_index -= self.mesh.mesh.indices().len();
118            }
119
120            let material_index = self.material_indices[triangle_index];
121
122            Some(Hit {
123                distance: hit.toi,
124                triangle_index,
125                object_index: 0,
126                material_index,
127                normal: hit.normal.into(),
128                material: self.materials[material_index],
129            })
130        } else {
131            None
132        };
133    }
134
135    pub(crate) fn any_hit(&self, ray: &Ray, min_distance: f32, max_distance: f32) -> bool {
136        let ray = Ray::new(ray.point_at_distance(min_distance), ray.direction());
137
138        self.mesh
139            .mesh
140            .cast_local_ray(&ray.0, max_distance, false)
141            .is_some()
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    #[test]
150    fn test_static_mesh() {
151        let vertices = vec![
152            Vec3::new(0.0, 0.0, 0.0),
153            Vec3::new(1.0, 0.0, 0.0),
154            Vec3::new(0.0, 1.0, 0.0),
155            Vec3::new(1.0, 1.0, 0.0),
156            Vec3::new(2.0, 0.0, 0.0),
157        ];
158
159        let triangle0 = Triangle { indices: [0, 1, 2] };
160        let triangle1 = Triangle { indices: [1, 2, 3] };
161        let triangle2 = Triangle { indices: [1, 3, 4] };
162        let triangles = vec![triangle0, triangle1, triangle2];
163
164        let material = Material {
165            absorption: [0.1, 0.1, 0.1],
166            scattering: 0.05,
167            transmission: [0.0, 0.0, 0.0],
168        };
169
170        let materials = vec![material];
171        let material_indices = vec![0, 0, 0];
172
173        let static_mesh = StaticMesh::new(vertices, triangles, material_indices, materials);
174
175        let ray0: Ray = Ray::new(Vec3::new(0.1, 0.1, -1.0), Vec3::new(0.0, 0.0, 1.0));
176        let ray1: Ray = Ray::new(Vec3::new(0.6, 0.6, -1.0), Vec3::new(0.0, 0.0, 1.0));
177        let ray2: Ray = Ray::new(Vec3::new(1.1, 0.1, -1.0), Vec3::new(0.0, 0.0, 1.0));
178
179        let ray_miss: Ray = Ray::new(Vec3::new(1.0, 0.0, -1.0), Vec3::new(0.0, 1.0, 0.0));
180
181        let hit0 = static_mesh.closest_hit(&ray0, 0.0, 10.0);
182        let hit1 = static_mesh.closest_hit(&ray1, 0.0, 10.0);
183        let hit2 = static_mesh.closest_hit(&ray2, 0.0, 10.0);
184
185        assert_eq!(hit0.unwrap().triangle_index, 0);
186        assert_eq!(hit1.unwrap().triangle_index, 1);
187        assert_eq!(hit2.unwrap().triangle_index, 2);
188
189        assert!(!static_mesh.any_hit(&ray_miss, 0.0, 10.0));
190    }
191}