Files
MetaCrate/tests/compat/tests/asset_document_semantics.rs
Chili Palmer 55424dbd7f
Some checks failed
Native code generation / deterministic (push) Failing after 1m48s
Imaging and meshing gate / native (push) Successful in 5m26s
JPEG 2000 feature / linux (push) Successful in 2m51s
Native Rust workspace compile / compile (push) Successful in 5m28s
Skia feature / linux (push) Successful in 31m53s
Implement native archive and model workflows (#74)
2026-08-10 20:31:34 +00:00

582 lines
24 KiB
Rust

#![allow(clippy::float_cmp)]
// Exact BSD-3-Clause fixture provenance at LibreMetaverse
// 2aa70bb68513b39795da5d13c88f31b86e85a3ba (see LICENSE.md):
// AssetMaterialOverrideTests.cs 3ec3a8bca08da06c52de1eeea1428b2b83c27660af20580f0333ed67b01bd09b
// GltfDocumentTests.cs e6e94d5b97a27b8cc4a345f08ab9a9a43b6ce587667edd89d8a4ebbc04eaa646
// Embedded 44-byte triangle buffer d3478e66e380d8a2b0c9d80f3459f75660efb111e7b801a7d1c1f8f48723d79b
use libremetaverse::assets::gltf::{GltfDocument, GltfPrimitive, GltfPrimitiveMode};
use libremetaverse::assets::{AssetMaterial, GltfAlphaMode};
use libremetaverse_structured_data::OSDMap;
use libremetaverse_types::{Color4, Quaternion, UUID, Vector2, Vector3};
fn color(r: f32, g: f32, b: f32, a: f32) -> Color4 {
Color4::new_with_single_single_single_single(r, g, b, a).expect("Color4 constructor")
}
fn vector2(x: f32, y: f32) -> Vector2 {
Vector2::new_with_single_single(x, y).expect("Vector2 constructor")
}
fn color_values(value: &Color4) -> [f32; 4] {
[value.r, value.g, value.b, value.a]
}
#[allow(clippy::trivially_copy_pass_by_ref)] // Mirrors the reference-oriented test helper shape.
fn vector2_values(value: &Vector2) -> [f32; 2] {
[value.x, value.y]
}
fn vector3_values(value: &Vector3) -> [f32; 3] {
[value.x, value.y, value.z]
}
fn quaternion_values(value: &Quaternion) -> [f32; 4] {
[value.x, value.y, value.z, value.w]
}
fn uuid_eq(actual: &UUID, expected: &UUID) {
assert_eq!(
actual.get_bytes().expect("actual UUID bytes"),
expected.get_bytes().expect("expected UUID bytes")
);
}
fn minimal_gltf_json() -> String {
r#"{
"asset": { "version": "2.0", "generator": "Test" },
"scene": 0,
"scenes": [{ "name": "Scene", "nodes": [0] }],
"nodes": [{ "name": "Mesh", "mesh": 0 }],
"meshes": [{
"name": "Triangle",
"primitives": [{
"attributes": { "POSITION": 0 },
"indices": 1,
"material": 0,
"mode": 4
}]
}],
"materials": [{
"name": "Mat",
"pbrMetallicRoughness": {
"baseColorFactor": [1.0, 0.5, 0.0, 1.0],
"metallicFactor": 0.0,
"roughnessFactor": 1.0
},
"alphaMode": "OPAQUE",
"alphaCutoff": 0.5,
"doubleSided": false
}],
"accessors": [
{
"bufferView": 0,
"byteOffset": 0,
"componentType": 5126,
"count": 3,
"type": "VEC3",
"max": [1.0, 1.0, 0.0],
"min": [0.0, 0.0, 0.0]
},
{
"bufferView": 1,
"byteOffset": 0,
"componentType": 5123,
"count": 3,
"type": "SCALAR"
}
],
"bufferViews": [
{ "buffer": 0, "byteOffset": 0, "byteLength": 36, "target": 34962 },
{ "buffer": 0, "byteOffset": 36, "byteLength": 6, "target": 34963 }
],
"buffers": [{
"byteLength": 44,
"uri": "data:application/octet-stream;base64,AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAABAAIAAAA="
}]
}"#
.into()
}
fn load_document() -> GltfDocument {
GltfDocument::load_gltf(minimal_gltf_json(), None).expect("GltfDocument LoadGltf")
}
fn first_primitive(document: &GltfDocument) -> GltfPrimitive {
document.meshes()[0]
.primitives()
.into_iter()
.next()
.expect("first primitive")
}
fn assert_close(actual: f32, expected: f32, tolerance: f32) {
assert!((actual - expected).abs() <= tolerance);
}
// parity-case: LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.SetBaseColorFactor_OverrideToDefault_IsNudgedOffDefault::test df553fd4df83e48d4dd74cdd8aa15a737ea3a416bcad802f7110bbfe79928a31 translated
#[test]
fn base_color_override_to_default_is_nudged() {
let mut material = AssetMaterial::new_with_constructor().expect("AssetMaterial constructor");
let default = AssetMaterial::default().base_color_factor();
material
.set_base_color_factor_with_color4_boolean(default, Some(true))
.expect("SetBaseColorFactor");
assert_ne!(
color_values(&material.base_color_factor()),
color_values(&AssetMaterial::default().base_color_factor())
);
}
// parity-case: LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.SetBaseColorFactor_NotOverride_LeavesExactDefaultUnchanged::test fec94fcfac0f36cc5813ed087a30e04fcc9815bc02072ea3f5dd282b8698a89c translated
#[test]
fn base_color_non_override_keeps_default() {
let mut material = AssetMaterial::new_with_constructor().expect("AssetMaterial constructor");
let default = AssetMaterial::default().base_color_factor();
material
.set_base_color_factor_with_color4_boolean(default, None)
.expect("SetBaseColorFactor");
assert_eq!(
color_values(&material.base_color_factor()),
color_values(&AssetMaterial::default().base_color_factor())
);
}
// parity-case: LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.SetMetallicFactor_OverrideToOne_ClampsBelowDefault::test 7fcee795160428082f6e579881867c7b6c83f31549077fc28a46d18c75c485c9 translated
#[test]
fn metallic_override_to_one_clamps_below_default() {
let mut material = AssetMaterial::new_with_constructor().expect("AssetMaterial constructor");
material
.set_metallic_factor_with_single_boolean(1.0, Some(true))
.expect("SetMetallicFactor");
assert!(material.metallic_factor() < 1.0);
assert_ne!(
material.metallic_factor(),
AssetMaterial::default().metallic_factor()
);
}
// parity-case: LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.SetAlphaMode_OverrideToDefaultOpaque_SetsOverrideFlag::test 8af09d12a58083657e27e285802bb3a6a1d1c136397cea53865d964006343715 translated
#[test]
fn alpha_mode_override_sets_flag() {
let mut material = AssetMaterial::new_with_constructor().expect("AssetMaterial constructor");
material
.set_alpha_mode_with_gltf_alpha_mode_boolean(GltfAlphaMode::Opaque, Some(true))
.expect("SetAlphaMode");
assert!(material.override_alpha_mode());
}
// parity-case: LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.SetTextureId_OverrideToNull_UsesSentinelUuid::test d9ebf410d2a55c3619b99524a571396fb268e5edd41de955cb86e4ec8cc55c78 translated
#[test]
fn null_texture_override_uses_sentinel() {
let mut material = AssetMaterial::new_with_constructor().expect("AssetMaterial constructor");
material
.set_texture_id(AssetMaterial::TEXTURE_BASE_COLOR, UUID::zero(), Some(true))
.expect("SetTextureId");
uuid_eq(
&material.texture_ids()[AssetMaterial::TEXTURE_BASE_COLOR as usize],
&AssetMaterial::gltf_override_null_uuid(),
);
}
// parity-case: LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.ApplyOverride_SentinelTexture_ClearsBaseTexture::test 17fd8cdb12ed5d1d0aa1b0a05dbcc357f75aa350d5079b066c957da459d9f628 translated
#[test]
fn sentinel_override_clears_base_texture() {
let mut base = AssetMaterial::new_with_constructor().expect("base material");
base.set_texture_id(
AssetMaterial::TEXTURE_BASE_COLOR,
UUID::random().expect("UUID Random"),
None,
)
.expect("base texture");
let mut override_ = AssetMaterial::new_with_constructor().expect("override material");
override_
.set_texture_id(AssetMaterial::TEXTURE_BASE_COLOR, UUID::zero(), Some(true))
.expect("override texture");
base.apply_override(override_).expect("ApplyOverride");
uuid_eq(
&base.texture_ids()[AssetMaterial::TEXTURE_BASE_COLOR as usize],
&UUID::zero(),
);
}
// parity-case: LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.ApplyOverride_UntouchedTexture_LeavesBaseTextureAlone::test 80ec5e369e3682d92ec6996841b15bda38a3ef6edcd36a2fd9893d8c15459348 translated
#[test]
fn untouched_override_keeps_base_texture() {
let original = UUID::random().expect("UUID Random");
let original_copy = UUID::new_with_bytes_int32(original.get_bytes().expect("UUID bytes"), 0)
.expect("UUID copy");
let mut base = AssetMaterial::new_with_constructor().expect("base material");
base.set_texture_id(AssetMaterial::TEXTURE_BASE_COLOR, original, None)
.expect("base texture");
let override_ = AssetMaterial::new_with_constructor().expect("override material");
base.apply_override(override_).expect("ApplyOverride");
uuid_eq(
&base.texture_ids()[AssetMaterial::TEXTURE_BASE_COLOR as usize],
&original_copy,
);
}
// parity-case: LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.ApplyOverride_ChangedScalar_ReplacesBaseValue::test 4c305c54c4e451932fbd4a4719957f92f38a7f7d28259559b43cb95fcf9250d1 translated
#[test]
fn changed_scalar_override_replaces_base() {
let mut base = AssetMaterial::new_with_constructor().expect("base material");
let mut override_ = AssetMaterial::new_with_constructor().expect("override material");
override_
.set_roughness_factor_with_single_boolean(0.3, Some(true))
.expect("SetRoughnessFactor");
base.apply_override(override_).expect("ApplyOverride");
assert_eq!(base.roughness_factor(), 0.3);
}
// parity-case: LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.SetBaseMaterial_ResetsFieldsButKeepsTransforms::test 39312602e736fd8607a41d9e6b6348bfaf6e659cf3f63100ee15edd20b672306 translated
#[test]
fn set_base_material_keeps_texture_transforms() {
let mut material = AssetMaterial::new_with_constructor().expect("AssetMaterial constructor");
material.set_name("Foo".into());
material
.set_roughness_factor_with_single_boolean(0.2, Some(true))
.expect("SetRoughnessFactor");
material
.set_texture_offset(AssetMaterial::TEXTURE_BASE_COLOR, vector2(0.5, 0.25))
.expect("SetTextureOffset");
material.set_base_material().expect("SetBaseMaterial");
assert_eq!(material.name(), "");
assert_eq!(
material.roughness_factor(),
AssetMaterial::default().roughness_factor()
);
assert_eq!(
vector2_values(
&material.texture_transforms()[AssetMaterial::TEXTURE_BASE_COLOR as usize].offset,
),
vector2_values(&vector2(0.5, 0.25))
);
}
// parity-case: LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.ToOverrideOsd_RoundTrip_PreservesOverriddenFields::test 8d542528a14e3b4dcb74ea8a940a44673aada51f98baf8abcdd59a06f36e3cb0 translated
#[test]
fn override_osd_round_trip_preserves_fields() {
let mut material = AssetMaterial::new_with_constructor().expect("AssetMaterial constructor");
material
.set_roughness_factor_with_single_boolean(0.4, Some(true))
.expect("SetRoughnessFactor");
material
.set_metallic_factor_with_single_boolean(0.6, Some(true))
.expect("SetMetallicFactor");
let texture = UUID::random().expect("UUID Random");
let expected =
UUID::new_with_bytes_int32(texture.get_bytes().expect("UUID bytes"), 0).expect("UUID copy");
material
.set_texture_id(AssetMaterial::TEXTURE_EMISSIVE, texture, Some(true))
.expect("SetTextureId");
material
.set_texture_rotation(AssetMaterial::TEXTURE_EMISSIVE, 1.25)
.expect("SetTextureRotation");
let osd = material.to_override_osd().expect("ToOverrideOsd");
let round_trip = AssetMaterial::from_override_osd(osd).expect("FromOverrideOsd");
assert_eq!(round_trip.roughness_factor(), 0.4);
assert_eq!(round_trip.metallic_factor(), 0.6);
uuid_eq(
&round_trip.texture_ids()[AssetMaterial::TEXTURE_EMISSIVE as usize],
&expected,
);
assert_eq!(
round_trip.texture_transforms()[AssetMaterial::TEXTURE_EMISSIVE as usize].rotation,
1.25
);
}
// parity-case: LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.ToOverrideOsd_UntouchedMaterial_ProducesEmptyMap::test c9e29f7ec0e6b28579e0499e3ec89841e01a313a9386ba60257e5e701129b1c2 translated
#[test]
fn untouched_material_override_osd_is_empty() {
let material = AssetMaterial::new_with_constructor().expect("AssetMaterial constructor");
let osd: OSDMap = material.to_override_osd().expect("ToOverrideOsd");
assert_eq!(osd.count(), 0);
}
// parity-case: LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.ToOverrideOsd_OverrideToDefaultRoughness_StillEncodesField::test 4ea9920c6709bd29858a8da66313fcee5ebebd646743c5d822dd6c9e477bf3b4 translated
#[test]
fn default_roughness_override_is_encoded() {
let mut material = AssetMaterial::new_with_constructor().expect("AssetMaterial constructor");
material
.set_roughness_factor_with_single_boolean(
AssetMaterial::default().roughness_factor(),
Some(true),
)
.expect("SetRoughnessFactor");
let osd = material.to_override_osd().expect("ToOverrideOsd");
assert!(osd.contains_key("rf".into()).expect("ContainsKey"));
let round_trip = AssetMaterial::from_override_osd(osd).expect("FromOverrideOsd");
assert_ne!(
round_trip.roughness_factor(),
AssetMaterial::default().roughness_factor()
);
}
// parity-case: LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.ToJson_RoundTrip_PreservesNameAndColor::test d7ae4270d82671a970fc1b6457292860d24481dd84e9fb919ae60fded5f1914e translated
#[test]
fn material_json_round_trip_preserves_name_and_color() {
let mut material = AssetMaterial::new_with_constructor().expect("AssetMaterial constructor");
material.set_name("Chrome".into());
material
.set_base_color_factor_with_color4_boolean(color(0.1, 0.2, 0.3, 1.0), None)
.expect("SetBaseColorFactor");
let json = material.to_json().expect("AssetMaterial ToJson");
let decoded =
AssetMaterial::new_with_uuid_bytes(UUID::random().expect("UUID Random"), json.into_bytes())
.expect("AssetMaterial bytes constructor");
assert_eq!(decoded.name(), "Chrome");
assert_eq!(
color_values(&decoded.base_color_factor()),
color_values(&material.base_color_factor())
);
}
// parity-case: LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.LoadGltf_ParsesSceneGraph::test e20583d8de31f793d1dc49d1dfde89e1eb8c00186359e7df4184e862ec9ed649 translated
#[test]
fn gltf_parses_scene_graph() {
let document = load_document();
assert_eq!(document.version(), "2.0");
assert_eq!(document.generator().as_deref(), Some("Test"));
assert_eq!(document.default_scene(), 0);
assert_eq!(document.scenes().len(), 1);
assert_eq!(document.scenes()[0].nodes().len(), 1);
assert_eq!(document.nodes().len(), 1);
assert_eq!(document.nodes()[0].mesh(), 0);
}
// parity-case: LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.LoadGltf_ParsesMeshAndPrimitive::test ef6a76308db1966423c7746f5414f3d0553587efc388bbedb962f38cc122ecaf translated
#[test]
fn gltf_parses_mesh_and_primitive() {
let document = load_document();
assert_eq!(document.meshes().len(), 1);
assert_eq!(document.meshes()[0].name().as_deref(), Some("Triangle"));
assert_eq!(document.meshes()[0].primitives().len(), 1);
let primitive = first_primitive(&document);
assert!(primitive.attributes().contains_key("POSITION"));
assert_eq!(primitive.indices(), 1);
assert_eq!(primitive.material(), 0);
assert_eq!(primitive.mode(), GltfPrimitiveMode::Triangles);
}
// parity-case: LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.LoadGltf_ParsesMaterial::test 0147b4d7102c5223f247f69fa4cf3daeb0aa6300ad46ef34d6a17163b694c13e translated
#[test]
fn gltf_parses_material() {
let document = load_document();
assert_eq!(document.materials().len(), 1);
let material = &document.materials()[0];
assert_eq!(material.name().as_deref(), Some("Mat"));
assert_close(material.base_color_factor().r, 1.0, 1e-4);
assert_close(material.base_color_factor().g, 0.5, 1e-4);
assert_close(material.metallic_factor(), 0.0, 1e-4);
assert_close(material.roughness_factor(), 1.0, 1e-4);
assert_eq!(material.alpha_mode(), GltfAlphaMode::Opaque);
}
// parity-case: LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.GetPositions_ReturnsCorrectTriangle::test c3d444122ab73794a011d5a5aafb0c2aa030975393f967e871dc88db200f5e96 translated
#[test]
fn gltf_positions_are_correct_triangle() {
let document = load_document();
let positions = document
.get_positions(first_primitive(&document))
.expect("GetPositions");
assert_eq!(positions.len(), 3);
assert_eq!(
vector3_values(&positions[0]),
vector3_values(&Vector3::zero())
);
assert_close(positions[1].x, 1.0, 1e-5);
assert_close(positions[1].y, 0.0, 1e-5);
assert_close(positions[2].x, 0.0, 1e-5);
assert_close(positions[2].y, 1.0, 1e-5);
}
// parity-case: LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.GetIndices_ReturnsCorrectValues::test fa39e87a3dfee7258d9a95907a97cbb107f5add7d6a6f9d9800249bb67e2fc58 translated
#[test]
fn gltf_indices_are_correct() {
let document = load_document();
let indices = document
.get_indices(first_primitive(&document))
.expect("GetIndices");
assert_eq!(indices, [0, 1, 2]);
}
// parity-case: LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.GetPositions_EmptyWhenNoAttribute::test 06c691911ee6933f1884f6540d69afafa1f272acfed827c628580438204d9b4b translated
#[test]
fn gltf_positions_empty_without_attribute() {
let document = load_document();
let primitive = GltfPrimitive::new().expect("GltfPrimitive constructor");
assert_eq!(
document
.get_positions(primitive)
.expect("GetPositions")
.len(),
0
);
}
// parity-case: LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.GetNormals_EmptyWhenNoAttribute::test e04480b0b9e23cb561cc55e744ebd85203c759f98ad69febeeb291919f0d91af translated
#[test]
fn gltf_normals_empty_without_attribute() {
let document = load_document();
assert_eq!(
document
.get_normals(first_primitive(&document))
.expect("GetNormals")
.len(),
0
);
}
// parity-case: LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.ToJson_RoundTrip_PreservesSceneGraph::test 41c385b7bde3f8e4f4ad98d4f1f737fc6ccc241c2613526fd745eb35430386b2 translated
#[test]
fn gltf_json_round_trip_preserves_scene_graph() {
let original = load_document();
let json = original.to_json(None).expect("GltfDocument ToJson");
let round_trip = GltfDocument::load_gltf(json, None).expect("GltfDocument LoadGltf");
assert_eq!(round_trip.scenes().len(), original.scenes().len());
assert_eq!(round_trip.nodes().len(), original.nodes().len());
assert_eq!(round_trip.meshes().len(), original.meshes().len());
assert_eq!(round_trip.materials().len(), original.materials().len());
assert_close(
round_trip.materials()[0].base_color_factor().r,
original.materials()[0].base_color_factor().r,
1e-4,
);
}
// parity-case: LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.ToGlb_RoundTrip_PreservesGeometry::test 1e97fe2d1492ac195f6a4386329c76ad131ab99a41b8e03216b1bab8b48fae36 translated
#[test]
fn glb_round_trip_preserves_geometry() {
let original = load_document();
let glb = original.to_glb().expect("GltfDocument ToGlb");
assert_eq!(&glb[..4], &[0x67, 0x6c, 0x54, 0x46]);
let round_trip = GltfDocument::load(glb, None).expect("GltfDocument Load");
let positions = round_trip
.get_positions(first_primitive(&round_trip))
.expect("GetPositions");
assert_eq!(positions.len(), 3);
assert_close(positions[1].x, 1.0, 1e-5);
}
// parity-case: LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.Load_AutoDetectsGlbVsJson::test 6d07930be2302e504ef9ece3fef10f47b855d33ae6c8bff152e82df0beb2a474 translated
#[test]
fn gltf_load_auto_detects_glb_and_json() {
let original = load_document();
let glb = original.to_glb().expect("GltfDocument ToGlb");
let json = original.to_json(None).expect("GltfDocument ToJson");
let from_glb = GltfDocument::load(glb, None).expect("load GLB");
let from_json = GltfDocument::load(json.into_bytes(), None).expect("load JSON");
assert_eq!(from_glb.meshes().len(), 1);
assert_eq!(from_json.meshes().len(), 1);
}
// parity-case: LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.ParseNode_TrsDefaults::test 0f0fbe116d20276c2aee917b940bded9de277ed0b3e3b659844c5b3532755259 translated
#[test]
fn gltf_node_trs_defaults() {
let document = load_document();
let node = &document.nodes()[0];
assert!(node.matrix().is_none());
assert_eq!(
vector3_values(&node.translation()),
vector3_values(&Vector3::zero())
);
assert_eq!(
quaternion_values(&node.rotation()),
quaternion_values(&Quaternion::identity())
);
assert_eq!(
vector3_values(&node.scale()),
vector3_values(&Vector3::one())
);
}
// parity-case: LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.ParseNode_ExplicitMatrix::test 197ba2ee3a66cbb7d927acea13ce9dc84e62af8a500d939957749039118755c8 translated
#[test]
fn gltf_node_explicit_matrix() {
let document = GltfDocument::load_gltf(
r#"{
"asset": { "version": "2.0" },
"nodes": [{
"matrix": [1,0,0,0, 0,1,0,0, 0,0,1,0, 5,6,7,1]
}]
}"#
.into(),
None,
)
.expect("GltfDocument LoadGltf");
let matrix = document.nodes()[0]
.matrix()
.flatten()
.expect("explicit matrix");
assert_close(matrix.m14, 5.0, 1e-5);
assert_close(matrix.m24, 6.0, 1e-5);
assert_close(matrix.m34, 7.0, 1e-5);
}
#[test]
fn gltf_rejects_buffer_view_outside_declared_buffer() {
let json = r#"{
"asset":{"version":"2.0"},
"buffers":[{"byteLength":4,"uri":"data:application/octet-stream;base64,AAAAAA=="}],
"bufferViews":[{"buffer":0,"byteOffset":3,"byteLength":4}]
}"#;
assert!(GltfDocument::load_gltf(json.into(), None).is_err());
}
#[test]
fn gltf_rejects_cyclic_node_graphs() {
let json = r#"{
"asset":{"version":"2.0"},
"nodes":[{"children":[1]},{"children":[0]}],
"scenes":[{"nodes":[0]}],
"scene":0
}"#;
assert!(GltfDocument::load_gltf(json.into(), None).is_err());
}
#[test]
fn gltf_surface_skin_and_animation_round_trip() {
let json = r#"{
"asset":{"version":"2.0"},
"accessors":[
{"componentType":5126,"count":0,"type":"SCALAR"},
{"componentType":5126,"count":0,"type":"VEC3"}
],
"images":[{"uri":"data:image/png;base64,iVBORw0KGgo=","mimeType":"image/png","name":"image"}],
"samplers":[{"magFilter":9729,"minFilter":9987,"wrapS":10497,"wrapT":33071,"name":"sampler"}],
"textures":[{"sampler":0,"source":0,"name":"texture"}],
"materials":[{
"name":"surface",
"pbrMetallicRoughness":{"baseColorTexture":{"index":0},"metallicRoughnessTexture":{"index":0}},
"normalTexture":{"index":0,"scale":0.5},
"occlusionTexture":{"index":0,"strength":0.75},
"emissiveTexture":{"index":0},
"emissiveFactor":[0.1,0.2,0.3]
}],
"nodes":[{"skin":0}],
"skins":[{"joints":[0],"skeleton":0,"name":"skin"}],
"animations":[{
"name":"move",
"samplers":[{"input":0,"output":1,"interpolation":"LINEAR"}],
"channels":[{"sampler":0,"target":{"node":0,"path":"translation"}}]
}]
}"#;
let document = GltfDocument::load_gltf(json.into(), None).expect("load complete surface");
let serialized = document.to_json(None).expect("serialize complete surface");
let round_trip = GltfDocument::load_gltf(serialized, None).expect("reload complete surface");
assert_eq!(round_trip.images().len(), 1);
assert_eq!(round_trip.samplers().len(), 1);
assert_eq!(round_trip.textures().len(), 1);
assert_eq!(round_trip.skins()[0].joints(), [0]);
assert_eq!(round_trip.animations()[0].channels().len(), 1);
let material = &round_trip.materials()[0];
assert!(material.base_color_texture().is_some());
assert!(material.metallic_roughness_texture().is_some());
assert!(material.normal_texture().is_some());
assert!(material.occlusion_texture().is_some());
assert!(material.emissive_texture().is_some());
}