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