using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
namespace SharpGLTF.Schema2.LoadAndSave
{
///
/// Test cases for models found in and more....
///
[TestFixture]
[AttachmentPathFormat("*/TestResults/LoadAndSave/?", true)]
[Category("Model Load and Save")]
public class LoadSampleTests
{
#region setup
[OneTimeSetUp]
public void Setup()
{
// TestFiles.DownloadReferenceModels();
}
#endregion
#region helpers
private static ModelRoot _LoadModel(string f, bool tryFix = false)
{
var settings = tryFix
? Validation.ValidationMode.TryFix
: Validation.ValidationMode.Strict;
ModelRoot model = null;
var perf = System.Diagnostics.Stopwatch.StartNew();
try
{
model = ModelRoot.Load(f, settings);
Assert.That(model, Is.Not.Null);
}
catch (Exception ex)
{
TestContext.Progress.WriteLine($"Failed {f.ToShortDisplayPath()}");
Assert.Fail(ex.Message);
}
var perf_load = perf.ElapsedMilliseconds;
// do a model clone and compare it
_AssertAreEqual(model, model.DeepClone());
var perf_clone = perf.ElapsedMilliseconds;
if (!f.Contains("Iridescence")) // the iridescence sample models declares using IOR but it's not actually used
{
var unsupportedExtensions = new[] { "MSFT_lod", "EXT_lights_image_based" };
// check extensions used
if (unsupportedExtensions.All(uex => !model.ExtensionsUsed.Contains(uex)))
{
var detectedExtensions = model.GatherUsedExtensions().ToArray();
Assert.That(detectedExtensions, Is.EquivalentTo(model.ExtensionsUsed));
}
}
// Save models
model.AttachToCurrentTest(System.IO.Path.ChangeExtension(System.IO.Path.GetFileName(f), ".obj"));
var perf_wavefront = perf.ElapsedMilliseconds;
model.AttachToCurrentTest(System.IO.Path.ChangeExtension(System.IO.Path.GetFileName(f), ".glb"));
var perf_glb = perf.ElapsedMilliseconds;
TestContext.Progress.WriteLine($"processed {f.ToShortDisplayPath()} - Load:{perf_load}ms Clone:{perf_clone}ms S.obj:{perf_wavefront}ms S.glb:{perf_glb}ms");
return model;
}
private static void _AssertAreEqual(ModelRoot a, ModelRoot b)
{
var aa = a.GetLogicalChildrenFlattened().ToList();
var bb = b.GetLogicalChildrenFlattened().ToList();
Assert.That(bb, Has.Count.EqualTo(aa.Count));
Assert.That
(
aa.Select(item => item.GetType()),
Is.EqualTo(bb.Select(item => item.GetType()))
);
}
#endregion
[TestCase("\\glTF\\")]
// [TestCase("\\glTF-Draco\\")] // Not supported
[TestCase("\\glTF-IBL\\")]
[TestCase("\\glTF-Binary\\")]
[TestCase("\\glTF-Embedded\\")]
[TestCase("\\glTF-Quantized\\")]
// [TestCase("\\glTF-Meshopt\\")] // not supported
public void LoadModelsFromKhronosSamples(string section)
{
TestContext.CurrentContext.AttachGltfValidatorLinks();
Assert.Multiple( () =>
{
foreach (var f in TestFiles.GetSampleModelsPaths())
{
if (f.Contains("SuzanneMorphSparse")) continue; // temporarily skipping due to empty BufferView issue
if (!f.Contains(section)) continue;
_LoadModel(f);
}
} );
}
[Test]
public void LoadModelsFromBabylonJs()
{
TestContext.CurrentContext.AttachGltfValidatorLinks();
foreach (var f in TestFiles.GetBabylonJSModelsPaths())
{
_LoadModel(f, true);
}
}
[TestCase("TeapotsGalore.gltf")]
[TestCase("GrassFieldInstanced.glb")]
[TestCase("InstanceTest.glb")]
public void LoadModelsWithGpuMeshInstancingExtension(string fileFilter)
{
TestContext.CurrentContext.AttachGltfValidatorLinks();
var f = TestFiles.GetMeshIntancingModelPaths().FirstOrDefault(item => item.Contains(fileFilter));
var model = _LoadModel(f, false);
var ff = System.IO.Path.GetFileNameWithoutExtension(f);
model.AttachToCurrentTest($"{ff}.loaded.glb");
// perform roundtrip
var roundtripDefault = model.DefaultScene
.ToSceneBuilder() // glTF to SceneBuilder
.ToGltf2(Scenes.SceneBuilderSchema2Settings.Default); // SceneBuilder to glTF
var roundtripInstanced = model.DefaultScene
.ToSceneBuilder() // glTF to SceneBuilder
.ToGltf2(Scenes.SceneBuilderSchema2Settings.WithGpuInstancing); // SceneBuilder to glTF
// compare bounding spheres
var modelBounds = Runtime.MeshDecoder.EvaluateBoundingBox(model.DefaultScene);
var rtripDefBounds = Runtime.MeshDecoder.EvaluateBoundingBox(roundtripDefault.DefaultScene);
var rtripGpuBounds = Runtime.MeshDecoder.EvaluateBoundingBox(roundtripInstanced.DefaultScene);
Assert.That(rtripDefBounds, Is.EqualTo(modelBounds));
Assert.That(rtripGpuBounds, Is.EqualTo(modelBounds));
// save results
roundtripDefault.AttachToCurrentTest($"{ff}.roundtrip.default.glb");
roundtripInstanced.AttachToCurrentTest($"{ff}.roundtrip.instancing.glb");
}
[TestCase("IridescenceMetallicSpheres.gltf")]
[TestCase("SpecGlossVsMetalRough.gltf")]
[TestCase(@"TextureTransformTest.gltf")]
[TestCase(@"UnlitTest\glTF-Binary\UnlitTest.glb")]
[TestCase(@"glTF-Quantized\Avocado.gltf")]
[TestCase(@"glTF-Quantized\AnimatedMorphCube.gltf")]
[TestCase(@"glTF-Quantized\AnimatedMorphCube.gltf")]
[TestCase(@"glTF-Quantized\Duck.gltf")]
[TestCase(@"glTF-Quantized\Lantern.gltf")]
[TestCase(@"MosquitoInAmber.glb")]
public void LoadModelsWithExtensions(string filePath)
{
TestContext.CurrentContext.AttachGltfValidatorLinks();
filePath = TestFiles
.GetSampleModelsPaths()
.FirstOrDefault(item => item.EndsWith(filePath));
_LoadModel(filePath);
}
[Test]
public void LoadModelWithUnlitMaterial()
{
var f = TestFiles
.GetSampleModelsPaths()
.FirstOrDefault(item => item.EndsWith(@"UnlitTest\glTF-Binary\UnlitTest.glb"));
var model = ModelRoot.Load(f);
Assert.That(model, Is.Not.Null);
Assert.That(model.LogicalMaterials[0].Unlit, Is.True);
// do a model roundtrip
var modelBis = ModelRoot.ParseGLB(model.WriteGLB());
Assert.That(modelBis, Is.Not.Null);
Assert.That(modelBis.LogicalMaterials[0].Unlit, Is.True);
}
[Test]
public void LoadModelWithLights()
{
var f = TestFiles
.GetSchemaExtensionsModelsPaths()
.FirstOrDefault(item => item.EndsWith("lights.gltf"));
var model = ModelRoot.Load(f);
Assert.That(model, Is.Not.Null);
Assert.That(model.LogicalPunctualLights, Has.Count.EqualTo(3));
Assert.That(model.DefaultScene.VisualChildren.ElementAt(0).PunctualLight.LogicalIndex, Is.EqualTo(1));
Assert.That(model.DefaultScene.VisualChildren.ElementAt(1).PunctualLight.LogicalIndex, Is.EqualTo(0));
}
[TestCase("SimpleSparseAccessor.gltf")]
// [TestCase("SuzanneMorphSparse.gltf")] requires supporting empty BufferView
public void LoadModelWithSparseAccessor(string fileName)
{
var path = TestFiles
.GetSampleModelsPaths()
.FirstOrDefault(item => item.Contains(fileName));
var model = ModelRoot.Load(path);
Assert.That(model, Is.Not.Null);
var primitive = model.LogicalMeshes[0].Primitives[0];
var accessor = primitive.GetVertexAccessor("POSITION");
var basePositions = accessor._GetMemoryAccessor().AsVector3Array();
var positions = accessor.AsVector3Array();
}
[Test]
public void LoadModelWithMorphTargets()
{
var path = TestFiles
.GetSampleModelsPaths()
.FirstOrDefault(item => item.Contains("MorphPrimitivesTest.glb"));
var model = ModelRoot.Load(path);
Assert.That(model, Is.Not.Null);
var triangles = model.DefaultScene
.EvaluateTriangles(null, null, 0)
.ToArray();
model.AttachToCurrentTest(System.IO.Path.ChangeExtension(System.IO.Path.GetFileName(path), ".obj"));
model.AttachToCurrentTest(System.IO.Path.ChangeExtension(System.IO.Path.GetFileName(path), ".glb"));
}
[TestCase("RiggedFigure.glb")]
[TestCase("RiggedSimple.glb")]
[TestCase("BoxAnimated.glb")]
[TestCase("AnimatedMorphCube.glb")]
[TestCase("AnimatedMorphSphere.glb")]
[TestCase("CesiumMan.glb")]
//[TestCase("Monster.glb")] // temporarily removed from khronos repo
[TestCase("BrainStem.glb")]
[TestCase("Fox.glb")]
public void LoadModelsWithAnimations(string path)
{
path = TestFiles
.GetSampleModelsPaths()
.FirstOrDefault(item => item.Contains(path));
var model = ModelRoot.Load(path);
Assert.That(model, Is.Not.Null);
path = System.IO.Path.GetFileNameWithoutExtension(path);
model.AttachToCurrentTest(path + ".glb");
var triangles = model.DefaultScene
.EvaluateTriangles()
.ToArray();
var anim = model.LogicalAnimations[0];
var duration = anim.Duration;
for(int i=0; i < 10; ++i)
{
var t = duration * i / 10;
int tt = (int)(t * 1000.0f);
model.AttachToCurrentTest($"{path} at {tt}.obj",anim, t);
}
}
[Test]
public void LoadAnimatedMorphCube()
{
var path = TestFiles
.GetSampleModelsPaths()
.FirstOrDefault(item => item.Contains("AnimatedMorphCube.glb"));
var model = ModelRoot.Load(path);
Assert.That(model, Is.Not.Null);
var anim = model.LogicalAnimations[0];
var node = model.LogicalNodes[0];
var acc_master = node.Mesh.Primitives[0].GetVertexAccessor("POSITION");
var acc_morph0 = node.Mesh.Primitives[0].GetMorphTargetAccessors(0)["POSITION"];
var acc_morph1 = node.Mesh.Primitives[0].GetMorphTargetAccessors(1)["POSITION"];
var pos_master = acc_master.AsVector3Array();
var pos_morph0 = acc_morph0.AsVector3Array();
var pos_morph1 = acc_morph1.AsVector3Array();
// pos_master
var instance = Runtime.SceneTemplate
.Create(model.DefaultScene)
.CreateInstance();
var pvrt = node.Mesh.Primitives[0].GetVertexColumns();
for (float t = 0; t < 5; t+=0.25f)
{
instance.Armature.SetAnimationFrame(anim.LogicalIndex, t);
var nodexform = instance.GetDrawableInstance(0).Transform;
TestContext.WriteLine($"Animation at {t}");
var curves = node.GetCurveSamplers(anim);
if (t < anim.Duration)
{
var mw = curves.GetMorphingSampler()
.CreateCurveSampler()
.GetPoint(t);
TestContext.WriteLine($" Morph Weights: {mw[0]} {mw[1]}");
}
var msw = curves.GetMorphingSampler()
.CreateCurveSampler()
.GetPoint(t);
TestContext.WriteLine($" Morph Sparse : {msw.Weight0} {msw.Weight1}");
var triangles = model.DefaultScene
.EvaluateTriangles(null, anim, t)
.ToList();
var vertices = triangles
.SelectMany(item => new[] { item.A.Position, item.B.Position, item.C.Position })
.Distinct()
.ToList();
foreach (var v in vertices) TestContext.WriteLine($"{v}");
TestContext.WriteLine();
}
}
[Test]
public void LoadMultiUVTexture()
{
var path = TestFiles
.GetSampleModelsPaths()
.FirstOrDefault(item => item.Contains("TextureTransformMultiTest.glb"));
var model = ModelRoot.Load(path);
Assert.That(model, Is.Not.Null);
var materials = model.LogicalMaterials;
var normalTest0Mat = materials.FirstOrDefault(item => item.Name == "NormalTest0Mat");
var normalTest0Mat_normal = normalTest0Mat.FindChannel("Normal").Value;
var normalTest0Mat_normal_xform = normalTest0Mat_normal.TextureTransform;
Assert.That(normalTest0Mat_normal_xform, Is.Not.Null);
}
[Test]
public void FindDependencyFiles()
{
TestContext.CurrentContext.AttachGltfValidatorLinks();
foreach (var f in TestFiles.GetBabylonJSModelsPaths())
{
TestContext.WriteLine(f);
var dependencies = ModelRoot.GetSatellitePaths(f);
foreach(var d in dependencies)
{
TestContext.WriteLine($" {d}");
}
TestContext.WriteLine();
}
}
}
}