1use engvis_core::{
2 Aabb, LightingEnvironment, Mesh, MeshVertex, PbrMaterial, Scene, SceneNode,
3 SubMesh,
4};
5use glam::{Affine3A, Quat, Vec3};
6use crate::texture_cache::TextureCache;
7
8fn gltf_image_to_rgba(img: &gltf::image::Data) -> image::RgbaImage {
10 let pixels = &img.pixels;
11 let width = img.width;
12 let height = img.height;
13
14 match img.format {
15 gltf::image::Format::R8G8B8A8 => {
16 image::RgbaImage::from_raw(width, height, pixels.clone())
17 .unwrap_or_else(|| image::RgbaImage::new(width, height))
18 }
19 gltf::image::Format::R8G8B8 => {
20 let mut rgba = Vec::with_capacity(pixels.len() / 3 * 4);
21 for chunk in pixels.chunks(3) {
22 if chunk.len() == 3 {
23 rgba.extend_from_slice(&[chunk[0], chunk[1], chunk[2], 255]);
24 }
25 }
26 image::RgbaImage::from_raw(width, height, rgba)
27 .unwrap_or_else(|| image::RgbaImage::new(width, height))
28 }
29 _ => {
30 image::RgbaImage::from_raw(width, height, pixels.clone())
31 .unwrap_or_else(|| image::RgbaImage::new(width, height))
32 }
33 }
34}
35
36#[derive(Debug)]
37pub enum GltfLoadError {
38 Io(std::io::Error),
39 Gltf(gltf::Error),
40 Image(image::ImageError),
41}
42
43impl From<std::io::Error> for GltfLoadError {
44 fn from(e: std::io::Error) -> Self {
45 GltfLoadError::Io(e)
46 }
47}
48
49impl From<gltf::Error> for GltfLoadError {
50 fn from(e: gltf::Error) -> Self {
51 GltfLoadError::Gltf(e)
52 }
53}
54
55impl From<image::ImageError> for GltfLoadError {
56 fn from(e: image::ImageError) -> Self {
57 GltfLoadError::Image(e)
58 }
59}
60
61impl std::fmt::Display for GltfLoadError {
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 match self {
64 GltfLoadError::Io(e) => write!(f, "IO error: {}", e),
65 GltfLoadError::Gltf(e) => write!(f, "glTF error: {}", e),
66 GltfLoadError::Image(e) => write!(f, "Image error: {}", e),
67 }
68 }
69}
70
71impl std::error::Error for GltfLoadError {}
72
73pub fn load_gltf(
77 path: &str,
78 device: &wgpu::Device,
79 queue: &wgpu::Queue,
80 texture_cache: &mut TextureCache,
81) -> Result<(Scene, Aabb), GltfLoadError> {
82 let (gltf, buffers, images) = gltf::import(path)?;
83
84 let mut meshes = Vec::new();
85 let mut materials = Vec::new();
86 let mut nodes = Vec::new();
87
88 for mat in gltf.materials() {
90 let pbr = mat.pbr_metallic_roughness();
91 let mut material = PbrMaterial {
92 name: mat.name().unwrap_or("unnamed").to_string(),
93 albedo: pbr.base_color_factor(),
94 metallic: pbr.metallic_factor(),
95 roughness: pbr.roughness_factor(),
96 emissive: mat.emissive_factor(),
97 normal_scale: mat.normal_texture().map_or(1.0, |t| t.scale()),
98 alpha_cutoff: mat.alpha_cutoff().unwrap_or(0.5),
99 albedo_texture: None,
100 metallic_roughness_texture: None,
101 normal_texture: None,
102 emissive_texture: None,
103 };
104
105 if let Some(info) = pbr.base_color_texture() {
107 let tex_index = info.texture().index();
108 if tex_index < images.len() {
109 let img = gltf_image_to_rgba(&images[tex_index]);
110 let idx = texture_cache.upload_image(
111 device,
112 queue,
113 &img,
114 "gltf_albedo",
115 true,
116 );
117 material.albedo_texture = Some(idx);
118 }
119 }
120
121 if let Some(info) = pbr.metallic_roughness_texture() {
123 let tex_index = info.texture().index();
124 if tex_index < images.len() {
125 let img = gltf_image_to_rgba(&images[tex_index]);
126 let idx = texture_cache.upload_image(
127 device,
128 queue,
129 &img,
130 "gltf_mr",
131 false,
132 );
133 material.metallic_roughness_texture = Some(idx);
134 }
135 }
136
137 if let Some(info) = mat.normal_texture() {
139 let tex_index = info.texture().index();
140 if tex_index < images.len() {
141 let img = gltf_image_to_rgba(&images[tex_index]);
142 let idx = texture_cache.upload_image(
143 device,
144 queue,
145 &img,
146 "gltf_normal",
147 false,
148 );
149 material.normal_texture = Some(idx);
150 }
151 }
152
153 if let Some(info) = mat.emissive_texture() {
155 let tex_index = info.texture().index();
156 if tex_index < images.len() {
157 let img = gltf_image_to_rgba(&images[tex_index]);
158 let idx = texture_cache.upload_image(
159 device,
160 queue,
161 &img,
162 "gltf_emissive",
163 true,
164 );
165 material.emissive_texture = Some(idx);
166 }
167 }
168
169 materials.push(material);
170 }
171
172 if materials.is_empty() {
174 materials.push(PbrMaterial::default());
175 }
176
177 for gltf_mesh in gltf.meshes() {
179 let mut vertices = Vec::new();
180 let mut indices = Vec::new();
181 let mut sub_meshes = Vec::new();
182 let mut aabb = Aabb::empty();
183
184 for primitive in gltf_mesh.primitives() {
185 let reader = primitive.reader(|buffer| Some(&buffers[buffer.index()]));
186
187 let positions: Vec<[f32; 3]> = reader
188 .read_positions()
189 .map(|iter| iter.collect())
190 .unwrap_or_default();
191 let normals: Vec<[f32; 3]> = reader
192 .read_normals()
193 .map(|iter| iter.collect())
194 .unwrap_or_default();
195 let uvs: Vec<[f32; 2]> = reader
196 .read_tex_coords(0)
197 .map(|iter| iter.into_f32().collect())
198 .unwrap_or_default();
199
200 let prim_indices: Vec<u32> = reader
201 .read_indices()
202 .map(|iter| iter.into_u32().collect())
203 .unwrap_or_default();
204
205 let tangents = if let Some(tan) = reader.read_tangents() {
207 tan.collect::<Vec<[f32; 4]>>()
208 } else {
209 engvis_core::math::compute_tangents(&positions, &normals, &uvs, &prim_indices)
210 };
211
212 let base_vertex = vertices.len() as u32;
213 for (i, &pos) in positions.iter().enumerate() {
214 vertices.push(MeshVertex {
215 position: pos,
216 normal: normals.get(i).copied().unwrap_or([0.0, 1.0, 0.0]),
217 uv: uvs.get(i).copied().unwrap_or([0.0, 0.0]),
218 tangent: tangents.get(i).copied().unwrap_or([1.0, 0.0, 0.0, 1.0]),
219 });
220 aabb.expand(Vec3::from(pos));
221 }
222
223 let index_offset = indices.len() as u32;
224 let adjusted_indices: Vec<u32> =
225 prim_indices.iter().map(|i| i + base_vertex).collect();
226 indices.extend_from_slice(&adjusted_indices);
227
228 sub_meshes.push(SubMesh {
229 material_index: primitive.material().index().unwrap_or(0),
230 index_offset,
231 index_count: adjusted_indices.len() as u32,
232 });
233 }
234
235 meshes.push(Mesh {
236 name: gltf_mesh.name().unwrap_or("unnamed").to_string(),
237 vertices,
238 indices,
239 sub_meshes,
240 aabb,
241 });
242 }
243
244 fn load_node(node: &gltf::Node) -> SceneNode {
246 let (translation, rotation, scale) = node.transform().decomposed();
247 let local_transform = Affine3A::from_scale_rotation_translation(
248 Vec3::from(scale),
249 Quat::from_array(rotation),
250 Vec3::from(translation),
251 );
252
253 SceneNode {
254 name: node.name().unwrap_or("node").to_string(),
255 local_transform,
256 mesh_index: node.mesh().map(|m| m.index()),
257 children: node.children().map(|child| load_node(&child)).collect(),
258 visible: true,
259 }
260 }
261
262 for gltf_scene in gltf.scenes() {
263 for node in gltf_scene.nodes() {
264 nodes.push(load_node(&node));
265 }
266 }
267
268 let scene = Scene {
269 meshes,
270 materials,
271 nodes,
272 lighting: LightingEnvironment::default(),
273 };
274
275 let aabb = scene.compute_aabb();
276
277 Ok((scene, aabb))
278}