1use std::{collections::HashMap, sync::Arc};
9
10use craballoc::prelude::*;
11use crabslab::{Array, Id};
12use glam::{Mat4, Quat, Vec2, Vec3, Vec4};
13use rustc_hash::{FxHashMap, FxHashSet};
14use snafu::{OptionExt, ResultExt, Snafu};
15
16use crate::{
17 atlas::{
18 shader::AtlasTextureDescriptor, AtlasError, AtlasImage, AtlasTexture, TextureAddressMode,
19 TextureModes,
20 },
21 bvol::Aabb,
22 camera::Camera,
23 geometry::{Indices, MorphTarget, MorphTargetWeights, MorphTargets, Skin, Vertex, Vertices},
24 light::{shader::LightStyle, AnalyticalLight, Candela, Lux},
25 material::Material,
26 primitive::Primitive,
27 stage::{Stage, StageError},
28 transform::{shader::TransformDescriptor, NestedTransform},
29 types::{GpuCpuArray, GpuOnlyArray},
30};
31
32mod anime;
33pub use anime::*;
34
35#[derive(Debug, Snafu)]
36pub enum StageGltfError {
37 #[snafu(
38 display(
39 "GLTF error with '{}': {source}",
40 path
41 .as_ref()
42 .map(|p| p.display().to_string())
43 .unwrap_or("<bytes>".to_string())),
44 visibility(pub(crate))
45 )]
46 Gltf {
47 source: gltf::Error,
48 path: Option<std::path::PathBuf>,
49 },
50
51 #[snafu(display("{source}"))]
52 Atlas { source: crate::atlas::AtlasError },
53
54 #[snafu(display("Wrong image at index {index} atlas offset {offset}"))]
55 WrongImage { offset: usize, index: usize },
56
57 #[snafu(display("Missing image {index} '{name}'"))]
58 MissingImage { index: usize, name: String },
59
60 #[snafu(display("Missing texture at gltf index {index} slab index {tex_id:?}"))]
61 MissingTexture {
62 index: usize,
63 tex_id: Id<AtlasTextureDescriptor>,
64 },
65
66 #[snafu(display("Missing material with index {index}"))]
67 MissingMaterial { index: usize },
68
69 #[snafu(display("Missing primitive with index {index}"))]
70 MissingPrimitive { index: usize },
71
72 #[snafu(display("Missing mesh with index {index}"))]
73 MissingMesh { index: usize },
74
75 #[snafu(display("Missing node with index {index}"))]
76 MissingNode { index: usize },
77
78 #[snafu(display("Missing light with index {index}"))]
79 MissingLight { index: usize },
80
81 #[snafu(display("Unsupported primitive mode: {:?}", mode))]
82 PrimitiveMode { mode: gltf::mesh::Mode },
83
84 #[snafu(display("No {} attribute for mesh", attribute.to_string()))]
85 MissingAttribute { attribute: gltf::Semantic },
86
87 #[snafu(display("No weights array"))]
88 MissingWeights,
89
90 #[snafu(display("Missing sampler"))]
91 MissingSampler,
92
93 #[snafu(display("Missing gltf camera at index {index}"))]
94 MissingCamera { index: usize },
95
96 #[snafu(display("Node has no skin"))]
97 NoSkin,
98
99 #[snafu(display("Missing gltf skin at index {index}"))]
100 MissingSkin { index: usize },
101
102 #[snafu(display("{source}"))]
103 Animation { source: anime::AnimationError },
104}
105
106impl From<AtlasError> for StageGltfError {
107 fn from(source: AtlasError) -> Self {
108 Self::Atlas { source }
109 }
110}
111
112impl From<gltf::scene::Transform> for TransformDescriptor {
113 fn from(transform: gltf::scene::Transform) -> Self {
114 let (translation, rotation, scale) = transform.decomposed();
115 TransformDescriptor {
116 translation: Vec3::from_array(translation),
117 rotation: Quat::from_array(rotation),
118 scale: Vec3::from_array(scale),
119 }
120 }
121}
122
123pub fn from_gltf_light_kind(kind: gltf::khr_lights_punctual::Kind) -> LightStyle {
124 match kind {
125 gltf::khr_lights_punctual::Kind::Directional => LightStyle::Directional,
126 gltf::khr_lights_punctual::Kind::Point => LightStyle::Point,
127 gltf::khr_lights_punctual::Kind::Spot { .. } => LightStyle::Spot,
128 }
129}
130
131pub fn gltf_light_intensity_units(kind: gltf::khr_lights_punctual::Kind) -> &'static str {
132 match kind {
133 gltf::khr_lights_punctual::Kind::Directional => "lux (lm/m^2)",
134 _ => "candelas (lm/sr)",
136 }
137}
138
139impl TextureAddressMode {
140 fn from_gltf(mode: gltf::texture::WrappingMode) -> TextureAddressMode {
141 match mode {
142 gltf::texture::WrappingMode::ClampToEdge => TextureAddressMode::ClampToEdge,
143 gltf::texture::WrappingMode::MirroredRepeat => TextureAddressMode::MirroredRepeat,
144 gltf::texture::WrappingMode::Repeat => TextureAddressMode::Repeat,
145 }
146 }
147}
148
149pub fn get_vertex_count(primitive: &gltf::Primitive<'_>) -> u32 {
150 if let Some(indices) = primitive.indices() {
151 let count = indices.count() as u32;
152 log::trace!(" has {count} indices");
153 count
154 } else if let Some(positions) = primitive.get(&gltf::Semantic::Positions) {
155 let count = positions.count() as u32;
156 log::trace!(" has {count} positions");
157 count
158 } else {
159 log::trace!(" has no indices nor positions");
160 0
161 }
162}
163
164impl Material {
165 pub fn preprocess_images(
166 material: gltf::Material,
167 images: &mut [AtlasImage],
168 ) -> Result<(), StageGltfError> {
169 let pbr = material.pbr_metallic_roughness();
170 if material.unlit() {
171 if let Some(info) = pbr.base_color_texture() {
172 let texture = info.texture();
173 let image_index = texture.source().index();
175 let name = texture.name().unwrap_or("unknown");
176 let image = images.get_mut(image_index).context(MissingImageSnafu {
178 index: image_index,
179 name,
180 })?;
181 image.apply_linear_transfer = true;
182 }
183 } else {
184 if let Some(info) = pbr.base_color_texture() {
185 let texture = info.texture();
186 let name = texture.name().unwrap_or("unknown");
187 let image_index = texture.source().index();
188 let image = images.get_mut(image_index).context(MissingImageSnafu {
190 index: image_index,
191 name,
192 })?;
193 image.apply_linear_transfer = true;
194 }
195
196 if let Some(emissive_tex) = material.emissive_texture() {
197 let texture = emissive_tex.texture();
198 let name = texture.name().unwrap_or("unknown");
199 let image_index = texture.source().index();
200 let image = images.get_mut(image_index).context(MissingImageSnafu {
202 index: image_index,
203 name,
204 })?;
205 image.apply_linear_transfer = true;
206 }
207 }
208 Ok(())
209 }
210
211 pub fn from_gltf(
212 stage: &Stage,
213 material: gltf::Material,
214 entries: &[AtlasTexture],
215 ) -> Result<Material, StageGltfError> {
216 let name = material.name().map(String::from);
217 log::trace!("loading material {:?} {name:?}", material.index());
218 let pbr = material.pbr_metallic_roughness();
219 let builder = stage.new_material();
220 if material.unlit() {
221 log::trace!(" is unlit");
222 builder.set_has_lighting(false);
223
224 if let Some(info) = pbr.base_color_texture() {
225 let texture = info.texture();
226 let index = texture.index();
227 if let Some(tex) = entries.get(index) {
228 builder.set_albedo_texture(tex);
229 builder.set_albedo_tex_coord(info.tex_coord());
230 }
231 }
232 builder.set_albedo_factor(pbr.base_color_factor().into());
233 } else {
234 log::trace!(" is pbr");
235 builder.set_has_lighting(true);
236
237 if let Some(info) = pbr.base_color_texture() {
238 let texture = info.texture();
239 let index = texture.index();
240 if let Some(tex) = entries.get(index) {
241 builder.set_albedo_texture(tex);
242 builder.set_albedo_tex_coord(info.tex_coord());
243 }
244 }
245 builder.set_albedo_factor(pbr.base_color_factor().into());
246
247 if let Some(info) = pbr.metallic_roughness_texture() {
248 let index = info.texture().index();
249 if let Some(tex) = entries.get(index) {
250 builder.set_metallic_roughness_texture(tex);
251 builder.set_metallic_roughness_tex_coord(info.tex_coord());
252 }
253 } else {
254 builder.set_metallic_factor(pbr.metallic_factor());
255 builder.set_roughness_factor(pbr.roughness_factor());
256 }
257
258 if let Some(norm_tex) = material.normal_texture() {
259 if let Some(tex) = entries.get(norm_tex.texture().index()) {
260 builder.set_normal_texture(tex);
261 builder.set_normal_tex_coord(norm_tex.tex_coord());
262 }
263 }
264
265 if let Some(occlusion_tex) = material.occlusion_texture() {
266 if let Some(tex) = entries.get(occlusion_tex.texture().index()) {
267 builder.set_ambient_occlusion_texture(tex);
268 builder.set_ambient_occlusion_tex_coord(occlusion_tex.tex_coord());
269 builder.set_ambient_occlusion_strength(occlusion_tex.strength());
270 }
271 }
272
273 if let Some(emissive_tex) = material.emissive_texture() {
274 let texture = emissive_tex.texture();
275 let index = texture.index();
276 if let Some(tex) = entries.get(index) {
277 builder.set_emissive_texture(tex);
278 builder.set_emissive_tex_coord(emissive_tex.tex_coord());
279 }
280 }
281 builder.set_emissive_strength_multiplier(material.emissive_strength().unwrap_or(1.0));
282 };
283 Ok(builder)
284 }
285}
286
287pub struct GltfPrimitive<Ct: IsContainer = GpuCpuArray> {
288 pub indices: Indices<Ct>,
289 pub vertices: Vertices<Ct>,
290 pub bounding_box: (Vec3, Vec3),
291 pub material_index: Option<usize>,
292 pub morph_targets: MorphTargets,
293}
294
295impl<Ct> core::fmt::Debug for GltfPrimitive<Ct>
296where
297 Ct: IsContainer<Pointer<Vertex> = Array<Vertex>>,
298 Ct: IsContainer<Pointer<u32> = Array<u32>>,
299{
300 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
301 f.debug_struct("GltfPrimitive")
302 .field("indices", &self.indices)
303 .field("vertices", &self.vertices)
304 .field("bounding_box", &self.bounding_box)
305 .field("material_index", &self.material_index)
306 .field("morph_targets", &self.morph_targets)
307 .finish()
308 }
309}
310
311impl GltfPrimitive {
312 pub fn from_gltf(
313 stage: &Stage,
314 primitive: gltf::Primitive,
315 buffer_data: &[gltf::buffer::Data],
316 ) -> Self {
317 let material_index = primitive.material().index();
318
319 let reader = primitive.reader(|buffer| {
320 let data = buffer_data.get(buffer.index())?;
321 Some(data.0.as_slice())
322 });
323
324 let indices = reader
325 .read_indices()
326 .map(|is| {
327 let indices = is.into_u32().collect::<Vec<_>>();
328 assert_eq!(indices.len() % 3, 0, "indices do not form triangles");
329 indices
330 })
331 .unwrap_or_default();
332
333 let positions = reader
334 .read_positions()
335 .into_iter()
336 .flat_map(|ps| ps.map(Vec3::from))
337 .collect::<Vec<_>>();
338
339 let uv0s = reader
340 .read_tex_coords(0)
341 .into_iter()
342 .flat_map(|uvs| uvs.into_f32().map(Vec2::from))
343 .chain(std::iter::repeat(Vec2::ZERO))
344 .take(positions.len())
345 .collect::<Vec<_>>();
346
347 let uv1s = reader
348 .read_tex_coords(0)
349 .into_iter()
350 .flat_map(|uvs| uvs.into_f32().map(Vec2::from))
351 .chain(std::iter::repeat(Vec2::ZERO))
352 .take(positions.len());
353
354 let mut normals = vec![Vec3::Z; positions.len()];
355 if let Some(ns) = reader.read_normals() {
356 let ns = ns.map(Vec3::from).collect::<Vec<_>>();
357 debug_assert_eq!(positions.len(), ns.len());
358 normals = ns;
359 } else {
360 log::trace!(" generating normals");
361
362 let indices = if indices.is_empty() {
363 (0..positions.len() as u32).collect::<Vec<_>>()
364 } else {
365 indices.to_vec()
366 };
367
368 indices.chunks(3).for_each(|chunk| match chunk {
369 [i, j, k] => {
370 let a = positions[*i as usize];
371 let b = positions[*j as usize];
372 let c = positions[*k as usize];
373 let n = Vertex::generate_normal(a, b, c);
374 normals[*i as usize] = n;
375 normals[*j as usize] = n;
376 normals[*k as usize] = n;
377 }
378 _ => panic!("not triangles!"),
379 });
380 }
381
382 let mut tangents = vec![Vec4::ZERO; positions.len()];
383 if let Some(ts) = reader.read_tangents() {
384 let ts = ts.map(Vec4::from).collect::<Vec<_>>();
385 debug_assert_eq!(positions.len(), ts.len());
386 tangents = ts;
387 } else {
388 log::trace!(" generating tangents");
389 let indices = if indices.is_empty() {
390 (0..positions.len() as u32).collect::<Vec<_>>()
391 } else {
392 indices.to_vec()
393 };
394
395 indices.chunks(3).for_each(|chunk| match chunk {
396 [i, j, k] => {
397 let a = positions[*i as usize];
398 let b = positions[*j as usize];
399 let c = positions[*k as usize];
400 let a_uv = uv0s[*i as usize];
401 let b_uv = uv0s[*j as usize];
402 let c_uv = uv0s[*k as usize];
403
404 let t = Vertex::generate_tangent(a, a_uv, b, b_uv, c, c_uv);
405 tangents[*i as usize] = t;
406 tangents[*j as usize] = t;
407 tangents[*k as usize] = t;
408 }
409 _ => panic!("not triangles!"),
410 });
411 }
412 let colors = reader
413 .read_colors(0)
414 .into_iter()
415 .flat_map(|cs| cs.into_rgba_f32().map(Vec4::from))
416 .chain(std::iter::repeat(Vec4::ONE))
417 .take(positions.len());
418
419 let joints = reader
420 .read_joints(0)
421 .into_iter()
422 .flat_map(|js| {
423 js.into_u16()
424 .map(|[a, b, c, d]| [a as u32, b as u32, c as u32, d as u32])
425 })
426 .chain(std::iter::repeat([u32::MAX; 4]))
427 .take(positions.len());
428 let joints = joints.collect::<Vec<_>>();
429 let mut all_joints = FxHashSet::default();
430 for js in joints.iter() {
431 all_joints.extend(*js);
432 }
433 log::debug!(" joints: {all_joints:?}");
434
435 const UNWEIGHTED_WEIGHTS: [f32; 4] = [1.0, 0.0, 0.0, 0.0];
436 let mut logged_weights_not_f32 = false;
437 let weights = reader
438 .read_weights(0)
439 .into_iter()
440 .flat_map(|ws| {
441 if !logged_weights_not_f32 {
442 match ws {
443 gltf::mesh::util::ReadWeights::U8(_) => log::warn!("weights are u8"),
444 gltf::mesh::util::ReadWeights::U16(_) => log::warn!("weights are u16"),
445 gltf::mesh::util::ReadWeights::F32(_) => {}
446 }
447 logged_weights_not_f32 = true;
448 }
449 ws.into_f32().map(|weights| {
450 let sum = weights[0] + weights[1] + weights[2] + weights[3];
452 weights.map(|w| w / sum)
453 })
454 })
455 .chain(std::iter::repeat(UNWEIGHTED_WEIGHTS))
456 .take(positions.len());
457
458 let morph_targets: Vec<Vec<MorphTarget>> = reader
465 .read_morph_targets()
466 .map(|(may_ps, may_ns, may_ts)| {
467 let ps = may_ps
468 .into_iter()
469 .flat_map(|iter| iter.map(Vec3::from_array))
470 .chain(std::iter::repeat(Vec3::ZERO))
471 .take(positions.len());
472
473 let ns = may_ns
474 .into_iter()
475 .flat_map(|iter| iter.map(Vec3::from_array))
476 .chain(std::iter::repeat(Vec3::ZERO))
477 .take(positions.len());
478
479 let ts = may_ts
480 .into_iter()
481 .flat_map(|iter| iter.map(Vec3::from_array))
482 .chain(std::iter::repeat(Vec3::ZERO))
483 .take(positions.len());
484
485 ps.zip(ns)
486 .zip(ts)
487 .map(|((position, normal), tangent)| MorphTarget {
488 position,
489 normal,
490 tangent,
491 })
492 .collect()
493 })
494 .collect();
495 log::debug!(
496 " {} morph_targets: {:?}",
497 morph_targets.len(),
498 morph_targets.iter().map(|mt| mt.len()).collect::<Vec<_>>()
499 );
500 let morph_targets = stage.new_morph_targets(morph_targets);
501 let vs = joints.into_iter().zip(weights);
502 let vs = colors.zip(vs);
503 let vs = tangents.into_iter().zip(vs);
504 let vs = normals.into_iter().zip(vs);
505 let vs = uv1s.zip(vs);
506 let vs = uv0s.into_iter().zip(vs);
507 let vs = positions.into_iter().zip(vs);
508
509 let mut min = Vec3::splat(f32::INFINITY);
510 let mut max = Vec3::splat(f32::NEG_INFINITY);
511 let vertices = vs
512 .map(
513 |(position, (uv0, (uv1, (normal, (tangent, (color, (joints, weights)))))))| {
514 min = min.min(position);
515 max = max.max(position);
516 Vertex {
517 position,
518 color,
519 uv0,
520 uv1,
521 normal,
522 tangent,
523 joints,
524 weights,
525 }
526 },
527 )
528 .collect::<Vec<_>>();
529 let vertices = stage.new_vertices(vertices);
530 log::debug!(
531 "{} vertices, {:?}",
532 vertices.array().len(),
533 vertices.array()
534 );
535 let indices = stage.new_indices(indices);
536 log::debug!("{} indices, {:?}", indices.array().len(), indices.array());
537 let (bbmin, bbmax) = {
538 let gltf::mesh::Bounds { min, max } = primitive.bounding_box();
539 (Vec3::from_array(min), Vec3::from_array(max))
540 };
541 if bbmin != min {
542 log::warn!("gltf supplied bounding box min ({bbmin:?}) doesn't match seen ({min:?})");
543 }
544 if bbmax != max {
545 log::warn!("gltf supplied bounding box max ({bbmax:?}) doesn't match seen ({max:?})");
546 }
547 let bounding_box = (min, max);
548
549 log::info!("primitive '{}' bounds: {bounding_box:?}", primitive.index());
550
551 Self {
552 vertices,
553 indices,
554 material_index,
555 morph_targets,
556 bounding_box,
557 }
558 }
559
560 pub fn into_gpu_only(self) -> GltfPrimitive<GpuOnlyArray> {
561 let Self {
562 indices,
563 vertices,
564 bounding_box,
565 material_index,
566 morph_targets,
567 } = self;
568 GltfPrimitive {
569 indices: indices.into_gpu_only(),
570 vertices: vertices.into_gpu_only(),
571 bounding_box,
572 material_index,
573 morph_targets,
574 }
575 }
576}
577
578pub struct GltfMesh<Ct: IsContainer = GpuCpuArray> {
579 pub primitives: Vec<GltfPrimitive<Ct>>,
581 pub weights: MorphTargetWeights,
583}
584
585impl<Ct> core::fmt::Debug for GltfMesh<Ct>
586where
587 Ct: IsContainer<Pointer<Vertex> = Array<Vertex>>,
588 Ct: IsContainer<Pointer<u32> = Array<u32>>,
589{
590 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
591 f.debug_struct("GltfMesh")
592 .field("primitives", &self.primitives)
593 .field("weights", &self.weights)
594 .finish()
595 }
596}
597
598impl GltfMesh {
599 fn from_gltf(stage: &Stage, buffer_data: &[gltf::buffer::Data], mesh: gltf::Mesh) -> Self {
600 log::debug!("Loading primitives for mesh {}", mesh.index());
601 let primitives = mesh
602 .primitives()
603 .map(|prim| GltfPrimitive::from_gltf(stage, prim, buffer_data))
604 .collect::<Vec<_>>();
605 log::debug!(" loaded {} primitives\n", primitives.len());
606 let weights = mesh.weights().unwrap_or(&[]).iter().copied();
607 GltfMesh {
608 primitives,
609 weights: stage.new_morph_target_weights(weights),
610 }
611 }
612
613 pub fn into_gpu_only(self) -> GltfMesh<GpuOnlyArray> {
614 let Self {
615 primitives,
616 weights,
617 } = self;
618 let primitives = primitives
619 .into_iter()
620 .map(GltfPrimitive::into_gpu_only)
621 .collect::<Vec<_>>();
622 GltfMesh {
623 primitives,
624 weights,
625 }
626 }
627}
628
629#[derive(Debug)]
630pub struct GltfCamera {
631 pub index: usize,
632 pub name: Option<String>,
633 pub node_transform: NestedTransform,
634 pub camera: Camera,
635}
636
637impl AsRef<Camera> for GltfCamera {
638 fn as_ref(&self) -> &Camera {
639 &self.camera
640 }
641}
642
643impl GltfCamera {
644 fn new(stage: &Stage, gltf_camera: gltf::Camera<'_>, transform: &NestedTransform) -> Self {
645 log::debug!("camera: {}", gltf_camera.name().unwrap_or("unknown"));
646 log::debug!(" transform: {:#?}", transform.global_descriptor());
647 let projection = match gltf_camera.projection() {
648 gltf::camera::Projection::Orthographic(o) => glam::Mat4::orthographic_rh(
649 -o.xmag(),
650 o.xmag(),
651 -o.ymag(),
652 o.ymag(),
653 o.znear(),
654 o.zfar(),
655 ),
656 gltf::camera::Projection::Perspective(p) => {
657 let fovy = p.yfov();
658 let aspect = p.aspect_ratio().unwrap_or(1.0);
659 if let Some(zfar) = p.zfar() {
660 glam::Mat4::perspective_rh(fovy, aspect, p.znear(), zfar)
661 } else {
662 glam::Mat4::perspective_infinite_rh(
663 p.yfov(),
664 p.aspect_ratio().unwrap_or(1.0),
665 p.znear(),
666 )
667 }
668 }
669 };
670 let view = Mat4::from(transform.global_descriptor()).inverse();
671 let camera = stage
672 .new_camera()
673 .with_projection_and_view(projection, view);
674 let extensions = gltf_camera.extensions();
676 log::debug!(" extensions: {extensions:#?}");
677 let extras = gltf_camera.extras();
678 log::debug!(" extras: {extras:#?}");
679 GltfCamera {
680 index: gltf_camera.index(),
681 name: gltf_camera.name().map(String::from),
682 node_transform: transform.clone(),
683 camera,
684 }
685 }
686}
687
688#[derive(Clone, Debug)]
690pub struct GltfNode {
691 pub index: usize,
693 pub name: Option<String>,
695 pub light: Option<usize>,
697 pub mesh: Option<usize>,
699 pub camera: Option<usize>,
701 pub skin: Option<usize>,
703 pub children: Vec<usize>,
707 pub weights: MorphTargetWeights,
709 pub transform: NestedTransform,
711}
712
713impl GltfNode {
714 pub fn global_transform(&self) -> TransformDescriptor {
715 self.transform.global_descriptor()
716 }
717}
718
719#[derive(Clone, Debug)]
720pub struct GltfSkin {
721 pub index: usize,
722 pub joint_nodes: Vec<usize>,
725 pub skeleton: Option<usize>,
728 pub skin: Skin,
730}
731
732impl GltfSkin {
733 pub fn from_gltf(
734 stage: &Stage,
735 buffer_data: &[gltf::buffer::Data],
736 nodes: &[GltfNode],
737 gltf_skin: gltf::Skin,
738 ) -> Result<Self, StageGltfError> {
739 log::debug!("reading skin {} {:?}", gltf_skin.index(), gltf_skin.name());
740 let joint_nodes = gltf_skin.joints().map(|n| n.index()).collect::<Vec<_>>();
741 log::debug!(" has {} joints", joint_nodes.len());
742 let mut joint_transforms = vec![];
743 for node_index in joint_nodes.iter() {
744 let gltf_node: &GltfNode = nodes
745 .get(*node_index)
746 .context(MissingNodeSnafu { index: *node_index })?;
747 let transform_id = gltf_node.transform.global_descriptor();
748 log::debug!(" joint node {node_index} is {transform_id:?}");
749 joint_transforms.push(gltf_node.transform.clone());
750 }
751 let reader = gltf_skin.reader(|b| buffer_data.get(b.index()).map(|d| d.0.as_slice()));
752 let mut inverse_bind_matrices = vec![];
753 if let Some(mats) = reader.read_inverse_bind_matrices() {
754 let invs = mats
755 .into_iter()
756 .map(|m| Mat4::from_cols_array_2d(&m))
757 .collect::<Vec<_>>();
758 log::debug!(" has {} inverse bind matrices", invs.len());
759 inverse_bind_matrices = invs;
760 } else {
761 log::debug!(" no inverse bind matrices");
762 }
763 let skeleton = if let Some(n) = gltf_skin.skeleton() {
764 let index = n.index();
765 log::debug!(" skeleton is node {index}, {:?}", n.name());
766 Some(index)
767 } else {
768 log::debug!(" skeleton is assumed to be the scene root");
769 None
770 };
771 Ok(GltfSkin {
772 index: gltf_skin.index(),
773 skin: stage.new_skin(joint_transforms, inverse_bind_matrices),
774 joint_nodes,
775 skeleton,
776 })
777 }
778}
779
780pub struct GltfDocument<Ct: IsContainer = GpuCpuArray> {
794 pub animations: Vec<Animation>,
795 pub cameras: Vec<GltfCamera>,
796 pub default_scene: Option<usize>,
797 pub extensions: Option<serde_json::Value>,
798 pub textures: Vec<AtlasTexture>,
799 pub lights: Vec<AnalyticalLight>,
800 pub meshes: Vec<GltfMesh<Ct>>,
801 pub nodes: Vec<GltfNode>,
802 pub default_material: Material,
803 pub materials: Vec<Material>,
804 pub primitives: FxHashMap<usize, Vec<Primitive>>,
806 pub scenes: Vec<Vec<usize>>,
808 pub skins: Vec<GltfSkin>,
809}
810
811impl GltfDocument {
812 pub fn from_gltf(
813 stage: &Stage,
814 document: &gltf::Document,
815 buffer_data: Vec<gltf::buffer::Data>,
816 images: Vec<gltf::image::Data>,
817 ) -> Result<GltfDocument, StageError> {
818 let textures = {
819 let mut images = images.into_iter().map(AtlasImage::from).collect::<Vec<_>>();
820 for gltf_material in document.materials() {
821 Material::preprocess_images(gltf_material, &mut images)?;
822 }
823 let images = images.into_iter().map(Arc::new).collect::<Vec<_>>();
826
827 log::debug!("Loading {} images into the atlas", images.len());
828
829 log::debug!("Writing textures");
830 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
831 struct Texture {
832 source: usize,
833 modes: TextureModes,
834 }
835 let mut textures = vec![];
836 let mut deduped_textures = FxHashMap::<Texture, Vec<usize>>::default();
837 for (i, texture) in document.textures().enumerate() {
838 let index = texture.index();
839 debug_assert_eq!(i, index);
840 let name = texture.name().unwrap_or("unknown");
841 log::trace!(" texture {i} '{name}'",);
842 let source = texture.source().index();
843 let modes = TextureModes {
844 s: TextureAddressMode::from_gltf(texture.sampler().wrap_s()),
845 t: TextureAddressMode::from_gltf(texture.sampler().wrap_t()),
846 };
847 let tex = Texture { modes, source };
848 textures.push(tex);
849 let entry = deduped_textures.entry(tex).or_default();
850 entry.push(i);
851 }
852
853 let mut deduped_textures = deduped_textures.into_iter().collect::<Vec<_>>();
855 deduped_textures.sort();
856 let mut prepared_images = vec![];
857 for (tex, refs) in deduped_textures.iter() {
858 let image = images
859 .get(tex.source)
860 .context(MissingImageSnafu {
861 index: refs[0],
862 name: "unknown".to_owned(),
863 })?
864 .clone();
865 prepared_images.push(image);
866 }
867 let duplicated_image_count = prepared_images.len() - images.len();
868 if duplicated_image_count > 0 {
869 log::debug!("had to duplicate {duplicated_image_count} images...");
870 }
871 drop(images);
872
873 let prepared_images: Vec<AtlasImage> = prepared_images
874 .into_iter()
875 .map(|aimg| match Arc::try_unwrap(aimg) {
876 Ok(img) => img,
877 Err(aimg) => aimg.as_ref().clone(),
878 })
879 .collect();
880 let hybrid_textures = stage.add_images(prepared_images)?;
881 let mut texture_lookup = FxHashMap::<usize, AtlasTexture>::default();
882 for (hybrid, (tex, refs)) in hybrid_textures.into_iter().zip(deduped_textures) {
883 hybrid.set_modes(tex.modes);
884 for tex_index in refs.into_iter() {
885 texture_lookup.insert(tex_index, hybrid.clone());
886 }
887 }
888 let mut textures = texture_lookup.into_iter().collect::<Vec<_>>();
889 textures.sort_by_key(|(index, _)| *index);
890 textures
891 .into_iter()
892 .map(|(_, hybrid)| hybrid)
893 .collect::<Vec<_>>()
894 };
895
896 log::debug!("Creating materials");
897 let mut default_material = stage.default_material().clone();
898 let mut materials = vec![];
899 for gltf_material in document.materials() {
900 let material_index = gltf_material.index();
901 let material = Material::from_gltf(stage, gltf_material, &textures)?;
902 if let Some(index) = material_index {
903 log::trace!(" created material {index}");
904 debug_assert_eq!(index, materials.len(), "unexpected material index");
905 materials.push(material);
906 } else {
907 log::trace!(" created default material");
908 default_material = material;
909 }
910 }
911 log::trace!(" created {} materials", materials.len());
912
913 log::debug!("Loading meshes");
914 let mut meshes = vec![];
915 for mesh in document.meshes() {
916 let mesh = GltfMesh::from_gltf(stage, &buffer_data, mesh);
917 meshes.push(mesh);
918 }
919 log::trace!(" loaded {} meshes", meshes.len());
920
921 log::debug!("Loading {} nodes", document.nodes().count());
922 let mut nodes = vec![];
923 let mut node_transforms = HashMap::<usize, NestedTransform>::new();
924
925 fn transform_for_node(
926 nesting_level: usize,
927 stage: &Stage,
928 cache: &mut HashMap<usize, NestedTransform>,
929 node: &gltf::Node,
930 ) -> NestedTransform {
931 let padding = std::iter::repeat_n(" ", nesting_level * 2)
932 .collect::<Vec<_>>()
933 .join("");
934 let nt = if let Some(nt) = cache.get(&node.index()) {
935 nt.clone()
936 } else {
937 let TransformDescriptor {
938 translation,
939 rotation,
940 scale,
941 } = node.transform().into();
942 let transform = stage
943 .new_nested_transform()
944 .with_local_translation(translation)
945 .with_local_rotation(rotation)
946 .with_local_scale(scale);
947 for node in node.children() {
948 let child_transform =
949 transform_for_node(nesting_level + 1, stage, cache, &node);
950 transform.add_child(&child_transform);
951 }
952 cache.insert(node.index(), transform.clone());
953 transform
954 };
955 let t = nt.local_descriptor();
956 log::trace!(
957 "{padding}{} {:?} {:?} {:?} {:?}",
958 node.index(),
959 node.name(),
960 t.translation,
961 t.rotation,
962 t.scale
963 );
964 nt
965 }
966 let mut camera_index_to_node_index = HashMap::<usize, usize>::new();
967 let mut light_index_to_node_index = HashMap::<usize, usize>::new();
968 for (i, node) in document.nodes().enumerate() {
969 let node_index = node.index();
970 if let Some(camera) = node.camera() {
971 camera_index_to_node_index.insert(camera.index(), node_index);
972 }
973 if let Some(light) = node.light() {
974 light_index_to_node_index.insert(light.index(), node_index);
975 }
976
977 debug_assert_eq!(i, node_index);
978 let children = node.children().map(|node| node.index()).collect::<Vec<_>>();
979 let mesh = node.mesh().map(|mesh| mesh.index());
980 let skin = node.skin().map(|skin| skin.index());
981 let camera = node.camera().map(|camera| camera.index());
982 let light = node.light().map(|light| light.index());
983 let weights = node.weights().map(|w| w.to_vec()).unwrap_or_default();
984 let weights = if weights.is_empty() {
992 if let Some(mesh) = node.mesh() {
993 meshes[mesh.index()].weights.clone()
994 } else {
995 stage.new_morph_target_weights(weights)
996 }
997 } else {
998 stage.new_morph_target_weights(weights)
999 };
1000 let transform = transform_for_node(0, stage, &mut node_transforms, &node);
1001 nodes.push(GltfNode {
1002 index: node.index(),
1003 name: node.name().map(String::from),
1004 light,
1005 mesh,
1006 camera,
1007 skin,
1008 children,
1009 weights,
1010 transform,
1011 });
1012 }
1013 log::trace!(" loaded {} nodes", nodes.len());
1014
1015 log::trace!("Loading cameras");
1016 let mut cameras = vec![];
1017 for camera in document.cameras() {
1018 let camera_index = camera.index();
1019 let node_index =
1020 *camera_index_to_node_index
1021 .get(&camera_index)
1022 .context(MissingCameraSnafu {
1023 index: camera_index,
1024 })?;
1025 let transform = node_transforms
1026 .get(&node_index)
1027 .context(MissingNodeSnafu { index: node_index })?;
1028 cameras.push(GltfCamera::new(stage, camera, transform));
1029 }
1030
1031 log::trace!("Loading lights");
1032 let mut lights = vec![];
1033 if let Some(gltf_lights) = document.lights() {
1034 for gltf_light in gltf_lights {
1035 let node_index = *light_index_to_node_index.get(&gltf_light.index()).context(
1036 MissingCameraSnafu {
1037 index: gltf_light.index(),
1038 },
1039 )?;
1040
1041 let node_transform = node_transforms
1042 .get(&node_index)
1043 .context(MissingNodeSnafu { index: node_index })?
1044 .clone();
1045
1046 let color = Vec3::from(gltf_light.color()).extend(1.0);
1047 let intensity = gltf_light.intensity();
1048 let light_bundle = match gltf_light.kind() {
1049 gltf::khr_lights_punctual::Kind::Directional => {
1050 stage
1051 .new_directional_light()
1052 .with_direction(Vec3::NEG_Z)
1053 .with_color(color)
1054 .with_intensity(Lux(intensity))
1056 .into_generic()
1057 }
1058
1059 gltf::khr_lights_punctual::Kind::Point => stage
1060 .new_point_light()
1061 .with_position(Vec3::ZERO)
1062 .with_color(color)
1063 .with_intensity(Candela(intensity))
1067 .into_generic(),
1068
1069 gltf::khr_lights_punctual::Kind::Spot {
1070 inner_cone_angle,
1071 outer_cone_angle,
1072 } => stage
1073 .new_spot_light()
1074 .with_position(Vec3::ZERO)
1075 .with_direction(Vec3::NEG_Z)
1076 .with_inner_cutoff(inner_cone_angle)
1077 .with_outer_cutoff(outer_cone_angle)
1078 .with_color(color)
1079 .with_intensity(Candela(intensity))
1083 .into_generic(),
1084 };
1085
1086 log::trace!(
1087 " linking light {:?} with node transform {:?}: {:#?}",
1088 light_bundle.id(),
1089 node_transform.global_id(),
1090 node_transform.global_descriptor()
1091 );
1092 light_bundle.link_node_transform(&node_transform);
1093 lights.push(light_bundle);
1094 }
1095 }
1096
1097 log::trace!("Loading skins");
1098 let mut skins = vec![];
1099 for skin in document.skins() {
1100 skins.push(GltfSkin::from_gltf(stage, &buffer_data, &nodes, skin)?);
1101 }
1102
1103 log::trace!("Loading animations");
1104 let mut animations = vec![];
1105 for animation in document.animations() {
1106 animations.push(Animation::from_gltf(&buffer_data, animation).context(AnimationSnafu)?);
1107 }
1108
1109 log::debug!("Loading scenes");
1110 let scenes = document
1111 .scenes()
1112 .map(|scene| scene.nodes().map(|node| node.index()).collect())
1113 .collect();
1114
1115 log::debug!("Creating renderlets");
1116 let mut renderlets = FxHashMap::default();
1117 for gltf_node in nodes.iter() {
1118 let mut node_renderlets = vec![];
1119 let maybe_skin = if let Some(skin_index) = gltf_node.skin {
1120 log::debug!(" node {} {:?} has skin", gltf_node.index, gltf_node.name);
1121 let gltf_skin = skins
1122 .get(skin_index)
1123 .context(MissingSkinSnafu { index: skin_index })?;
1124 Some(gltf_skin.skin.clone())
1125 } else {
1126 None
1127 };
1128
1129 if let Some(mesh_index) = gltf_node.mesh {
1130 log::trace!(
1131 " node {} {:?} has mesh {mesh_index}",
1132 gltf_node.index,
1133 gltf_node.name
1134 );
1135 let mesh = meshes
1136 .get(mesh_index)
1137 .context(MissingMeshSnafu { index: mesh_index })?;
1138 let num_prims = mesh.primitives.len();
1139 log::trace!(" has {num_prims} primitives");
1140 for (prim, i) in mesh.primitives.iter().zip(1..) {
1141 let material = prim
1142 .material_index
1143 .and_then(|index| materials.get(index))
1144 .unwrap_or(&default_material);
1145 let renderlet = stage
1146 .new_primitive()
1147 .with_vertices(&prim.vertices)
1148 .with_indices(&prim.indices)
1149 .with_transform(&gltf_node.transform)
1150 .with_material(material)
1151 .with_bounds(prim.bounding_box.into())
1152 .with_morph_targets(&prim.morph_targets, &gltf_node.weights);
1153 if let Some(skin) = maybe_skin.as_ref() {
1154 renderlet.set_skin(skin);
1155 }
1156 log::trace!(
1157 " created renderlet {i}/{num_prims}: {:#?}",
1158 renderlet.id()
1159 );
1160 node_renderlets.push(renderlet);
1161 }
1162 }
1163 if !node_renderlets.is_empty() {
1164 renderlets.insert(gltf_node.index, node_renderlets);
1165 }
1166 }
1167
1168 log::debug!("Extensions used: {:?}", document.extensions_used());
1169 log::debug!("Extensions required: {:?}", document.extensions_required());
1170 log::debug!("Done loading gltf");
1171
1172 Ok(GltfDocument {
1173 textures,
1174 animations,
1175 lights,
1176 cameras,
1177 materials,
1178 default_material,
1179 meshes,
1180 nodes,
1181 scenes,
1182 skins,
1183 default_scene: document.default_scene().map(|scene| scene.index()),
1184 primitives: renderlets,
1185 extensions: document
1186 .extensions()
1187 .cloned()
1188 .map(serde_json::Value::Object),
1189 })
1190 }
1191
1192 pub fn into_gpu_only(self) -> GltfDocument<GpuOnlyArray> {
1196 let Self {
1197 animations,
1198 cameras,
1199 default_scene,
1200 extensions,
1201 textures,
1202 lights,
1203 meshes,
1204 nodes,
1205 default_material,
1206 materials,
1207 primitives,
1208 scenes,
1209 skins,
1210 } = self;
1211 let meshes = meshes
1212 .into_iter()
1213 .map(GltfMesh::into_gpu_only)
1214 .collect::<Vec<_>>();
1215 GltfDocument {
1216 animations,
1217 cameras,
1218 default_scene,
1219 extensions,
1220 textures,
1221 lights,
1222 meshes,
1223 nodes,
1224 default_material,
1225 materials,
1226 primitives,
1227 scenes,
1228 skins,
1229 }
1230 }
1231}
1232
1233impl<Ct> GltfDocument<Ct>
1234where
1235 Ct: IsContainer<Pointer<Vertex> = Array<Vertex>>,
1236 Ct: IsContainer<Pointer<u32> = Array<u32>>,
1237{
1238 pub fn renderlets_iter(&self) -> impl Iterator<Item = &Primitive> {
1239 self.primitives.iter().flat_map(|(_, rs)| rs.iter())
1240 }
1241
1242 fn collect_nodes_recursive<'a>(&'a self, node_index: usize, nodes: &mut Vec<&'a GltfNode>) {
1243 if let Some(node) = self.nodes.get(node_index) {
1244 nodes.push(node);
1245 for child_index in node.children.iter() {
1246 self.collect_nodes_recursive(*child_index, nodes);
1247 }
1248 }
1249 }
1250
1251 pub fn root_nodes_in_scene(&self, scene_index: usize) -> impl Iterator<Item = &GltfNode> {
1260 let scene = self.scenes.get(scene_index);
1261 let mut nodes = vec![];
1262 if let Some(indices) = scene {
1263 for node_index in indices {
1264 if let Some(node) = self.nodes.get(*node_index) {
1265 nodes.push(node);
1266 }
1267 }
1268 }
1269 nodes.into_iter()
1270 }
1271
1272 pub fn recursive_nodes_in_scene(&self, scene_index: usize) -> impl Iterator<Item = &GltfNode> {
1278 let scene = self.scenes.get(scene_index);
1279 let mut nodes = vec![];
1280 if let Some(indices) = scene {
1281 for node_index in indices {
1282 self.collect_nodes_recursive(*node_index, &mut nodes);
1283 }
1284 }
1285 nodes.into_iter()
1286 }
1287
1288 pub fn bounding_volume(&self) -> Option<Aabb> {
1293 let mut aabbs = vec![];
1294 for node in self.nodes.iter() {
1295 if let Some(mesh_index) = node.mesh {
1296 let mesh = self.meshes.get(mesh_index)?;
1297 for prim in mesh.primitives.iter() {
1298 let (prim_min, prim_max) = prim.bounding_box;
1299 let prim_aabb = Aabb::new(prim_min, prim_max);
1300 aabbs.push(prim_aabb);
1301 }
1302 }
1303 }
1304 let mut aabbs = aabbs.into_iter();
1305 let mut aabb = aabbs.next()?;
1306 for next_aabb in aabbs {
1307 aabb = Aabb::union(aabb, next_aabb);
1308 }
1309 Some(aabb)
1310 }
1311}
1312
1313#[cfg(test)]
1314mod test {
1315 use crate::{context::Context, geometry::Vertex, test::BlockOnFuture};
1316 use glam::{Vec3, Vec4};
1317
1318 #[test]
1319 fn get_vertex_count_primitive_sanity() {
1320 let (document, _, _) =
1321 gltf::import("../../gltf/gltfTutorial_008_SimpleMeshes.gltf").unwrap();
1322 let prim = document
1323 .meshes()
1324 .next()
1325 .unwrap()
1326 .primitives()
1327 .next()
1328 .unwrap();
1329 let vertex_count = super::get_vertex_count(&prim);
1330 assert_eq!(3, vertex_count);
1331 }
1332
1333 #[test]
1334 fn stage_gltf_simple_meshes() {
1340 let ctx = Context::headless(100, 50).block();
1341 let projection = crate::camera::perspective(100.0, 50.0);
1342 let position = Vec3::new(1.0, 0.5, 1.5);
1343 let view = crate::camera::look_at(position, Vec3::new(1.0, 0.5, 0.0), Vec3::Y);
1344 let stage = ctx
1345 .new_stage()
1346 .with_lighting(false)
1347 .with_bloom(false)
1348 .with_background_color(Vec3::splat(0.0).extend(1.0));
1349 let _camera = stage
1350 .new_camera()
1351 .with_projection_and_view(projection, view);
1352 let _doc = stage
1353 .load_gltf_document_from_path("../../gltf/gltfTutorial_008_SimpleMeshes.gltf")
1354 .unwrap();
1355
1356 let frame = ctx.get_next_frame().unwrap();
1357 stage.render(&frame.view());
1358 let img = frame.read_image().block().unwrap();
1359 img_diff::assert_img_eq("gltf/simple_meshes.png", img);
1360 }
1361
1362 #[test]
1363 fn minimal_mesh() {
1365 let ctx = Context::headless(20, 20).block();
1366 let stage = ctx
1367 .new_stage()
1368 .with_lighting(false)
1369 .with_bloom(false)
1370 .with_background_color(Vec3::splat(0.0).extend(1.0));
1371
1372 let projection = crate::camera::perspective(20.0, 20.0);
1373 let eye = Vec3::new(0.5, 0.5, 2.0);
1374 let view = crate::camera::look_at(eye, Vec3::new(0.5, 0.5, 0.0), Vec3::Y);
1375 let _camera = stage
1376 .new_camera()
1377 .with_projection_and_view(projection, view);
1378
1379 let _doc = stage
1380 .load_gltf_document_from_path("../../gltf/gltfTutorial_003_MinimalGltfFile.gltf")
1381 .unwrap();
1382
1383 let frame = ctx.get_next_frame().unwrap();
1384 stage.render(&frame.view());
1385 let img = frame.read_image().block().unwrap();
1386 img_diff::assert_img_eq("gltf/minimal_mesh.png", img);
1387 }
1388
1389 #[test]
1390 fn gltf_images() {
1394 let ctx = Context::headless(100, 100).block();
1395 let stage = ctx
1396 .new_stage()
1397 .with_lighting(false)
1398 .with_background_color(Vec4::splat(1.0));
1399 let (projection, view) = crate::camera::default_ortho2d(100.0, 100.0);
1400 let _camera = stage
1401 .new_camera()
1402 .with_projection_and_view(projection, view);
1403 let doc = stage
1404 .load_gltf_document_from_path("../../gltf/cheetah_cone.glb")
1405 .unwrap();
1406 assert!(!doc.textures.is_empty());
1407 let material = stage
1408 .new_material()
1409 .with_albedo_texture(&doc.textures[0])
1410 .with_has_lighting(false);
1411 let _rez = stage
1412 .new_primitive()
1413 .with_material(&material)
1414 .with_vertices(
1415 stage.new_vertices([
1416 Vertex::default()
1417 .with_position([0.0, 0.0, 0.0])
1418 .with_uv0([0.0, 0.0]),
1419 Vertex::default()
1420 .with_position([1.0, 0.0, 0.0])
1421 .with_uv0([1.0, 0.0]),
1422 Vertex::default()
1423 .with_position([1.0, 1.0, 0.0])
1424 .with_uv0([1.0, 1.0]),
1425 Vertex::default()
1426 .with_position([0.0, 1.0, 0.0])
1427 .with_uv0([0.0, 1.0]),
1428 ]),
1429 )
1430 .with_indices(stage.new_indices([0u32, 3, 2, 0, 2, 1]))
1431 .with_transform(
1432 stage
1433 .new_transform()
1434 .with_scale(Vec3::new(100.0, 100.0, 1.0)),
1435 );
1436 println!("material_id: {:#?}", material.id());
1437
1438 let frame = ctx.get_next_frame().unwrap();
1439 stage.render(&frame.view());
1440 let img = frame.read_linear_image().block().unwrap();
1441 img_diff::assert_img_eq("gltf/images.png", img);
1442 }
1443
1444 #[test]
1445 fn simple_texture() {
1446 let size = 100;
1447 let ctx = Context::headless(size, size).block();
1448 let stage = ctx
1449 .new_stage()
1450 .with_background_color(Vec3::splat(0.0).extend(1.0))
1451 .with_lighting(false)
1454 .with_bloom(false);
1455 let projection = crate::camera::perspective(size as f32, size as f32);
1456 let view =
1457 crate::camera::look_at(Vec3::new(0.5, 0.5, 1.25), Vec3::new(0.5, 0.5, 0.0), Vec3::Y);
1458 let _camera = stage
1459 .new_camera()
1460 .with_projection_and_view(projection, view);
1461
1462 let _doc = stage
1463 .load_gltf_document_from_path("../../gltf/gltfTutorial_013_SimpleTexture.gltf")
1464 .unwrap();
1465
1466 let frame = ctx.get_next_frame().unwrap();
1467 stage.render(&frame.view());
1468 let img = frame.read_image().block().unwrap();
1469 img_diff::assert_img_eq("gltf/simple_texture.png", img);
1470 }
1471
1472 #[test]
1473 fn normal_mapping_brick_sphere() {
1476 let ctx = Context::headless(1920, 1080).block();
1477 let stage = ctx
1478 .new_stage()
1479 .with_lighting(true)
1480 .with_background_color(Vec4::new(0.01, 0.01, 0.01, 1.0));
1481
1482 let _doc = stage
1483 .load_gltf_document_from_path("../../gltf/normal_mapping_brick_sphere.glb")
1484 .unwrap();
1485
1486 let frame = ctx.get_next_frame().unwrap();
1487 stage.render(&frame.view());
1488 let img = frame.read_image().block().unwrap();
1489 img_diff::assert_img_eq("gltf/normal_mapping_brick_sphere.png", img);
1490 }
1491
1492 #[test]
1493 fn rigged_fox() {
1494 let ctx = Context::headless(256, 256).block();
1495 let stage = ctx
1496 .new_stage()
1497 .with_lighting(false)
1498 .with_vertex_skinning(false)
1499 .with_bloom(false)
1500 .with_background_color(Vec3::splat(0.5).extend(1.0));
1501
1502 let aspect = 256.0 / 256.0;
1503 let fovy = core::f32::consts::PI / 4.0;
1504 let znear = 0.1;
1505 let zfar = 1000.0;
1506 let projection = glam::Mat4::perspective_rh(fovy, aspect, znear, zfar);
1507 let y = 50.0;
1508 let eye = Vec3::new(120.0, y, 120.0);
1509 let target = Vec3::new(0.0, y, 0.0);
1510 let up = Vec3::Y;
1511 let view = glam::Mat4::look_at_rh(eye, target, up);
1512
1513 let _camera = stage
1514 .new_camera()
1515 .with_projection_and_view(projection, view);
1516 let _doc = stage
1517 .load_gltf_document_from_path("../../gltf/Fox.glb")
1518 .unwrap();
1519
1520 let frame = ctx.get_next_frame().unwrap();
1522 stage.render(&frame.view());
1523 let img = frame.read_image().block().unwrap();
1524 img_diff::assert_img_eq("gltf/skinning/rigged_fox_no_skinning.png", img);
1525
1526 stage.set_has_vertex_skinning(true);
1528 let frame = ctx.get_next_frame().unwrap();
1529 stage.render(&frame.view());
1530 let img = frame.read_image().block().unwrap();
1531 img_diff::assert_img_eq_cfg(
1532 "gltf/skinning/rigged_fox_no_skinning.png",
1533 img,
1534 img_diff::DiffCfg {
1535 test_name: Some("gltf/skinning/rigged_fox"),
1536 ..Default::default()
1537 },
1538 );
1539
1540 }
1573
1574 #[test]
1575 fn camera_position_sanity() {
1576 let ctx = Context::headless(100, 100).block();
1580 let stage = ctx.new_stage();
1581 let doc = stage
1582 .load_gltf_document_from_path(
1583 crate::test::workspace_dir()
1584 .join("gltf")
1585 .join("shadow_mapping_sanity_camera.gltf"),
1586 )
1587 .unwrap();
1588 let camera_a = doc.cameras.first().unwrap();
1589
1590 let desc = camera_a.camera.descriptor();
1591 const THRESHOLD: f32 = 10e-6;
1592 let a = Vec3::new(14.699949, 4.958309, 12.676651);
1593 let b = Vec3::new(14.699949, -12.676651, 4.958309);
1594 let distance_a = a.distance(desc.position());
1595 let distance_b = b.distance(desc.position());
1596 if distance_a > THRESHOLD && distance_b > THRESHOLD {
1597 println!("desc: {desc:#?}");
1598 println!("distance_a: {distance_a}");
1599 println!("distance_b: {distance_b}");
1600 println!("threshold: {THRESHOLD}");
1601 panic!("distance greater than threshold");
1602 }
1603
1604 let doc = stage
1605 .load_gltf_document_from_path(
1606 crate::test::workspace_dir()
1607 .join("gltf")
1608 .join("shadow_mapping_sanity.gltf"),
1609 )
1610 .unwrap();
1611 let camera_b = doc.cameras.first().unwrap();
1612
1613 let eq = |a: Vec3, b: Vec3| {
1614 let c = Vec3::new(b.x, -b.z, b.y);
1615 println!("a: {a}");
1616 println!("b: {b}");
1617 println!("c: {c}");
1618 a.distance(b) <= 10e-6 || c.distance(c) <= 10e-6
1619 };
1620 assert!(eq(
1621 camera_a.camera.descriptor().position(),
1622 camera_b.camera.descriptor().position()
1623 ));
1624 }
1625}