1use std::collections::HashMap;
4use std::path::PathBuf;
5use std::sync::Arc;
6
7use crate::asset::{load_gltf_async, AssetServerResource, GltfSceneAssets, Handle};
8use oxide_ecs::entity::Entity;
9use oxide_ecs::world::World;
10use oxide_ecs::{Component, Resource};
11use oxide_renderer::gltf::{GltfNode, GltfScene};
12use oxide_transform::{attach_child, GlobalTransform, TransformComponent};
13
14use oxide_math::transform::Transform;
15use wgpu::{Device, Queue};
16
17#[derive(Component, Clone, Copy, Debug, PartialEq, Eq)]
19pub struct GltfMeshRef {
20 pub mesh_index: usize,
21}
22
23#[derive(Resource, Default)]
25pub struct PendingGltfSceneSpawns {
26 pub handles: Vec<Handle<GltfScene>>,
27}
28
29impl PendingGltfSceneSpawns {
30 pub fn queue(&mut self, handle: Handle<GltfScene>) {
31 if !self.handles.contains(&handle) {
32 self.handles.push(handle);
33 }
34 }
35}
36
37#[derive(Resource, Default)]
39pub struct SpawnedGltfScenes {
40 pub roots_by_scene: HashMap<u64, Vec<Entity>>,
41}
42
43pub fn spawn_gltf_scene_hierarchy(world: &mut World, scene: &GltfScene) -> Vec<Entity> {
47 scene
48 .nodes
49 .iter()
50 .map(|node| spawn_gltf_node(world, node, None))
51 .collect()
52}
53
54fn spawn_gltf_node(world: &mut World, node: &GltfNode, parent: Option<Entity>) -> Entity {
55 let mut entity_builder = world.spawn((
56 TransformComponent::new(Transform {
57 position: node.translation,
58 rotation: node.rotation,
59 scale: node.scale,
60 }),
61 GlobalTransform::default(),
62 ));
63
64 if let Some(mesh_index) = node.mesh_index {
65 entity_builder.insert(GltfMeshRef { mesh_index });
66 }
67
68 let entity = entity_builder.id();
69
70 if let Some(parent_entity) = parent {
71 attach_child(world, parent_entity, entity);
72 }
73
74 for child in &node.children {
75 spawn_gltf_node(world, child, Some(entity));
76 }
77
78 entity
79}
80
81pub fn queue_gltf_scene_spawn(world: &mut World, handle: Handle<GltfScene>) {
83 if !world.contains_resource::<PendingGltfSceneSpawns>() {
84 world.insert_resource(PendingGltfSceneSpawns::default());
85 }
86 world.resource_mut::<PendingGltfSceneSpawns>().queue(handle);
87}
88
89pub fn request_gltf_scene_spawn(
91 world: &mut World,
92 device: Arc<Device>,
93 queue: Arc<Queue>,
94 path: impl Into<PathBuf>,
95) -> Handle<GltfScene> {
96 if !world.contains_resource::<AssetServerResource>() {
97 world.insert_resource(AssetServerResource::default());
98 }
99 if !world.contains_resource::<PendingGltfSceneSpawns>() {
100 world.insert_resource(PendingGltfSceneSpawns::default());
101 }
102 if !world.contains_resource::<GltfSceneAssets>() {
103 world.insert_resource(GltfSceneAssets::default());
104 }
105
106 let handle = {
107 let server = world.resource_mut::<AssetServerResource>();
108 load_gltf_async(&mut server.server, device, queue, path)
109 };
110 world.resource_mut::<PendingGltfSceneSpawns>().queue(handle);
111 handle
112}
113
114pub fn take_spawned_scene_roots(
116 world: &mut World,
117 handle: Handle<GltfScene>,
118) -> Option<Vec<Entity>> {
119 if !world.contains_resource::<SpawnedGltfScenes>() {
120 return None;
121 }
122 world
123 .resource_mut::<SpawnedGltfScenes>()
124 .roots_by_scene
125 .remove(&handle.id())
126}
127
128pub fn gltf_scene_spawn_system(world: &mut World) {
130 if !world.contains_resource::<AssetServerResource>()
131 || !world.contains_resource::<GltfSceneAssets>()
132 || !world.contains_resource::<PendingGltfSceneSpawns>()
133 || !world.contains_resource::<SpawnedGltfScenes>()
134 {
135 return;
136 }
137
138 let completed = {
139 let server = world.resource_mut::<AssetServerResource>();
140 server.server.poll_ready::<GltfScene>()
141 };
142
143 if !completed.is_empty() {
144 let mut ready_handles = Vec::new();
145 {
146 let scene_assets = world.resource_mut::<GltfSceneAssets>();
147 for result in completed {
148 match result {
149 Ok((handle, scene)) => {
150 scene_assets.assets.insert(handle, scene);
151 ready_handles.push(handle);
152 }
153 Err(err) => tracing::warn!("Failed to load glTF scene: {err}"),
154 }
155 }
156 }
157
158 if !ready_handles.is_empty() {
159 let pending = world.resource_mut::<PendingGltfSceneSpawns>();
160 for handle in ready_handles {
161 pending.queue(handle);
162 }
163 }
164 }
165
166 let queued_handles = world.resource::<PendingGltfSceneSpawns>().handles.clone();
167 let mut spawned = Vec::new();
168 for handle in queued_handles {
169 let scene = {
170 let scene_assets = world.resource_mut::<GltfSceneAssets>();
171 scene_assets.assets.remove(&handle)
172 };
173
174 if let Some(scene) = scene {
175 let roots = spawn_gltf_scene_hierarchy(world, &scene);
176 spawned.push((handle, roots));
177 }
178 }
179
180 if spawned.is_empty() {
181 return;
182 }
183
184 {
185 let pending = world.resource_mut::<PendingGltfSceneSpawns>();
186 pending
187 .handles
188 .retain(|handle| !spawned.iter().any(|(done, _)| done == handle));
189 }
190
191 {
192 let results = world.resource_mut::<SpawnedGltfScenes>();
193 for (handle, roots) in spawned {
194 results.roots_by_scene.insert(handle.id(), roots);
195 }
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202 use crate::asset::AssetServerResource;
203 use glam::{Quat, Vec3};
204 use oxide_transform::{Children, Parent};
205
206 #[test]
207 fn gltf_hierarchy_spawns_parent_child_relationships() {
208 let mut world = World::new();
209
210 let scene = GltfScene {
211 meshes: Vec::new(),
212 nodes: vec![GltfNode {
213 name: Some("root".to_string()),
214 mesh_index: None,
215 translation: Vec3::new(1.0, 0.0, 0.0),
216 rotation: Quat::IDENTITY,
217 scale: Vec3::ONE,
218 children: vec![GltfNode {
219 name: Some("child".to_string()),
220 mesh_index: Some(0),
221 translation: Vec3::new(0.0, 2.0, 0.0),
222 rotation: Quat::IDENTITY,
223 scale: Vec3::ONE,
224 children: Vec::new(),
225 }],
226 }],
227 };
228
229 let roots = spawn_gltf_scene_hierarchy(&mut world, &scene);
230 assert_eq!(roots.len(), 1);
231
232 let root = roots[0];
233 let children = world.get::<Children>(root).unwrap();
234 assert_eq!(children.len(), 1);
235
236 let child = children.iter().next().unwrap();
237 let parent = world.get::<Parent>(child).unwrap();
238 assert_eq!(parent.0, root);
239 assert!(world.get::<GltfMeshRef>(child).is_some());
240 }
241
242 #[test]
243 fn queued_gltf_scene_spawns_when_asset_is_available() {
244 let mut world = World::new();
245 world.insert_resource(AssetServerResource::default());
246 world.insert_resource(GltfSceneAssets::default());
247 world.insert_resource(PendingGltfSceneSpawns::default());
248 world.insert_resource(SpawnedGltfScenes::default());
249
250 let handle = {
251 let server = world.resource_mut::<AssetServerResource>();
252 server.server.allocate_handle::<GltfScene>()
253 };
254
255 let scene = GltfScene {
256 meshes: Vec::new(),
257 nodes: vec![GltfNode {
258 name: Some("root".to_string()),
259 mesh_index: None,
260 translation: Vec3::ZERO,
261 rotation: Quat::IDENTITY,
262 scale: Vec3::ONE,
263 children: Vec::new(),
264 }],
265 };
266
267 world.resource_mut::<GltfSceneAssets>().assets.insert(handle, scene);
268 queue_gltf_scene_spawn(&mut world, handle);
269 gltf_scene_spawn_system(&mut world);
270
271 let spawned_roots =
272 take_spawned_scene_roots(&mut world, handle).expect("scene should have spawned");
273 assert_eq!(spawned_roots.len(), 1);
274 assert!(world.contains(spawned_roots[0]));
275 }
276}