1use std::{collections::HashMap, sync::Arc};
14
15use serde::{Deserialize, Serialize};
16use wde_logger::prelude::*;
17
18use bevy::{
19 asset::{AssetLoader, LoadContext, io::Reader},
20 prelude::*
21};
22use wde_animation::prelude::*;
23use wde_pbr::prelude::*;
24use wde_renderer::prelude::*;
25
26#[doc(hidden)]
27pub mod prelude {
28 pub use crate::GltfAsset;
29 pub use crate::GltfError;
30 pub use crate::GltfLight;
31 pub use crate::GltfLightKind;
32 pub use crate::GltfLoader;
33 pub use crate::GltfLoaderSettings;
34 pub use crate::GltfSpawnQueue;
35 pub use crate::MeshGeometry;
36 pub use crate::ModelCondition;
37}
38
39mod accessor;
40mod error;
41mod loader;
42mod material;
43mod model;
44mod parser;
45
46pub use error::GltfError;
47pub use model::{GltfBone, GltfLight, GltfLightKind, ModelCondition};
48
49pub struct GltfPlugin;
50impl Plugin for GltfPlugin {
51 fn build(&self, app: &mut App) {
52 app.init_asset::<GltfAsset>()
53 .init_asset_loader::<GltfAssetLoader>()
54 .init_resource::<GltfSpawnQueue>()
55 .add_systems(Update, process_gltf_spawn_queue);
56 }
57}
58
59#[derive(Resource, Default)]
61pub struct GltfSpawnQueue {
62 pending: Vec<(Entity, Handle<GltfAsset>)>
63}
64
65#[derive(Asset, TypePath, Clone)]
69pub struct GltfAsset {
70 path: String,
71
72 pub models: Vec<(Handle<Mesh>, Handle<PbrMaterial>)>,
75 pub conditions: Vec<Option<ModelCondition>>,
77 pub geometry: Vec<Arc<MeshGeometry>>,
80 pub bbox: MeshBbox,
82 pub properties: HashMap<String, (Vec3, Vec3)>,
85 pub bones: Vec<GltfBone>,
87 pub model_bones: Vec<Option<usize>>,
89 pub animations: HashMap<String, Handle<AnimationClip>>,
91 pub lights: Vec<GltfLight>
93}
94
95impl GltfAsset {
96 pub fn animation(&self, name: &str) -> Option<&Handle<AnimationClip>> {
97 self.animations.get(name)
98 }
99
100 pub fn spawn_lights(&self, commands: &mut Commands, parent: Entity) {
102 for light in &self.lights {
103 let color = wde_renderer::prelude::Color::LinearRgba(
104 light.color.x,
105 light.color.y,
106 light.color.z,
107 1.0
108 );
109 let mut entity = commands.spawn((
110 Name::new(format!("Light {}", light.name)),
111 light.transform,
112 ChildOf(parent)
113 ));
114 match light.kind {
115 GltfLightKind::Point => entity.insert(PointLight {
116 position: Vec3::ZERO,
117 color,
118 intensity: light.intensity,
119 range: light.range
120 }),
121 GltfLightKind::Spot {
122 inner_cone,
123 outer_cone
124 } => entity.insert(SpotLight {
125 position: Vec3::ZERO,
126 direction: Vec3::NEG_Z,
127 color,
128 intensity: light.intensity,
129 range: light.range,
130 inner_cone,
131 outer_cone
132 })
133 };
134 }
135 }
136
137 pub fn models_where(
139 &self,
140 connected: impl Fn(&str) -> bool
141 ) -> impl Iterator<Item = &(Handle<Mesh>, Handle<PbrMaterial>)> {
142 self.models
143 .iter()
144 .zip(self.conditions.iter())
145 .filter(move |(_, condition): &(_, &Option<ModelCondition>)| {
146 condition
147 .as_ref()
148 .is_none_or(|c| connected(&c.anchor) == c.connected)
149 })
150 .map(|(model, _)| model)
151 }
152}
153
154#[derive(Debug, Default)]
156pub struct MeshGeometry {
157 pub positions: Vec<Vec3>,
158 pub indices: Vec<u32>
159}
160
161#[derive(Serialize, Deserialize, Default)]
163pub struct GltfLoaderSettings {
164 pub stencil_value: Option<u32>
166}
167
168#[derive(Default, TypePath)]
169pub(crate) struct GltfAssetLoader;
170impl AssetLoader for GltfAssetLoader {
171 type Asset = GltfAsset;
172 type Settings = GltfLoaderSettings;
173 type Error = GltfError;
174
175 async fn load(
176 &self,
177 reader: &mut dyn Reader,
178 _settings: &Self::Settings,
179 load_context: &mut LoadContext<'_>
180 ) -> Result<Self::Asset, Self::Error> {
181 let path = load_context.path().clone();
182 debug!("Loading glTF file {}.", &path);
183
184 let mut bytes = Vec::new();
186 reader.read_to_end(&mut bytes).await?;
187 let model = parser::parse_gltf(bytes, &path.to_string())?;
188
189 let (raw_materials, raw_meshes, bounding_boxes) = loader::form_models(&model)?;
191
192 let materials_handles: Vec<Handle<PbrMaterial>> = raw_materials
194 .iter()
195 .map(|material| material.to_pbr(load_context))
196 .collect();
197
198 let mut bone_matrices: Vec<Mat4> = Vec::with_capacity(model.bones.len());
200 for bone in &model.bones {
201 let local = Mat4::from_scale_rotation_translation(
202 bone.rest.scale,
203 bone.rest.rotation,
204 bone.rest.translation
205 );
206 bone_matrices.push(match bone.parent {
207 Some(parent) => bone_matrices[parent] * local,
208 None => local
209 });
210 }
211
212 let mut models = Vec::new();
214 let mut conditions = Vec::new();
215 let mut geometry = Vec::new();
216 let mut model_bones = Vec::new();
217 let mut bbox_min = Vec3::splat(f32::INFINITY);
218 let mut bbox_max = Vec3::splat(f32::NEG_INFINITY);
219 for (i, (indices_data, vertices, material_id, condition, bone)) in
220 raw_meshes.iter().enumerate()
221 {
222 let label = format!("gltf_mesh_{}", i);
223 let (bb_min, bb_max) = bounding_boxes[i];
224 let mesh_asset = Mesh {
225 label: label.clone(),
226 vertices: vertices.clone(),
227 indices: indices_data.clone(),
228 bbox: MeshBbox {
229 min: bb_min,
230 max: bb_max
231 },
232 use_ssbo: true
233 };
234
235 models.push((
236 load_context.add_labeled_asset(label.clone(), mesh_asset),
237 materials_handles[*material_id].clone()
238 ));
239 conditions.push(condition.clone());
240 geometry.push(Arc::new(MeshGeometry {
241 positions: vertices.iter().map(|v| Vec3::from(v.position)).collect(),
242 indices: match indices_data.is_empty() {
243 true => (0..vertices.len() as u32).collect(),
244 false => indices_data.clone()
245 }
246 }));
247 model_bones.push(*bone);
248
249 let corners = match bone {
251 Some(bone) => (0..8)
252 .map(|c| {
253 let corner = Vec3::select(
254 bevy::math::BVec3::new(c & 1 != 0, c & 2 != 0, c & 4 != 0),
255 bb_max,
256 bb_min
257 );
258 bone_matrices[*bone].transform_point3(corner)
259 })
260 .collect(),
261 None => vec![bb_min, bb_max]
262 };
263 for corner in corners {
264 bbox_min = bbox_min.min(corner);
265 bbox_max = bbox_max.max(corner);
266 }
267 }
268
269 let animations = model
270 .animations
271 .into_iter()
272 .map(|clip| {
273 let name = clip.name.clone();
274 let handle = load_context.add_labeled_asset(format!("animation_{name}"), clip);
275 (name, handle)
276 })
277 .collect();
278
279 Ok(GltfAsset {
280 path: path.to_string(),
281 models,
282 conditions,
283 geometry,
284 bbox: MeshBbox {
285 min: bbox_min,
286 max: bbox_max
287 },
288 properties: model.properties,
289 bones: model.bones,
290 model_bones,
291 animations,
292 lights: model.lights
293 })
294 }
295
296 fn extensions(&self) -> &[&str] {
297 &["gltf", "glb"]
298 }
299}
300
301pub struct GltfLoader;
304impl GltfLoader {
305 pub fn spawn(queue: &mut GltfSpawnQueue, gltf_asset: Handle<GltfAsset>, parent: Entity) {
307 queue.pending.push((parent, gltf_asset));
308 }
309
310 pub fn try_spawn(
313 commands: &mut Commands,
314 gltf_asset: &Handle<GltfAsset>,
315 gltf_assets: &Assets<GltfAsset>,
316 parent: Entity
317 ) -> Option<()> {
318 let gltf_asset = gltf_assets.get(gltf_asset)?;
319
320 let mut bones: Vec<Entity> = Vec::with_capacity(gltf_asset.bones.len());
322 for bone in &gltf_asset.bones {
323 let bone_entity = commands
324 .spawn((
325 Name::new(format!("Bone {}", bone.name)),
326 bone.rest,
327 AnimationBone {
328 name: bone.name.clone(),
329 rest: bone.rest
330 },
331 ChildOf(bone.parent.map_or(parent, |p| bones[p]))
332 ))
333 .id();
334 bones.push(bone_entity);
335 }
336
337 for (i, (mesh_handle, material_handle)) in gltf_asset.models.iter().enumerate() {
338 let mesh_parent = gltf_asset.model_bones[i].map_or(parent, |bone| bones[bone]);
339 commands.spawn((
340 Name::new(format!(
341 "Mesh Entity {} for GLTF Model {}",
342 i, gltf_asset.path
343 )),
344 Transform::default(),
345 Mesh3d(mesh_handle.clone()),
346 PbrMaterial3d(material_handle.clone()),
347 ChildOf(mesh_parent)
348 ));
349 }
350 gltf_asset.spawn_lights(commands, parent);
351 Some(())
352 }
353}
354
355fn process_gltf_spawn_queue(
356 mut commands: Commands,
357 mut queue: ResMut<GltfSpawnQueue>,
358 gltf_assets: Res<Assets<GltfAsset>>
359) {
360 let mut i = 0;
361 while i < queue.pending.len() {
362 let (parent, gltf_asset) = &queue.pending[i];
363 if GltfLoader::try_spawn(&mut commands, gltf_asset, &gltf_assets, *parent).is_some() {
364 queue.pending.swap_remove(i);
365 } else {
366 i += 1;
367 }
368 }
369}