1use quaso::{
2 GameLauncher,
3 animation::gltf::{
4 GltfAnimationTarget, GltfAnimationTransition, GltfAnimationTransitionController,
5 GltfAnimationTransitionLayer, GltfNodeId, GltfRenderablesOptions, GltfSceneAnimation,
6 GltfSceneAttribute, GltfSceneInstance, GltfSceneInstantiateOptions, GltfSceneRenderable,
7 GltfSceneRenderables, GltfSceneTemplate,
8 },
9 assets::{make_directory_database, shader::ShaderAsset},
10 config::Config,
11 context::GameContext,
12 coroutine::{async_game_context, async_wait_for_asset},
13 game::{GameInstance, GameState, GameStateChange},
14 third_party::{
15 keket::database::AssetDatabase,
16 nodio::{AnyIndex, graph::Graph, query::Related},
17 spitfire_core::Triangle,
18 spitfire_draw::utils::{Drawable, ShaderRef, Vertex},
19 spitfire_glow::{
20 graphics::{CameraScaling, Shader},
21 renderer::GlowBlending,
22 },
23 spitfire_input::{
24 InputActionRef, InputConsume, InputMapping, VirtualAction, VirtualKeyCode,
25 },
26 vek::{Aabr, Mat4, Vec2, Vec3},
27 },
28};
29use serde_json::Value;
30use std::{collections::HashMap, error::Error, ops::Range, pin::Pin};
31
32const HURTBOX_COLOR: [f32; 4] = [0.5, 0.5, 1.0, 0.5];
33const HITBOX_COLOR: [f32; 4] = [1.0, 0.0, 0.0, 0.5];
34const STEP_DELTA_TIME: f32 = 0.1;
35
36struct HitBox;
37struct HurtBox;
38
39fn main() -> Result<(), Box<dyn Error>> {
40 GameLauncher::new(GameInstance::new(Preloader).setup_assets(|assets| {
41 *assets = make_directory_database("./resources/").unwrap();
42 }))
43 .title("GLTF - Step animation")
44 .config(Config::load_from_file("./resources/GameConfig.toml")?)
45 .run();
46 Ok(())
47}
48
49#[derive(Default)]
50struct Preloader;
51
52impl GameState for Preloader {
53 fn timeline(
54 &mut self,
55 context: GameContext,
56 ) -> Pin<Box<dyn Future<Output = ()> + Send + Sync>> {
57 context.graphics.state.color = [0.2, 0.2, 0.2, 1.0];
58 context.graphics.state.main_camera.screen_alignment = [0.5, 0.8].into();
59 context.graphics.state.main_camera.scaling = CameraScaling::FitVertical(1.5);
60 context.graphics.state.main_camera.transform.scale = Vec3::new(1.0, -1.0, 1.0);
61
62 context
63 .assets
64 .spawn(
65 "shader://color",
66 (ShaderAsset::new(
67 Shader::COLORED_VERTEX_2D,
68 Shader::PASS_FRAGMENT,
69 ),),
70 )
71 .unwrap();
72 context
73 .assets
74 .spawn(
75 "shader://image",
76 (ShaderAsset::new(
77 Shader::TEXTURED_VERTEX_2D,
78 Shader::TEXTURED_FRAGMENT,
79 ),),
80 )
81 .unwrap();
82 context
83 .assets
84 .spawn(
85 "shader://text",
86 (ShaderAsset::new(Shader::TEXT_VERTEX, Shader::TEXT_FRAGMENT),),
87 )
88 .unwrap();
89
90 let handle = context.assets.ensure("gltf://stickman.glb?binary").unwrap();
91
92 Box::pin(async move {
93 println!("Waiting for GLTF asset to load...");
94 async_wait_for_asset(handle).await;
95 println!("GLTF asset loaded.");
96
97 {
98 let context = async_game_context().await.unwrap();
99 for handle in handle.dependencies(context.assets) {
100 println!(
101 "Dependency: {}",
102 handle.path(context.assets).unwrap().content()
103 );
104 }
105 }
106
107 {
108 let context = async_game_context().await.unwrap();
109 let scene = context
110 .assets
111 .find("gltf-scene://stickman.glb/Scene")
112 .unwrap();
113
114 let controller = GltfAnimationTransitionController::default();
115 let instance = scene
116 .access::<&GltfSceneTemplate>(context.assets)
117 .instantiate_with_options(
118 context.assets,
119 &GltfSceneInstantiateOptions::default().extract_extras(extract_extras),
120 )
121 .with_animation(
122 "idle",
123 GltfSceneAnimation::new(
124 context
125 .assets
126 .find("gltf-anim://stickman.glb/TPose")
127 .unwrap(),
128 context.assets,
129 )
130 .unwrap()
131 .weight(0.0)
132 .playing(true)
133 .looped(true),
134 )
135 .with_animation(
136 "walk",
137 GltfSceneAnimation::new(
138 context
139 .assets
140 .find("gltf-anim://stickman.glb/Walk")
141 .unwrap(),
142 context.assets,
143 )
144 .unwrap()
145 .weight(0.0)
146 .playing(true)
147 .looped(true),
148 )
149 .with_animation(
150 "run",
151 GltfSceneAnimation::new(
152 context.assets.find("gltf-anim://stickman.glb/Run").unwrap(),
153 context.assets,
154 )
155 .unwrap()
156 .weight(0.0)
157 .playing(true)
158 .looped(true),
159 )
160 .with_animation_node(
161 GltfAnimationTransition::new(controller.clone())
162 .default_layer("idle")
163 .layer(GltfAnimationTransitionLayer::new(
164 "idle",
165 GltfAnimationTarget::new("idle"),
166 ))
167 .layer(GltfAnimationTransitionLayer::new(
168 "walk",
169 GltfAnimationTarget::new("walk"),
170 ))
171 .layer(GltfAnimationTransitionLayer::new(
172 "run",
173 GltfAnimationTarget::new("run"),
174 )),
175 );
176
177 instance.visit_tree(&mut |level, index, id, name, transform, mesh, skin, bone| {
178 println!(
179 "{}Node {} | id: {} | name: {} | transform: {} | mesh: {} | skin: {} | bone: {}",
180 " ".repeat(level),
181 index,
182 id,
183 name.map(|n| n.as_str()).unwrap_or("<unnamed>"),
184 transform.is_some(),
185 mesh.is_some(),
186 skin.is_some(),
187 bone.is_some(),
188 );
189 true
190 });
191 *context.state_change = GameStateChange::Swap(Box::new(State {
192 controller,
193 instance,
194 idle: InputActionRef::default(),
195 walk: InputActionRef::default(),
196 run: InputActionRef::default(),
197 toggle: InputActionRef::default(),
198 prev: InputActionRef::default(),
199 next: InputActionRef::default(),
200 playing: true,
201 }));
202 }
203 })
204 }
205}
206
207struct State {
208 controller: GltfAnimationTransitionController,
209 instance: GltfSceneInstance,
210 idle: InputActionRef,
211 walk: InputActionRef,
212 run: InputActionRef,
213 toggle: InputActionRef,
214 prev: InputActionRef,
215 next: InputActionRef,
216 playing: bool,
217}
218
219impl GameState for State {
220 fn enter(&mut self, context: GameContext) {
221 context.input.push_mapping(
222 InputMapping::default()
223 .consume(InputConsume::Hit)
224 .action(
225 VirtualAction::KeyButton(VirtualKeyCode::Key1),
226 self.idle.clone(),
227 )
228 .action(
229 VirtualAction::KeyButton(VirtualKeyCode::Key2),
230 self.walk.clone(),
231 )
232 .action(
233 VirtualAction::KeyButton(VirtualKeyCode::Key3),
234 self.run.clone(),
235 )
236 .action(
237 VirtualAction::KeyButton(VirtualKeyCode::W),
238 self.toggle.clone(),
239 )
240 .action(
241 VirtualAction::KeyButton(VirtualKeyCode::Q),
242 self.prev.clone(),
243 )
244 .action(
245 VirtualAction::KeyButton(VirtualKeyCode::E),
246 self.next.clone(),
247 ),
248 );
249 }
250
251 fn exit(&mut self, context: GameContext) {
252 context.input.pop_mapping();
253 }
254
255 fn fixed_update(&mut self, context: GameContext, delta_time: f32) {
256 if self.idle.get().is_pressed() {
257 self.controller.change_to(["idle"]);
258 } else if self.walk.get().is_pressed() {
259 self.controller.change_to(["walk"]);
260 } else if self.run.get().is_pressed() {
261 self.controller.change_to(["run"]);
262 } else if self.toggle.get().is_pressed() {
263 self.playing = !self.playing;
264 for (_, animation) in self.instance.animations() {
265 if let Some(mut animation) = animation.write() {
266 animation.playing = self.playing;
267 }
268 }
269 } else if self.prev.get().is_pressed() {
270 for (_, animation) in self.instance.animations() {
271 if let Some(mut animation) = animation.write() {
272 animation.time -= STEP_DELTA_TIME;
273 animation.sanitize_time();
274 }
275 }
276 } else if self.next.get().is_pressed() {
277 for (_, animation) in self.instance.animations() {
278 if let Some(mut animation) = animation.write() {
279 animation.time += STEP_DELTA_TIME;
280 animation.sanitize_time();
281 }
282 }
283 }
284
285 if self.playing {
286 self.instance
287 .update_and_apply_animations(delta_time, context.assets);
288 } else {
289 self.instance
290 .update_and_apply_animations(0.0, context.assets);
291 }
292 }
293
294 fn draw(&mut self, context: GameContext) {
295 let renderables = self
296 .instance
297 .build_renderables(
298 context.assets,
299 &GltfRenderablesOptions::default()
300 .sort_triangles_by_max_positive_z()
301 .sort_renderables_by_max_positive_z()
302 .renderable_modifier(renderable_modifier)
303 .custom_renderables(custom_renderables)
304 .axes([0, 2]),
305 )
306 .unwrap();
307 renderables.draw(context.draw, context.graphics);
308 }
309}
310
311fn extract_extras(value: &Value, graph: &mut Graph, index: AnyIndex) {
312 if let Some(boxtype) = value.get("boxtype") {
313 match boxtype.as_str() {
314 Some("hurt") => {
315 let attr = graph.insert(HurtBox);
316 graph.relate::<GltfSceneAttribute>(index, attr);
317 }
318 Some("hit") => {
319 let attr = graph.insert(HitBox);
320 graph.relate::<GltfSceneAttribute>(index, attr);
321 }
322 _ => {}
323 }
324 }
325}
326
327fn renderable_modifier(graph: &Graph, index: AnyIndex, renderable: &mut GltfSceneRenderable) {
328 if graph
329 .query::<Related<GltfSceneAttribute, &HitBox>>(index)
330 .next()
331 .is_some()
332 {
333 renderable.shader = Some(ShaderRef::name("color"));
334 renderable.main_texture = None;
335 renderable.blending = GlowBlending::Alpha;
336 for vertex in &mut renderable.vertices {
337 vertex.color = HITBOX_COLOR;
338 vertex.uv = [0.0, 0.0, 0.0];
339 }
340 }
341
342 if graph
343 .query::<Related<GltfSceneAttribute, &HurtBox>>(index)
344 .next()
345 .is_some()
346 {
347 renderable.shader = Some(ShaderRef::name("color"));
348 renderable.main_texture = None;
349 renderable.blending = GlowBlending::Alpha;
350 for vertex in &mut renderable.vertices {
351 vertex.color = HURTBOX_COLOR;
352 vertex.uv = [0.0, 0.0, 0.0];
353 }
354 }
355}
356
357fn custom_renderables(
358 graph: &Graph,
359 index: AnyIndex,
360 _: &AssetDatabase,
361 _: &GltfRenderablesOptions,
362 _: &HashMap<GltfNodeId, Mat4<f32>>,
363 renderables: &mut GltfSceneRenderables,
364 range: Range<usize>,
365) -> Result<(), Box<dyn Error>> {
366 if graph
367 .query::<Related<GltfSceneAttribute, &HitBox>>(index)
368 .next()
369 .is_some()
370 {
371 custom_renderable(renderables, range.clone(), HITBOX_COLOR);
372 }
373
374 if graph
375 .query::<Related<GltfSceneAttribute, &HurtBox>>(index)
376 .next()
377 .is_some()
378 {
379 custom_renderable(renderables, range, HURTBOX_COLOR);
380 }
381
382 Ok(())
383}
384
385fn custom_renderable(renderables: &mut GltfSceneRenderables, range: Range<usize>, color: [f32; 4]) {
386 let aabr = renderables.renderables[range.clone()]
387 .iter()
388 .flat_map(|renderable| {
389 renderable
390 .vertices
391 .iter()
392 .map(|vertex| Vec2::from(vertex.position))
393 })
394 .fold(Option::<Aabr<f32>>::None, |aabr, position| {
395 if let Some(aabr) = aabr {
396 Some(aabr.expanded_to_contain_point(position))
397 } else {
398 Some(Aabr::new_empty(position))
399 }
400 });
401
402 if let Some(aabr) = aabr {
403 renderables.renderables.push(GltfSceneRenderable {
404 shader: Some(ShaderRef::name("color")),
405 main_texture: None,
406 blending: GlowBlending::Alpha,
407 wireframe: true,
408 triangles: vec![Triangle { a: 0, b: 1, c: 2 }, Triangle { a: 2, b: 3, c: 0 }],
409 vertices: vec![
410 Vertex {
411 position: [aabr.min.x, aabr.min.y],
412 uv: [0.0, 0.0, 0.0],
413 color,
414 },
415 Vertex {
416 position: [aabr.max.x, aabr.min.y],
417 uv: [0.0, 0.0, 0.0],
418 color,
419 },
420 Vertex {
421 position: [aabr.max.x, aabr.max.y],
422 uv: [0.0, 0.0, 0.0],
423 color,
424 },
425 Vertex {
426 position: [aabr.min.x, aabr.max.y],
427 uv: [0.0, 0.0, 0.0],
428 color,
429 },
430 ],
431 });
432 }
433}