diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 0000000..d687a91
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,31 @@
+name: main
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ Build:
+ runs-on: windows-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ lfs: true
+
+ - name: Restore packages
+ run: nuget restore
+ working-directory: ./Source
+
+ - name: Setup build
+ uses: microsoft/setup-msbuild@v2
+
+ - name: Build targets
+ run: msbuild /t:ExplorerCommand /t:glTF /t:Tests /p:Configuration=Release /p:Platform=x64
+ working-directory: ./Source
+
+ - name: Run tests
+ run: dotnet test ./Build/Tests/bin/Release/AnyCPU/Tests.dll -s ./Source/.runsettings -v n
diff --git a/.gitignore b/.gitignore
index 72e650c..04da875 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,6 @@ Build
# published files end up here
/Source/glTF/bin
+
+# launc settings
+/Source/glTF/Properties/launchSettings.json
diff --git a/Source/.runsettings b/Source/.runsettings
new file mode 100644
index 0000000..c1b8723
--- /dev/null
+++ b/Source/.runsettings
@@ -0,0 +1,6 @@
+
+
+
+ ..\Build\Tests\Results
+
+
\ No newline at end of file
diff --git a/Source/glTF/Core/Binary.cs b/Source/Core/Binary.cs
similarity index 100%
rename from Source/glTF/Core/Binary.cs
rename to Source/Core/Binary.cs
diff --git a/Source/Core/Core.csproj b/Source/Core/Core.csproj
new file mode 100644
index 0000000..34edaf8
--- /dev/null
+++ b/Source/Core/Core.csproj
@@ -0,0 +1,10 @@
+
+
+
+ net8.0
+ enable
+ enable
+ glTF
+
+
+
diff --git a/Source/Core/Extensions/JsonExtensions.cs b/Source/Core/Extensions/JsonExtensions.cs
new file mode 100644
index 0000000..d74611d
--- /dev/null
+++ b/Source/Core/Extensions/JsonExtensions.cs
@@ -0,0 +1,79 @@
+using System.Text.Json;
+using System.Text.Json.Nodes;
+
+namespace glTF
+{
+ internal static class JsonExtensions
+ {
+ public static JsonArray? GetArray(this JsonNode node, string propertyName, JsonArray? defaultValue = null)
+ {
+ var propertyNode = node[propertyName];
+ if (propertyNode == null || propertyNode.GetValueKind() != JsonValueKind.Array)
+ {
+ return defaultValue;
+ }
+
+ return propertyNode.AsArray();
+ }
+
+ public static int GetInt(this JsonNode node, string propertyName, int defaultValue = -1)
+ {
+ var propertyNode = node[propertyName];
+ if (propertyNode == null || propertyNode.GetValueKind() != JsonValueKind.Number)
+ {
+ return defaultValue;
+ }
+
+ return propertyNode.GetValue();
+ }
+
+ public static string? GetString(this JsonNode node, string propertyName, string? defaultValue = null)
+ {
+ var propertyNode = node[propertyName];
+ if (propertyNode == null || propertyNode.GetValueKind() != JsonValueKind.String)
+ {
+ return defaultValue;
+ }
+
+ return propertyNode.GetValue();
+ }
+
+ public static string? GetLocalPath(this JsonNode node, string propertyName, Uri baseUri, string? defaultValue = null)
+ {
+ var uriString = node.GetString(propertyName);
+ if (uriString == null)
+ {
+ return defaultValue;
+ }
+
+ if (!Uri.TryCreate(baseUri, uriString, out var uri) || !uri.IsFile)
+ {
+ return defaultValue;
+ }
+
+ return uri.LocalPath;
+ }
+
+ public static void SetInt(this JsonNode jsonNode, string propertyName, int value, int defaultValue)
+ {
+ if (value == defaultValue)
+ {
+ jsonNode.AsObject().Remove(propertyName);
+ }
+ else
+ {
+ jsonNode[propertyName] = JsonValue.Create(value);
+ }
+ }
+
+ public static bool Remove(this JsonNode node, string propertyName)
+ {
+ if (node.GetValueKind() != JsonValueKind.Object)
+ {
+ return false;
+ }
+
+ return node.AsObject().Remove(propertyName);
+ }
+ }
+}
diff --git a/Source/glTF/Extensions/StreamExtensions.cs b/Source/Core/Extensions/StreamExtensions.cs
similarity index 100%
rename from Source/glTF/Extensions/StreamExtensions.cs
rename to Source/Core/Extensions/StreamExtensions.cs
diff --git a/Source/Core/MimeType.cs b/Source/Core/MimeType.cs
new file mode 100644
index 0000000..39da8b1
--- /dev/null
+++ b/Source/Core/MimeType.cs
@@ -0,0 +1,47 @@
+namespace glTF
+{
+ internal class MimeType
+ {
+ public static string ToFileExtension(string? mimeType)
+ {
+ switch (mimeType)
+ {
+ case "image/png":
+ return ".png";
+ case "image/jpeg":
+ return ".jpg";
+ case "image/vnd-ms.dds":
+ return ".dds";
+ case "image/ktx2":
+ return ".ktx2";
+ case "image/webp":
+ return ".webp";
+ }
+
+ return ".bin";
+ }
+
+ public static string FromFileExtension(string? fileExtension)
+ {
+ if (fileExtension != null)
+ {
+ switch (fileExtension.ToLower())
+ {
+ case ".png":
+ return "image/png";
+ case ".jpg":
+ case ".jpeg":
+ return "image/jpeg";
+ case ".dds":
+ return "image/vnd-ms.dds";
+ case ".ktx2":
+ return "image/ktx2";
+ case ".webp":
+ return "image/webp";
+ }
+ }
+
+ return "application/octet-stream";
+ }
+ }
+}
diff --git a/Source/Core/Packer.cs b/Source/Core/Packer.cs
new file mode 100644
index 0000000..f3e3a05
--- /dev/null
+++ b/Source/Core/Packer.cs
@@ -0,0 +1,237 @@
+using System.IO.MemoryMappedFiles;
+using System.Text.Json;
+using System.Text.Json.Nodes;
+
+namespace glTF
+{
+ public class Packer
+ {
+ private struct FileInfo
+ {
+ public MemoryMappedFile File;
+ public int FileLength;
+ public MemoryMappedViewStream Stream;
+ public int ByteOffset;
+ }
+
+ public static void Pack(string inputFilePath, string outputFilePath)
+ {
+ var baseUri = new Uri(inputFilePath);
+
+ var fileMap = new Dictionary();
+ var byteOffset = 0;
+
+ FileInfo AddFile(string filePath)
+ {
+ if (!fileMap.TryGetValue(filePath, out var fileInfo))
+ {
+ var file = MemoryMappedFile.CreateFromFile(filePath, FileMode.Open);
+ var fileLength = Tools.GetFileLength(filePath);
+ var stream = file.CreateViewStream(0, fileLength, MemoryMappedFileAccess.Read);
+ fileInfo = new() { File = file, FileLength = fileLength, Stream = stream, ByteOffset = byteOffset };
+ fileMap.Add(filePath, fileInfo);
+ byteOffset = Tools.Align(byteOffset + fileLength);
+ }
+
+ return fileInfo;
+ }
+
+ JsonNode root;
+ using (var stream = File.OpenRead(inputFilePath))
+ {
+ root = JsonNode.Parse(stream)!;
+ }
+
+ var buffers = root.GetArray("buffers");
+ var bufferViews = root.GetArray("bufferViews");
+
+ if (buffers != null)
+ {
+ for (var index = buffers.Count - 1; index >= 0; index--)
+ {
+ var buffer = buffers[index];
+ if (buffer == null)
+ {
+ continue;
+ }
+
+ var filePath = buffer.GetLocalPath("uri", baseUri);
+ if (filePath == null)
+ {
+ continue;
+ }
+
+ var fileInfo = AddFile(filePath);
+
+ if (bufferViews != null)
+ {
+ foreach (var bufferView in bufferViews)
+ {
+ if (bufferView == null)
+ {
+ continue;
+ }
+
+ var bufferIndex = bufferView.GetInt("buffer");
+ if (bufferIndex == -1)
+ {
+ continue;
+ }
+
+ if (bufferIndex == index)
+ {
+ bufferView.Remove("buffer");
+ bufferView.SetInt("byteOffset", fileInfo.ByteOffset + bufferView.GetInt("byteOffset", 0), 0);
+ }
+ else if (bufferIndex > index)
+ {
+ bufferView["buffer"] = bufferIndex - 1;
+ }
+ }
+ }
+
+ buffers.RemoveAt(index);
+ }
+ }
+
+ void ProcessArray(JsonArray array)
+ {
+ foreach (var element in array)
+ {
+ if (element == null)
+ {
+ continue;
+ }
+
+ var filePath = element.GetLocalPath("uri", baseUri);
+ if (filePath == null)
+ {
+ continue;
+ }
+
+ var fileInfo = AddFile(filePath);
+
+ if (bufferViews == null)
+ {
+ bufferViews = [];
+ root["bufferViews"] = bufferViews;
+ }
+
+ element.Remove("uri");
+ element["bufferView"] = bufferViews.Count;
+ element["mimeType"] = MimeType.FromFileExtension(Path.GetExtension(filePath));
+
+ JsonNode bufferView = new JsonObject();
+ bufferView.SetInt("byteOffset", fileInfo.ByteOffset, 0);
+ bufferView["byteLength"] = fileInfo.FileLength;
+ bufferViews.Add(bufferView);
+ }
+ }
+
+ var images = root.GetArray("images");
+ if (images != null)
+ {
+ ProcessArray(images);
+ }
+
+ // EXT_lights_ies
+ var lights = root["extensions"]?.AsObject()["EXT_lights_ies"]?.AsObject().GetArray("lights");
+ if (lights != null)
+ {
+ ProcessArray(lights);
+ }
+
+ if (fileMap.Count != 0)
+ {
+ if (buffers == null)
+ {
+ buffers = [];
+ root["buffers"] = buffers;
+ }
+
+ JsonNode buffer = new JsonObject();
+ buffer["byteLength"] = byteOffset;
+ buffers.Insert(0, buffer);
+
+ if (bufferViews != null)
+ {
+ foreach (var bufferView in bufferViews)
+ {
+ if (bufferView == null)
+ {
+ continue;
+ }
+
+ var bufferIndex = bufferView.GetInt("buffer");
+ bufferView["buffer"] = bufferIndex + 1;
+ }
+ }
+ }
+
+ var outputDirectoryPath = Path.GetDirectoryName(outputFilePath);
+ if (outputDirectoryPath != null)
+ {
+ Directory.CreateDirectory(outputDirectoryPath);
+ }
+
+ using (var fileStream = File.Create(outputFilePath))
+ using (var binaryWriter = new BinaryWriter(fileStream))
+ {
+ binaryWriter.Write(Binary.Magic);
+ binaryWriter.Write(Binary.Version);
+
+ var chunksPosition = binaryWriter.BaseStream.Position;
+
+ binaryWriter.Write(0U); // length
+
+ var jsonChunkPosition = binaryWriter.BaseStream.Position;
+
+ binaryWriter.Write(0U); // json chunk length
+ binaryWriter.Write(Binary.ChunkFormatJson);
+
+ using (var jsonTextWriter = new Utf8JsonWriter(binaryWriter.BaseStream))
+ {
+ root.WriteTo(jsonTextWriter);
+ }
+
+ binaryWriter.BaseStream.Align(0x20);
+ var jsonChunkLength = checked((uint)(binaryWriter.BaseStream.Length - jsonChunkPosition)) - Binary.ChunkHeaderLength;
+
+ binaryWriter.BaseStream.Seek(jsonChunkPosition, SeekOrigin.Begin);
+ binaryWriter.Write(jsonChunkLength);
+
+ if (fileMap.Count != 0)
+ {
+ binaryWriter.BaseStream.Seek(0, SeekOrigin.End);
+ var binChunkPosition = binaryWriter.BaseStream.Position;
+
+ binaryWriter.Write(0); // bin chunk length
+ binaryWriter.Write(Binary.ChunkFormatBin);
+
+ foreach (var value in fileMap.Values)
+ {
+ binaryWriter.BaseStream.Align();
+ value.Stream.CopyTo(binaryWriter.BaseStream);
+ }
+
+ binaryWriter.BaseStream.Align(0x20);
+ var binChunkLength = checked((uint)(binaryWriter.BaseStream.Length - binChunkPosition)) - Binary.ChunkHeaderLength;
+
+ binaryWriter.BaseStream.Seek(binChunkPosition, SeekOrigin.Begin);
+ binaryWriter.Write(binChunkLength);
+ }
+
+ var length = checked((uint)binaryWriter.BaseStream.Length);
+
+ binaryWriter.BaseStream.Seek(chunksPosition, SeekOrigin.Begin);
+ binaryWriter.Write(length);
+ }
+
+ foreach (var value in fileMap.Values)
+ {
+ value.Stream.Dispose();
+ value.File.Dispose();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/glTF/Core/Tools.cs b/Source/Core/Tools.cs
similarity index 89%
rename from Source/glTF/Core/Tools.cs
rename to Source/Core/Tools.cs
index 0558708..3824676 100644
--- a/Source/glTF/Core/Tools.cs
+++ b/Source/Core/Tools.cs
@@ -1,6 +1,4 @@
-using System.IO;
-
-namespace glTF
+namespace glTF
{
internal static class Tools
{
diff --git a/Source/glTF/Core/Unpacker.cs b/Source/Core/Unpacker.cs
similarity index 63%
rename from Source/glTF/Core/Unpacker.cs
rename to Source/Core/Unpacker.cs
index 2be4786..e8748e6 100644
--- a/Source/glTF/Core/Unpacker.cs
+++ b/Source/Core/Unpacker.cs
@@ -1,14 +1,10 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.IO.MemoryMappedFiles;
-using System.Linq;
+using System.IO.MemoryMappedFiles;
using System.Text.Json;
using System.Text.Json.Nodes;
namespace glTF
{
- internal class Unpacker
+ public class Unpacker
{
private readonly string inputFilePath;
private readonly string inputDirectoryPath;
@@ -19,7 +15,7 @@ internal class Unpacker
private Unpacker(string inputFilePath, string outputDirectoryPath, bool unpackImages)
{
this.inputFilePath = inputFilePath;
- this.inputDirectoryPath = Path.GetDirectoryName(this.inputFilePath);
+ this.inputDirectoryPath = Path.GetDirectoryName(this.inputFilePath)!;
this.inputFileName = Path.GetFileNameWithoutExtension(this.inputFilePath);
this.outputDirectoryPath = outputDirectoryPath;
this.unpackImages = unpackImages;
@@ -60,7 +56,7 @@ private void Unpack()
using (var jsonStream = GetJsonChunk(binaryReader, memoryMappedFile))
{
- jsonNode = JsonNode.Parse(jsonStream);
+ jsonNode = JsonNode.Parse(jsonStream)!;
}
var binChunkOffset = FindBinChunk(binaryReader);
@@ -68,6 +64,7 @@ private void Unpack()
this.ProcessBinFiles(jsonNode, memoryMappedFile, binChunkOffset);
}
+ Directory.CreateDirectory(this.outputDirectoryPath);
using (var fileStream = File.Create(Path.Combine(this.outputDirectoryPath, $"{this.inputFileName}.gltf")))
using (var jsonWriter = new Utf8JsonWriter(fileStream, new() { Indented = true }))
{
@@ -75,7 +72,7 @@ private void Unpack()
}
}
- private static Stream GetJsonChunk(BinaryReader binaryReader, MemoryMappedFile memoryMappedFile)
+ private static MemoryMappedViewStream GetJsonChunk(BinaryReader binaryReader, MemoryMappedFile memoryMappedFile)
{
var chunkLength = binaryReader.ReadUInt32();
var chunkFormat = binaryReader.ReadUInt32();
@@ -119,12 +116,11 @@ private static long FindBinChunk(BinaryReader binaryReader)
return -1;
}
- private void ProcessImageFiles(JsonNode jsonNode, MemoryMappedFile memoryMappedFile, long binChunkOffset)
+ private void ProcessImageFiles(JsonNode node, MemoryMappedFile memoryMappedFile, long binChunkOffset)
{
- var root = jsonNode.AsObject();
- var accessors = root["accessors"]?.AsArray();
- var bufferViews = root["bufferViews"]?.AsArray();
- var images = root["images"]?.AsArray();
+ var accessors = node.GetArray("accessors");
+ var bufferViews = node.GetArray("bufferViews");
+ var images = node.GetArray("images");
var bufferViewIndicesToRemove = new List();
@@ -132,51 +128,63 @@ private void ProcessImageFiles(JsonNode jsonNode, MemoryMappedFile memoryMappedF
{
for (var index = 0; index < images.Count; index++)
{
- var image = images[index]?.AsObject();
- if (image != null)
+ var image = images[index];
+ if (image == null)
{
- var uri = image["uri"]?.GetValue();
- if (Uri.IsWellFormedUriString(uri, UriKind.Relative))
- {
- var sourceFilePath = Path.Combine(this.inputDirectoryPath, Path.GetFileName(uri));
- var fileExtension = Path.GetExtension(uri);
- var fileName = $"{this.inputFileName}_image{index}.{fileExtension}";
+ continue;
+ }
- if (File.Exists(sourceFilePath))
- {
- var destinationFilePath = Path.Combine(this.outputDirectoryPath, fileName);
- File.Copy(sourceFilePath, destinationFilePath, true);
- }
+ var uri = image.GetString("uri");
+ if (uri != null && Uri.IsWellFormedUriString(uri, UriKind.Relative))
+ {
+ var sourceFilePath = Path.Combine(this.inputDirectoryPath, uri.Replace('/', Path.DirectorySeparatorChar));
+ var fileExtension = Path.GetExtension(uri);
+ var fileName = $"{this.inputFileName}_image{index}.{fileExtension}";
- image["uri"] = fileName;
+ if (File.Exists(sourceFilePath))
+ {
+ var destinationFilePath = Path.Combine(this.outputDirectoryPath, fileName);
+ File.Copy(sourceFilePath, destinationFilePath, true);
}
- else if (this.unpackImages && bufferViews != null && binChunkOffset != -1)
+
+ image["uri"] = fileName;
+ }
+ else if (this.unpackImages && bufferViews != null && binChunkOffset != -1)
+ {
+ var bufferViewIndex = image.GetInt("bufferView");
+ if (bufferViewIndex != -1)
{
- var bufferViewIndex = (int)image["bufferView"];
var bufferView = bufferViews[bufferViewIndex];
- var bufferIndex = (int)bufferView["buffer"];
- if (bufferIndex == 0)
+ if (bufferView != null)
{
- var mimeType = (string)image["mimeType"];
- var fileExtension = MimeType.ToFileExtension(mimeType);
- var fileName = $"{this.inputFileName}_image{index}{fileExtension}";
-
- using (var fileStream = File.Create(Path.Combine(this.outputDirectoryPath, fileName)))
+ var bufferIndex = bufferView.GetInt("buffer");
+ if (bufferIndex == 0)
{
- var byteOffset = (long?)bufferView["byteOffset"] ?? 0;
- var byteLength = (int)bufferView["byteLength"];
-
- using (var viewStream = memoryMappedFile.CreateViewStream(binChunkOffset + byteOffset, byteLength, MemoryMappedFileAccess.Read))
+ var byteOffset = bufferView.GetInt("byteOffset", 0);
+ var byteLength = bufferView.GetInt("byteLength", -1);
+ if (byteLength != -1)
{
- viewStream.CopyTo(fileStream);
- }
- }
+ var mimeType = image.GetString("mimeType");
+ var fileExtension = MimeType.ToFileExtension(mimeType);
+ var fileName = $"{this.inputFileName}_image{index}{fileExtension}";
+
+ Directory.CreateDirectory(this.outputDirectoryPath);
+
+ using (var fileStream = File.Create(Path.Combine(this.outputDirectoryPath, fileName)))
+ {
+ using (var viewStream = memoryMappedFile.CreateViewStream(binChunkOffset + byteOffset, byteLength, MemoryMappedFileAccess.Read))
+ {
+ viewStream.CopyTo(fileStream);
+ }
+ }
- image.Remove("bufferView");
- image.Remove("mimeType");
- image["uri"] = fileName;
+ image.Remove("bufferView");
+ image.Remove("mimeType");
+ image["uri"] = fileName;
- bufferViewIndicesToRemove.Add(bufferViewIndex);
+ bufferViewIndicesToRemove.Add(bufferViewIndex);
+ }
+ }
}
}
}
@@ -203,7 +211,7 @@ private void ProcessImageFiles(JsonNode jsonNode, MemoryMappedFile memoryMappedF
if (newIndex == 0)
{
- root.Remove("bufferViews");
+ node.Remove("bufferViews");
}
else
{
@@ -218,10 +226,15 @@ private void ProcessImageFiles(JsonNode jsonNode, MemoryMappedFile memoryMappedF
{
foreach (var accessor in accessors)
{
- var bufferViewIndex = (int?)accessor["bufferView"];
- if (bufferViewIndex.HasValue)
+ if (accessor == null)
+ {
+ continue;
+ }
+
+ var bufferViewIndex = accessor.GetInt("bufferView");
+ if (bufferViewIndex != -1)
{
- if (bufferViewIndexMap.TryGetValue(bufferViewIndex.Value, out int newBufferViewIndex))
+ if (bufferViewIndexMap.TryGetValue(bufferViewIndex, out int newBufferViewIndex))
{
accessor["bufferView"] = newBufferViewIndex;
}
@@ -230,19 +243,23 @@ private void ProcessImageFiles(JsonNode jsonNode, MemoryMappedFile memoryMappedF
}
}
- private void ProcessBinFiles(JsonNode jsonNode, MemoryMappedFile memoryMappedFile, long binChunkOffset)
+ private void ProcessBinFiles(JsonNode node, MemoryMappedFile memoryMappedFile, long binChunkOffset)
{
- var root = jsonNode.AsObject();
- var buffers = root["buffers"]?.AsArray();
+ var buffers = node.GetArray("buffers");
if (buffers != null)
{
for (var index = 0; index < buffers.Count; index++)
{
var buffer = buffers[index];
- var uri = (string)buffer["uri"];
+ if (buffer == null)
+ {
+ continue;
+ }
+
+ var uri = buffer.GetString("uri");
if (uri != null && Uri.IsWellFormedUriString(uri, UriKind.Relative))
{
- var sourceFilePath = Path.Combine(this.inputDirectoryPath, Path.GetFileName(uri));
+ var sourceFilePath = Path.Combine(this.inputDirectoryPath, uri.Replace('/', Path.DirectorySeparatorChar));
var fileName = $"{this.inputFileName}{index}.bin";
if (File.Exists(sourceFilePath))
@@ -255,29 +272,36 @@ private void ProcessBinFiles(JsonNode jsonNode, MemoryMappedFile memoryMappedFil
}
}
- var bufferViews = root["bufferViews"]?.AsArray();
+ var firstBuffer = buffers[0];
+ if (firstBuffer == null)
+ {
+ return;
+ }
+
+ var bufferViews = node.GetArray("bufferViews");
if (bufferViews != null && binChunkOffset != -1)
{
- if (bufferViews.Any(bufferView => (int)bufferView["buffer"] == 0))
+ if (bufferViews.Any(bufferView => bufferView != null && bufferView.GetInt("buffer") == 0))
{
+ Directory.CreateDirectory(this.outputDirectoryPath);
+
var binFileName = $"{this.inputFileName}.bin";
var binFilePath = Path.Combine(this.outputDirectoryPath, binFileName);
using (var fileStream = File.Create(binFilePath))
{
- var buffer = buffers[0];
- buffer["uri"] = binFileName;
+ firstBuffer["uri"] = binFileName;
if (this.unpackImages)
{
foreach (var bufferView in bufferViews)
{
- if ((int)bufferView["buffer"] == 0)
+ if (bufferView != null && bufferView.GetInt("buffer") == 0)
{
fileStream.Align();
var outputOffset = fileStream.Position;
- var byteOffset = (long?)bufferView["byteOffset"] ?? 0;
- var byteLength = (int)bufferView["byteLength"];
+ var byteOffset = bufferView.GetInt("byteOffset", 0);
+ var byteLength = bufferView.GetInt("byteLength", 0);
using (var viewStream = memoryMappedFile.CreateViewStream(binChunkOffset + byteOffset, byteLength, MemoryMappedFileAccess.Read))
{
viewStream.CopyTo(fileStream, byteLength);
@@ -287,11 +311,11 @@ private void ProcessBinFiles(JsonNode jsonNode, MemoryMappedFile memoryMappedFil
}
}
- buffer["byteLength"] = fileStream.Length;
+ firstBuffer["byteLength"] = fileStream.Length;
}
else
{
- var byteLength = (int)buffer["byteLength"];
+ var byteLength = firstBuffer.GetInt("byteLength", 0);
using (var viewStream = memoryMappedFile.CreateViewStream(binChunkOffset, byteLength, MemoryMappedFileAccess.Read))
{
viewStream.CopyTo(fileStream, byteLength);
@@ -302,12 +326,20 @@ private void ProcessBinFiles(JsonNode jsonNode, MemoryMappedFile memoryMappedFil
else
{
buffers.RemoveAt(0);
- root["buffers"] = buffers;
+ node["buffers"] = buffers;
foreach (var bufferView in bufferViews)
{
- var bufferIndex = (int)bufferView["buffer"];
- bufferView["buffer"] = bufferIndex - 1;
+ if (bufferView == null)
+ {
+ continue;
+ }
+
+ var bufferIndex = bufferView.GetInt("buffer", -1);
+ if (bufferIndex > 0)
+ {
+ bufferView["buffer"] = bufferIndex - 1;
+ }
}
}
}
diff --git a/Source/Package/Package.appxmanifest b/Source/Package/Package.appxmanifest
index a9423fe..2489b17 100644
--- a/Source/Package/Package.appxmanifest
+++ b/Source/Package/Package.appxmanifest
@@ -13,7 +13,7 @@
+ Version="0.4.3.0" />
diff --git a/Source/Package/Package.wapproj b/Source/Package/Package.wapproj
index d3d2751..53ec9b6 100644
--- a/Source/Package/Package.wapproj
+++ b/Source/Package/Package.wapproj
@@ -36,7 +36,7 @@
13b11981-bd9f-4769-b89e-84271568c258
- 10.0.26100.0
+ 10.0.22621.010.0.17763.0net8.0-windows$(TargetPlatformVersion);$(AssetTargetFallback)en-US
diff --git a/Source/Tests/.gitattributes b/Source/Tests/.gitattributes
new file mode 100644
index 0000000..a172cd4
--- /dev/null
+++ b/Source/Tests/.gitattributes
@@ -0,0 +1,4 @@
+External/**/*.exe filter=lfs diff=lfs merge=lfs -text
+Assets/**/*.glb filter=lfs diff=lfs merge=lfs -text
+Assets/**/*.png filter=lfs diff=lfs merge=lfs -text
+Assets/**/*.jpg filter=lfs diff=lfs merge=lfs -text
diff --git a/Source/Tests/Assets/Box/LICENSE.md b/Source/Tests/Assets/Box/LICENSE.md
new file mode 100644
index 0000000..8478783
--- /dev/null
+++ b/Source/Tests/Assets/Box/LICENSE.md
@@ -0,0 +1,15 @@
+# LICENSE file for the model: Box
+
+All files in this directory tree are licensed as indicated below.
+
+* All files directly associated with the model including all text, image and binary files:
+
+ * [CC BY 4.0 International]("https://creativecommons.org/licenses/by/4.0/legalcode") [SPDX license identifier: "CC-BY-4.0"]
+
+* This file and all other metadocumentation files including "metadata.json":
+
+ * [Creative Commons Attribtution 4.0 International]("https://creativecommons.org/licenses/by/4.0/legalcode") [SPDX license identifier: "CC-BY-4.0"]
+
+Full license text of these licenses are available at the links above
+
+#### Generated by modelmetadata
\ No newline at end of file
diff --git a/Source/Tests/Assets/Box/glTF-Binary/Box.glb b/Source/Tests/Assets/Box/glTF-Binary/Box.glb
new file mode 100644
index 0000000..5445f06
--- /dev/null
+++ b/Source/Tests/Assets/Box/glTF-Binary/Box.glb
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ed52f7192b8311d700ac0ce80644e3852cd01537e4d62241b9acba023da3d54e
+size 1664
diff --git a/Source/Tests/Assets/Box/glTF-Embedded/Box.gltf b/Source/Tests/Assets/Box/glTF-Embedded/Box.gltf
new file mode 100644
index 0000000..ea0e8ad
--- /dev/null
+++ b/Source/Tests/Assets/Box/glTF-Embedded/Box.gltf
@@ -0,0 +1,142 @@
+{
+ "asset": {
+ "generator": "COLLADA2GLTF",
+ "version": "2.0"
+ },
+ "scene": 0,
+ "scenes": [
+ {
+ "nodes": [
+ 0
+ ]
+ }
+ ],
+ "nodes": [
+ {
+ "children": [
+ 1
+ ],
+ "matrix": [
+ 1.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ -1.0,
+ 0.0,
+ 0.0,
+ 1.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 1.0
+ ]
+ },
+ {
+ "mesh": 0
+ }
+ ],
+ "meshes": [
+ {
+ "primitives": [
+ {
+ "attributes": {
+ "NORMAL": 1,
+ "POSITION": 2
+ },
+ "indices": 0,
+ "mode": 4,
+ "material": 0
+ }
+ ],
+ "name": "Mesh"
+ }
+ ],
+ "accessors": [
+ {
+ "bufferView": 0,
+ "byteOffset": 0,
+ "componentType": 5123,
+ "count": 36,
+ "max": [
+ 23
+ ],
+ "min": [
+ 0
+ ],
+ "type": "SCALAR"
+ },
+ {
+ "bufferView": 1,
+ "byteOffset": 0,
+ "componentType": 5126,
+ "count": 24,
+ "max": [
+ 1.0,
+ 1.0,
+ 1.0
+ ],
+ "min": [
+ -1.0,
+ -1.0,
+ -1.0
+ ],
+ "type": "VEC3"
+ },
+ {
+ "bufferView": 1,
+ "byteOffset": 288,
+ "componentType": 5126,
+ "count": 24,
+ "max": [
+ 0.5,
+ 0.5,
+ 0.5
+ ],
+ "min": [
+ -0.5,
+ -0.5,
+ -0.5
+ ],
+ "type": "VEC3"
+ }
+ ],
+ "materials": [
+ {
+ "pbrMetallicRoughness": {
+ "baseColorFactor": [
+ 0.800000011920929,
+ 0.0,
+ 0.0,
+ 1.0
+ ],
+ "metallicFactor": 0.0
+ },
+ "name": "Red"
+ }
+ ],
+ "bufferViews": [
+ {
+ "buffer": 0,
+ "byteOffset": 576,
+ "byteLength": 72,
+ "target": 34963
+ },
+ {
+ "buffer": 0,
+ "byteOffset": 0,
+ "byteLength": 576,
+ "byteStride": 12,
+ "target": 34962
+ }
+ ],
+ "buffers": [
+ {
+ "byteLength": 648,
+ "uri": "data:application/octet-stream;base64,AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAvwAAAL8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAL8AAAA/AAAAPwAAAL8AAAC/AAAAvwAAAL8AAAC/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAPwAAAD8AAAC/AAAAPwAAAL8AAAC/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAvwAAAD8AAAC/AAAAPwAAAD8AAAC/AAAAvwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAPwAAAL8AAAC/AAAAPwAAAD8AAAC/AAABAAIAAwACAAEABAAFAAYABwAGAAUACAAJAAoACwAKAAkADAANAA4ADwAOAA0AEAARABIAEwASABEAFAAVABYAFwAWABUA"
+ }
+ ]
+}
diff --git a/Source/Tests/Assets/Box/glTF/Box.gltf b/Source/Tests/Assets/Box/glTF/Box.gltf
new file mode 100644
index 0000000..7f603f0
--- /dev/null
+++ b/Source/Tests/Assets/Box/glTF/Box.gltf
@@ -0,0 +1,142 @@
+{
+ "asset": {
+ "generator": "COLLADA2GLTF",
+ "version": "2.0"
+ },
+ "scene": 0,
+ "scenes": [
+ {
+ "nodes": [
+ 0
+ ]
+ }
+ ],
+ "nodes": [
+ {
+ "children": [
+ 1
+ ],
+ "matrix": [
+ 1.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ -1.0,
+ 0.0,
+ 0.0,
+ 1.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 1.0
+ ]
+ },
+ {
+ "mesh": 0
+ }
+ ],
+ "meshes": [
+ {
+ "primitives": [
+ {
+ "attributes": {
+ "NORMAL": 1,
+ "POSITION": 2
+ },
+ "indices": 0,
+ "mode": 4,
+ "material": 0
+ }
+ ],
+ "name": "Mesh"
+ }
+ ],
+ "accessors": [
+ {
+ "bufferView": 0,
+ "byteOffset": 0,
+ "componentType": 5123,
+ "count": 36,
+ "max": [
+ 23
+ ],
+ "min": [
+ 0
+ ],
+ "type": "SCALAR"
+ },
+ {
+ "bufferView": 1,
+ "byteOffset": 0,
+ "componentType": 5126,
+ "count": 24,
+ "max": [
+ 1.0,
+ 1.0,
+ 1.0
+ ],
+ "min": [
+ -1.0,
+ -1.0,
+ -1.0
+ ],
+ "type": "VEC3"
+ },
+ {
+ "bufferView": 1,
+ "byteOffset": 288,
+ "componentType": 5126,
+ "count": 24,
+ "max": [
+ 0.5,
+ 0.5,
+ 0.5
+ ],
+ "min": [
+ -0.5,
+ -0.5,
+ -0.5
+ ],
+ "type": "VEC3"
+ }
+ ],
+ "materials": [
+ {
+ "pbrMetallicRoughness": {
+ "baseColorFactor": [
+ 0.800000011920929,
+ 0.0,
+ 0.0,
+ 1.0
+ ],
+ "metallicFactor": 0.0
+ },
+ "name": "Red"
+ }
+ ],
+ "bufferViews": [
+ {
+ "buffer": 0,
+ "byteOffset": 576,
+ "byteLength": 72,
+ "target": 34963
+ },
+ {
+ "buffer": 0,
+ "byteOffset": 0,
+ "byteLength": 576,
+ "byteStride": 12,
+ "target": 34962
+ }
+ ],
+ "buffers": [
+ {
+ "byteLength": 648,
+ "uri": "Box0.bin"
+ }
+ ]
+}
diff --git a/Source/Tests/Assets/Box/glTF/Box0.bin b/Source/Tests/Assets/Box/glTF/Box0.bin
new file mode 100644
index 0000000..d7798ab
Binary files /dev/null and b/Source/Tests/Assets/Box/glTF/Box0.bin differ
diff --git a/Source/Tests/Assets/BoxTextured/LICENSE.md b/Source/Tests/Assets/BoxTextured/LICENSE.md
new file mode 100644
index 0000000..766c3c9
--- /dev/null
+++ b/Source/Tests/Assets/BoxTextured/LICENSE.md
@@ -0,0 +1,17 @@
+# LICENSE file for the model: Box Textured
+
+All files in this directory tree are licensed as indicated below.
+
+* All files directly associated with the model including all text, image and binary files:
+
+ * [CC-BY 4.0 International with Trademark Limitations]("") [SPDX license identifier: "LicenseRef-CC-BY-TM"]
+
+ * [Cesium Trademark or Logo]("") [SPDX license identifier: "LicenseRef-LegalMark-Cesium"]
+
+* This file and all other metadocumentation files including "metadata.json":
+
+ * [Creative Commons Attribtution 4.0 International]("https://creativecommons.org/licenses/by/4.0/legalcode") [SPDX license identifier: "CC-BY-4.0"]
+
+Full license text of these licenses are available at the links above
+
+#### Generated by modelmetadata
\ No newline at end of file
diff --git a/Source/Tests/Assets/BoxTextured/glTF-Binary/BoxTextured.glb b/Source/Tests/Assets/BoxTextured/glTF-Binary/BoxTextured.glb
new file mode 100644
index 0000000..6524740
--- /dev/null
+++ b/Source/Tests/Assets/BoxTextured/glTF-Binary/BoxTextured.glb
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2d055d2d56a492d1b9302de6e733046b625cf51e5f2a3090bd3a8c11acc93c17
+size 6540
diff --git a/Source/Tests/Assets/BoxTextured/glTF-Embedded/BoxTextured.gltf b/Source/Tests/Assets/BoxTextured/glTF-Embedded/BoxTextured.gltf
new file mode 100644
index 0000000..70fb0a7
--- /dev/null
+++ b/Source/Tests/Assets/BoxTextured/glTF-Embedded/BoxTextured.gltf
@@ -0,0 +1,181 @@
+{
+ "asset": {
+ "generator": "COLLADA2GLTF",
+ "version": "2.0"
+ },
+ "scene": 0,
+ "scenes": [
+ {
+ "nodes": [
+ 0
+ ]
+ }
+ ],
+ "nodes": [
+ {
+ "children": [
+ 1
+ ],
+ "matrix": [
+ 1.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ -1.0,
+ 0.0,
+ 0.0,
+ 1.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 1.0
+ ]
+ },
+ {
+ "mesh": 0
+ }
+ ],
+ "meshes": [
+ {
+ "primitives": [
+ {
+ "attributes": {
+ "NORMAL": 1,
+ "POSITION": 2,
+ "TEXCOORD_0": 3
+ },
+ "indices": 0,
+ "mode": 4,
+ "material": 0
+ }
+ ],
+ "name": "Mesh"
+ }
+ ],
+ "accessors": [
+ {
+ "bufferView": 0,
+ "byteOffset": 0,
+ "componentType": 5123,
+ "count": 36,
+ "max": [
+ 23
+ ],
+ "min": [
+ 0
+ ],
+ "type": "SCALAR"
+ },
+ {
+ "bufferView": 1,
+ "byteOffset": 0,
+ "componentType": 5126,
+ "count": 24,
+ "max": [
+ 1.0,
+ 1.0,
+ 1.0
+ ],
+ "min": [
+ -1.0,
+ -1.0,
+ -1.0
+ ],
+ "type": "VEC3"
+ },
+ {
+ "bufferView": 1,
+ "byteOffset": 288,
+ "componentType": 5126,
+ "count": 24,
+ "max": [
+ 0.5,
+ 0.5,
+ 0.5
+ ],
+ "min": [
+ -0.5,
+ -0.5,
+ -0.5
+ ],
+ "type": "VEC3"
+ },
+ {
+ "bufferView": 2,
+ "byteOffset": 0,
+ "componentType": 5126,
+ "count": 24,
+ "max": [
+ 6.0,
+ 1.0
+ ],
+ "min": [
+ 0.0,
+ 0.0
+ ],
+ "type": "VEC2"
+ }
+ ],
+ "materials": [
+ {
+ "pbrMetallicRoughness": {
+ "baseColorTexture": {
+ "index": 0
+ },
+ "metallicFactor": 0.0
+ },
+ "name": "Texture"
+ }
+ ],
+ "textures": [
+ {
+ "sampler": 0,
+ "source": 0
+ }
+ ],
+ "images": [
+ {
+ "uri": "data:image/png;base64,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"
+ }
+ ],
+ "samplers": [
+ {
+ "magFilter": 9729,
+ "minFilter": 9986,
+ "wrapS": 10497,
+ "wrapT": 10497
+ }
+ ],
+ "bufferViews": [
+ {
+ "buffer": 0,
+ "byteOffset": 768,
+ "byteLength": 72,
+ "target": 34963
+ },
+ {
+ "buffer": 0,
+ "byteOffset": 0,
+ "byteLength": 576,
+ "byteStride": 12,
+ "target": 34962
+ },
+ {
+ "buffer": 0,
+ "byteOffset": 576,
+ "byteLength": 192,
+ "byteStride": 8,
+ "target": 34962
+ }
+ ],
+ "buffers": [
+ {
+ "byteLength": 840,
+ "uri": "data:application/octet-stream;base64,AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAvwAAAL8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAPwAAAD8AAAC/AAAAPwAAAL8AAAC/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAvwAAAD8AAAC/AAAAPwAAAD8AAAC/AAAAPwAAAL8AAAA/AAAAvwAAAL8AAAA/AAAAPwAAAL8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAPwAAAL8AAAC/AAAAPwAAAD8AAAC/AADAQAAAAAAAAKBAAAAAAAAAwED+/38/AACgQP7/fz8AAIBAAAAAAAAAoEAAAAAAAACAQAAAgD8AAKBAAACAPwAAAEAAAAAAAACAPwAAAAAAAABAAACAPwAAgD8AAIA/AABAQAAAAAAAAIBAAAAAAAAAQEAAAIA/AACAQAAAgD8AAEBAAAAAAAAAAEAAAAAAAABAQAAAgD8AAABAAACAPwAAAAAAAAAAAAAAAP7/fz8AAIA/AAAAAAAAgD/+/38/AAABAAIAAwACAAEABAAFAAYABwAGAAUACAAJAAoACwAKAAkADAANAA4ADwAOAA0AEAARABIAEwASABEAFAAVABYAFwAWABUA"
+ }
+ ]
+}
diff --git a/Source/Tests/Assets/BoxTextured/glTF/BoxTextured.gltf b/Source/Tests/Assets/BoxTextured/glTF/BoxTextured.gltf
new file mode 100644
index 0000000..eff658f
--- /dev/null
+++ b/Source/Tests/Assets/BoxTextured/glTF/BoxTextured.gltf
@@ -0,0 +1,181 @@
+{
+ "asset": {
+ "generator": "COLLADA2GLTF",
+ "version": "2.0"
+ },
+ "scene": 0,
+ "scenes": [
+ {
+ "nodes": [
+ 0
+ ]
+ }
+ ],
+ "nodes": [
+ {
+ "children": [
+ 1
+ ],
+ "matrix": [
+ 1.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ -1.0,
+ 0.0,
+ 0.0,
+ 1.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 1.0
+ ]
+ },
+ {
+ "mesh": 0
+ }
+ ],
+ "meshes": [
+ {
+ "primitives": [
+ {
+ "attributes": {
+ "NORMAL": 1,
+ "POSITION": 2,
+ "TEXCOORD_0": 3
+ },
+ "indices": 0,
+ "mode": 4,
+ "material": 0
+ }
+ ],
+ "name": "Mesh"
+ }
+ ],
+ "accessors": [
+ {
+ "bufferView": 0,
+ "byteOffset": 0,
+ "componentType": 5123,
+ "count": 36,
+ "max": [
+ 23
+ ],
+ "min": [
+ 0
+ ],
+ "type": "SCALAR"
+ },
+ {
+ "bufferView": 1,
+ "byteOffset": 0,
+ "componentType": 5126,
+ "count": 24,
+ "max": [
+ 1.0,
+ 1.0,
+ 1.0
+ ],
+ "min": [
+ -1.0,
+ -1.0,
+ -1.0
+ ],
+ "type": "VEC3"
+ },
+ {
+ "bufferView": 1,
+ "byteOffset": 288,
+ "componentType": 5126,
+ "count": 24,
+ "max": [
+ 0.5,
+ 0.5,
+ 0.5
+ ],
+ "min": [
+ -0.5,
+ -0.5,
+ -0.5
+ ],
+ "type": "VEC3"
+ },
+ {
+ "bufferView": 2,
+ "byteOffset": 0,
+ "componentType": 5126,
+ "count": 24,
+ "max": [
+ 6.0,
+ 1.0
+ ],
+ "min": [
+ 0.0,
+ 0.0
+ ],
+ "type": "VEC2"
+ }
+ ],
+ "materials": [
+ {
+ "pbrMetallicRoughness": {
+ "baseColorTexture": {
+ "index": 0
+ },
+ "metallicFactor": 0.0
+ },
+ "name": "Texture"
+ }
+ ],
+ "textures": [
+ {
+ "sampler": 0,
+ "source": 0
+ }
+ ],
+ "images": [
+ {
+ "uri": "CesiumLogoFlat.png"
+ }
+ ],
+ "samplers": [
+ {
+ "magFilter": 9729,
+ "minFilter": 9986,
+ "wrapS": 10497,
+ "wrapT": 10497
+ }
+ ],
+ "bufferViews": [
+ {
+ "buffer": 0,
+ "byteOffset": 768,
+ "byteLength": 72,
+ "target": 34963
+ },
+ {
+ "buffer": 0,
+ "byteOffset": 0,
+ "byteLength": 576,
+ "byteStride": 12,
+ "target": 34962
+ },
+ {
+ "buffer": 0,
+ "byteOffset": 576,
+ "byteLength": 192,
+ "byteStride": 8,
+ "target": 34962
+ }
+ ],
+ "buffers": [
+ {
+ "byteLength": 840,
+ "uri": "BoxTextured0.bin"
+ }
+ ]
+}
diff --git a/Source/Tests/Assets/BoxTextured/glTF/BoxTextured0.bin b/Source/Tests/Assets/BoxTextured/glTF/BoxTextured0.bin
new file mode 100644
index 0000000..d2a7355
Binary files /dev/null and b/Source/Tests/Assets/BoxTextured/glTF/BoxTextured0.bin differ
diff --git a/Source/Tests/Assets/BoxTextured/glTF/CesiumLogoFlat.png b/Source/Tests/Assets/BoxTextured/glTF/CesiumLogoFlat.png
new file mode 100644
index 0000000..1674d5e
--- /dev/null
+++ b/Source/Tests/Assets/BoxTextured/glTF/CesiumLogoFlat.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:89b210e0ba3c0a1ac10c93f8881b62e24f220731643215a68568a72381d3313e
+size 4333
diff --git a/Source/Tests/Assets/LightsIES/Example.gltf b/Source/Tests/Assets/LightsIES/Example.gltf
new file mode 100644
index 0000000..bd60730
--- /dev/null
+++ b/Source/Tests/Assets/LightsIES/Example.gltf
@@ -0,0 +1,164 @@
+{
+ "asset": {
+ "version": "2.0"
+ },
+ "scene": 0,
+ "scenes": [
+ {
+ "nodes": [
+ 0
+ ]
+ }
+ ],
+ "nodes": [
+ {
+ "children": [
+ 1,
+ 2
+ ]
+ },
+ {
+ "mesh": 0,
+ "scale": [
+ 7.5,
+ 7.5,
+ 0.1
+ ]
+ },
+ {
+ "extensions": {
+ "EXT_lights_ies": {
+ "light": 0,
+ "color": [
+ 0.181,
+ 0.485,
+ 0
+ ]
+ }
+ },
+ "translation": [
+ 0,
+ 3,
+ 0.15
+ ],
+ "rotation": [
+ -0.7071068,
+ 0,
+ 0,
+ 0.7071068
+ ]
+ }
+ ],
+ "meshes": [
+ {
+ "primitives": [
+ {
+ "attributes": {
+ "NORMAL": 1,
+ "POSITION": 2
+ },
+ "indices": 0,
+ "mode": 4,
+ "material": 0
+ }
+ ],
+ "name": "Mesh"
+ }
+ ],
+ "accessors": [
+ {
+ "bufferView": 0,
+ "byteOffset": 0,
+ "componentType": 5123,
+ "count": 36,
+ "max": [
+ 23
+ ],
+ "min": [
+ 0
+ ],
+ "type": "SCALAR"
+ },
+ {
+ "bufferView": 1,
+ "byteOffset": 0,
+ "componentType": 5126,
+ "count": 24,
+ "max": [
+ 1.0,
+ 1.0,
+ 1.0
+ ],
+ "min": [
+ -1.0,
+ -1.0,
+ -1.0
+ ],
+ "type": "VEC3"
+ },
+ {
+ "bufferView": 1,
+ "byteOffset": 288,
+ "componentType": 5126,
+ "count": 24,
+ "max": [
+ 0.5,
+ 0.5,
+ 0.5
+ ],
+ "min": [
+ -0.5,
+ -0.5,
+ -0.5
+ ],
+ "type": "VEC3"
+ }
+ ],
+ "materials": [
+ {
+ "pbrMetallicRoughness": {
+ "baseColorFactor": [
+ 1.0,
+ 1.0,
+ 1.0,
+ 1.0
+ ],
+ "metallicFactor": 0.0
+ },
+ "name": "White"
+ }
+ ],
+ "bufferViews": [
+ {
+ "buffer": 0,
+ "byteOffset": 576,
+ "byteLength": 72,
+ "target": 34963
+ },
+ {
+ "buffer": 0,
+ "byteOffset": 0,
+ "byteLength": 576,
+ "byteStride": 12,
+ "target": 34962
+ }
+ ],
+ "buffers": [
+ {
+ "byteLength": 648,
+ "uri": "data:application/octet-stream;base64,AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAvwAAAL8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAL8AAAA/AAAAPwAAAL8AAAC/AAAAvwAAAL8AAAC/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAPwAAAD8AAAC/AAAAPwAAAL8AAAC/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAvwAAAD8AAAC/AAAAPwAAAD8AAAC/AAAAvwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAPwAAAL8AAAC/AAAAPwAAAD8AAAC/AAABAAIAAwACAAEABAAFAAYABwAGAAUACAAJAAoACwAKAAkADAANAA4ADwAOAA0AEAARABIAEwASABEAFAAVABYAFwAWABUA"
+ }
+ ],
+ "extensions": {
+ "EXT_lights_ies": {
+ "lights": [
+ {
+ "uri": "LightProfile.ies"
+ }
+ ]
+ }
+ },
+ "extensionsUsed": [
+ "EXT_lights_ies"
+ ]
+}
diff --git a/Source/Tests/Assets/LightsIES/Example_bufferView.gltf b/Source/Tests/Assets/LightsIES/Example_bufferView.gltf
new file mode 100644
index 0000000..8fe5a63
--- /dev/null
+++ b/Source/Tests/Assets/LightsIES/Example_bufferView.gltf
@@ -0,0 +1,173 @@
+{
+ "asset": {
+ "version": "2.0"
+ },
+ "scene": 0,
+ "scenes": [
+ {
+ "nodes": [
+ 0
+ ]
+ }
+ ],
+ "nodes": [
+ {
+ "children": [
+ 1,
+ 2
+ ]
+ },
+ {
+ "mesh": 0,
+ "scale": [
+ 7.5,
+ 7.5,
+ 0.1
+ ]
+ },
+ {
+ "extensions": {
+ "EXT_lights_ies": {
+ "light": 0,
+ "color": [
+ 0.181,
+ 0.485,
+ 0
+ ]
+ }
+ },
+ "translation": [
+ 0,
+ 3,
+ 0.15
+ ],
+ "rotation": [
+ -0.7071068,
+ 0,
+ 0,
+ 0.7071068
+ ]
+ }
+ ],
+ "meshes": [
+ {
+ "primitives": [
+ {
+ "attributes": {
+ "NORMAL": 1,
+ "POSITION": 2
+ },
+ "indices": 0,
+ "mode": 4,
+ "material": 0
+ }
+ ],
+ "name": "Mesh"
+ }
+ ],
+ "accessors": [
+ {
+ "bufferView": 0,
+ "byteOffset": 0,
+ "componentType": 5123,
+ "count": 36,
+ "max": [
+ 23
+ ],
+ "min": [
+ 0
+ ],
+ "type": "SCALAR"
+ },
+ {
+ "bufferView": 1,
+ "byteOffset": 0,
+ "componentType": 5126,
+ "count": 24,
+ "max": [
+ 1.0,
+ 1.0,
+ 1.0
+ ],
+ "min": [
+ -1.0,
+ -1.0,
+ -1.0
+ ],
+ "type": "VEC3"
+ },
+ {
+ "bufferView": 1,
+ "byteOffset": 288,
+ "componentType": 5126,
+ "count": 24,
+ "max": [
+ 0.5,
+ 0.5,
+ 0.5
+ ],
+ "min": [
+ -0.5,
+ -0.5,
+ -0.5
+ ],
+ "type": "VEC3"
+ }
+ ],
+ "materials": [
+ {
+ "pbrMetallicRoughness": {
+ "baseColorFactor": [
+ 1.0,
+ 1.0,
+ 1.0,
+ 1.0
+ ],
+ "metallicFactor": 0.0
+ },
+ "name": "White"
+ }
+ ],
+ "bufferViews": [
+ {
+ "buffer": 0,
+ "byteOffset": 576,
+ "byteLength": 72,
+ "target": 34963
+ },
+ {
+ "buffer": 0,
+ "byteOffset": 0,
+ "byteLength": 576,
+ "byteStride": 12,
+ "target": 34962
+ },
+ {
+ "buffer": 1,
+ "byteLength": 222
+ }
+ ],
+ "buffers": [
+ {
+ "byteLength": 648,
+ "uri": "data:application/octet-stream;base64,AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAvwAAAL8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAL8AAAA/AAAAPwAAAL8AAAC/AAAAvwAAAL8AAAC/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAPwAAAD8AAAC/AAAAPwAAAL8AAAC/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAvwAAAD8AAAC/AAAAPwAAAD8AAAC/AAAAvwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAPwAAAL8AAAC/AAAAPwAAAD8AAAC/AAABAAIAAwACAAEABAAFAAYABwAGAAUACAAJAAoACwAKAAkADAANAA4ADwAOAA0AEAARABIAEwASABEAFAAVABYAFwAWABUA"
+ },
+ {
+ "byteLength": 222,
+ "uri": "data:application/octet-stream;base64,SUVTOkxNLTYzLTIwMTkKW1RFU1RdIEV4YW1wbGUgUHJvZmlsZQpbVEVTVExBQl0gTlZJRElBCltJU1NVRURBVEVdIDAxLUpBTi0yMDIzCltNQU5VRkFDXSBOVklESUEKVElMVD1OT05FCjEgLTEgMSAxMCAxIDEgMSAtMC4yIC0wLjIgMAoxLjAgMS4xMDAwMSAyNQowIDEwIDIwIDMwIDQwIDUwIDYwIDcwIDgwIDkwCjAKODUwMCA1MDAwIDMyNTAgNTI1MCAyNzUwIDE1MDAgMjAwIDUwIDEwIDAK"
+ }
+ ],
+ "extensions": {
+ "EXT_lights_ies": {
+ "lights": [
+ {
+ "bufferView": 2,
+ "mimeType": "application/x-ies-lm-63"
+ }
+ ]
+ }
+ },
+ "extensionsUsed": [
+ "EXT_lights_ies"
+ ]
+}
diff --git a/Source/Tests/Assets/LightsIES/Example_dataUri.gltf b/Source/Tests/Assets/LightsIES/Example_dataUri.gltf
new file mode 100644
index 0000000..6519100
--- /dev/null
+++ b/Source/Tests/Assets/LightsIES/Example_dataUri.gltf
@@ -0,0 +1,164 @@
+{
+ "asset": {
+ "version": "2.0"
+ },
+ "scene": 0,
+ "scenes": [
+ {
+ "nodes": [
+ 0
+ ]
+ }
+ ],
+ "nodes": [
+ {
+ "children": [
+ 1,
+ 2
+ ]
+ },
+ {
+ "mesh": 0,
+ "scale": [
+ 7.5,
+ 7.5,
+ 0.1
+ ]
+ },
+ {
+ "extensions": {
+ "EXT_lights_ies": {
+ "light": 0,
+ "color": [
+ 0.181,
+ 0.485,
+ 0
+ ]
+ }
+ },
+ "translation": [
+ 0,
+ 3,
+ 0.15
+ ],
+ "rotation": [
+ -0.7071068,
+ 0,
+ 0,
+ 0.7071068
+ ]
+ }
+ ],
+ "meshes": [
+ {
+ "primitives": [
+ {
+ "attributes": {
+ "NORMAL": 1,
+ "POSITION": 2
+ },
+ "indices": 0,
+ "mode": 4,
+ "material": 0
+ }
+ ],
+ "name": "Mesh"
+ }
+ ],
+ "accessors": [
+ {
+ "bufferView": 0,
+ "byteOffset": 0,
+ "componentType": 5123,
+ "count": 36,
+ "max": [
+ 23
+ ],
+ "min": [
+ 0
+ ],
+ "type": "SCALAR"
+ },
+ {
+ "bufferView": 1,
+ "byteOffset": 0,
+ "componentType": 5126,
+ "count": 24,
+ "max": [
+ 1.0,
+ 1.0,
+ 1.0
+ ],
+ "min": [
+ -1.0,
+ -1.0,
+ -1.0
+ ],
+ "type": "VEC3"
+ },
+ {
+ "bufferView": 1,
+ "byteOffset": 288,
+ "componentType": 5126,
+ "count": 24,
+ "max": [
+ 0.5,
+ 0.5,
+ 0.5
+ ],
+ "min": [
+ -0.5,
+ -0.5,
+ -0.5
+ ],
+ "type": "VEC3"
+ }
+ ],
+ "materials": [
+ {
+ "pbrMetallicRoughness": {
+ "baseColorFactor": [
+ 1.0,
+ 1.0,
+ 1.0,
+ 1.0
+ ],
+ "metallicFactor": 0.0
+ },
+ "name": "White"
+ }
+ ],
+ "bufferViews": [
+ {
+ "buffer": 0,
+ "byteOffset": 576,
+ "byteLength": 72,
+ "target": 34963
+ },
+ {
+ "buffer": 0,
+ "byteOffset": 0,
+ "byteLength": 576,
+ "byteStride": 12,
+ "target": 34962
+ }
+ ],
+ "buffers": [
+ {
+ "byteLength": 648,
+ "uri": "data:application/octet-stream;base64,AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAvwAAAL8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAL8AAAA/AAAAPwAAAL8AAAC/AAAAvwAAAL8AAAC/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAPwAAAD8AAAC/AAAAPwAAAL8AAAC/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAvwAAAD8AAAC/AAAAPwAAAD8AAAC/AAAAvwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAPwAAAL8AAAC/AAAAPwAAAD8AAAC/AAABAAIAAwACAAEABAAFAAYABwAGAAUACAAJAAoACwAKAAkADAANAA4ADwAOAA0AEAARABIAEwASABEAFAAVABYAFwAWABUA"
+ }
+ ],
+ "extensions": {
+ "EXT_lights_ies": {
+ "lights": [
+ {
+ "uri": "data:application/x-ies-lm-63;base64,SUVTOkxNLTYzLTIwMTkNCltURVNUXSBFeGFtcGxlIFByb2ZpbGUNCltURVNUTEFCXSBOVklESUENCltJU1NVRURBVEVdIDAxLUpBTi0yMDIzDQpbTUFOVUZBQ10gTlZJRElBDQpUSUxUPU5PTkUNCjEgLTEgMSAxMCAxIDEgMSAtMC4yIC0wLjIgMA0KMS4wIDEuMTAwMDEgMjUNCjAgMTAgMjAgMzAgNDAgNTAgNjAgNzAgODAgOTANCjANCjg1MDAgNTAwMCAzMjUwIDUyNTAgMjc1MCAxNTAwIDIwMCA1MCAxMCAw"
+ }
+ ]
+ }
+ },
+ "extensionsUsed": [
+ "EXT_lights_ies"
+ ]
+}
diff --git a/Source/Tests/Assets/LightsIES/LightProfile.ies b/Source/Tests/Assets/LightsIES/LightProfile.ies
new file mode 100644
index 0000000..c7cdeaa
--- /dev/null
+++ b/Source/Tests/Assets/LightsIES/LightProfile.ies
@@ -0,0 +1,11 @@
+IES:LM-63-2019
+[TEST] Example Profile
+[TESTLAB] NVIDIA
+[ISSUEDATE] 01-JAN-2023
+[MANUFAC] NVIDIA
+TILT=NONE
+1 -1 1 10 1 1 1 -0.2 -0.2 0
+1.0 1.10001 25
+0 10 20 30 40 50 60 70 80 90
+0
+8500 5000 3250 5250 2750 1500 200 50 10 0
diff --git a/Source/Tests/Core.cs b/Source/Tests/Core.cs
new file mode 100644
index 0000000..2ddcd08
--- /dev/null
+++ b/Source/Tests/Core.cs
@@ -0,0 +1,101 @@
+using System.Diagnostics;
+using System.Reflection;
+
+namespace glTF
+{
+ [TestClass]
+ public sealed class Core(TestContext testContext)
+ {
+ private static readonly string ValidatorExePath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "gltf_validator.exe");
+
+ private readonly CancellationToken cancellationToken = testContext.CancellationTokenSource.Token;
+ private readonly string resultsDirectoryPath = Path.Combine(testContext.TestRunDirectory!, testContext.TestName!);
+ private readonly string modelsDirectoryPath = Path.GetFullPath(Path.Combine(testContext.TestRunDirectory!, @"..\..\..\..\Source\Tests\Assets"));
+
+ public async Task ValidateAsync(string filePath)
+ {
+ using var process = Process.Start(new ProcessStartInfo(ValidatorExePath, [filePath])
+ {
+ RedirectStandardError = true,
+ RedirectStandardOutput = true,
+ })!;
+
+ await process.WaitForExitAsync(this.cancellationToken);
+ if (process.ExitCode != 0)
+ {
+ throw new Exception("Validation failed");
+ }
+ }
+
+ private string Pack(string filePath)
+ {
+ var fileName = Path.GetFileNameWithoutExtension(filePath);
+ var inputFilePath = Path.Combine(this.modelsDirectoryPath, filePath);
+ var outputFilePath = Path.Combine(this.resultsDirectoryPath, $"{fileName}.glb");
+ Packer.Pack(inputFilePath, outputFilePath);
+ return outputFilePath;
+ }
+
+ private string Unpack(string filePath)
+ {
+ var fileName = Path.GetFileNameWithoutExtension(filePath);
+ var inputFilePath = Path.Combine(this.modelsDirectoryPath, filePath);
+ var outputDirectoryPath = Path.Combine(this.resultsDirectoryPath, fileName);
+ Unpacker.Unpack(inputFilePath, outputDirectoryPath, true);
+ return Path.Combine(outputDirectoryPath, $"{fileName}.gltf");
+ }
+
+ [TestMethod]
+ public async Task Pack_Box()
+ {
+ var outputFilePath = this.Pack(@"Box\glTF\Box.gltf");
+ await this.ValidateAsync(outputFilePath);
+ }
+
+ [TestMethod]
+ public async Task Pack_Box_Embedded()
+ {
+ var outputFilePath = this.Pack(@"Box\glTF-Embedded\Box.gltf");
+ await this.ValidateAsync(outputFilePath);
+ }
+
+ [TestMethod]
+ public async Task Pack_BoxTextured()
+ {
+ var outputFilePath = this.Pack(@"BoxTextured\glTF\BoxTextured.gltf");
+ await this.ValidateAsync(outputFilePath);
+ }
+
+ [TestMethod]
+ public async Task Unpack_Box()
+ {
+ var outputFilePath = this.Unpack(@"Box\glTF-Binary\Box.glb");
+ await this.ValidateAsync(outputFilePath);
+ }
+
+ [TestMethod]
+ public async Task Unpack_BoxTextured()
+ {
+ var outputFilePath = this.Unpack(@"BoxTextured\glTF-Binary\BoxTextured.glb");
+ await this.ValidateAsync(outputFilePath);
+ }
+
+ [TestMethod]
+ public async Task Pack_Unpack_BoxTextured_Embedded()
+ {
+ var outputFilePath = this.Unpack(this.Pack(@"BoxTextured\glTF-Embedded\BoxTextured.gltf"));
+ await this.ValidateAsync(outputFilePath);
+ }
+
+ [TestMethod]
+ public async Task Pack_Unpack_LightsIES()
+ {
+ string[] filePaths = [@"LightsIES\Example.gltf", @"LightsIES\Example_bufferview.gltf", @"LightsIES\Example_dataUri.gltf"];
+ foreach (var filePath in filePaths)
+ {
+ var outputFilePath = this.Unpack(this.Pack(filePath));
+ await this.ValidateAsync(outputFilePath);
+ }
+ }
+ }
+}
diff --git a/Source/Tests/External/gltf_validator-2.0.0-dev.3.10-win64/LICENSE b/Source/Tests/External/gltf_validator-2.0.0-dev.3.10-win64/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/Source/Tests/External/gltf_validator-2.0.0-dev.3.10-win64/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/Source/Tests/External/gltf_validator-2.0.0-dev.3.10-win64/NOTICES b/Source/Tests/External/gltf_validator-2.0.0-dev.3.10-win64/NOTICES
new file mode 100644
index 0000000..ec35524
--- /dev/null
+++ b/Source/Tests/External/gltf_validator-2.0.0-dev.3.10-win64/NOTICES
@@ -0,0 +1,435 @@
+Dart SDK
+
+Copyright 2012, the Dart project authors.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google LLC nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+--------------------------------------------------------------------------------
+
+args
+
+Copyright 2013, the Dart project authors.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google LLC nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+--------------------------------------------------------------------------------
+
+collection
+
+Copyright 2015, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google Inc. nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+--------------------------------------------------------------------------------
+
+isolate
+
+Copyright 2015, the Dart project authors.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google LLC nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+--------------------------------------------------------------------------------
+
+meta
+
+Copyright 2016, the Dart project authors.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google LLC nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+--------------------------------------------------------------------------------
+
+path
+
+Copyright 2014, the Dart project authors.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google LLC nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+--------------------------------------------------------------------------------
+
+source_span
+
+Copyright 2014, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google Inc. nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+--------------------------------------------------------------------------------
+
+string_scanner
+
+Copyright 2014, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google Inc. nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+--------------------------------------------------------------------------------
+
+term_glyph
+
+Copyright 2017, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google Inc. nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+--------------------------------------------------------------------------------
+
+vector_math
+
+Copyright 2015, Google Inc. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Copyright (C) 2013 Andrew Magill
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+
+--------------------------------------------------------------------------------
+
+yaml
+
+Copyright (c) 2014, the Dart project authors.
+Copyright (c) 2006, Kirill Simonov.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+--------------------------------------------------------------------------------
+
+node_preamble
+
+The MIT License (MIT)
+
+Copyright (c) 2015 Michael Bullington
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+===
+
+Copyright 2012, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google Inc. nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+--------------------------------------------------------------------------------
+
+PrismJS
+
+MIT LICENSE
+
+Copyright (c) 2012 Lea Verou
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/Source/Tests/External/gltf_validator-2.0.0-dev.3.10-win64/docs/config-example.yaml b/Source/Tests/External/gltf_validator-2.0.0-dev.3.10-win64/docs/config-example.yaml
new file mode 100644
index 0000000..da4d7d5
--- /dev/null
+++ b/Source/Tests/External/gltf_validator-2.0.0-dev.3.10-win64/docs/config-example.yaml
@@ -0,0 +1,14 @@
+# Maximum number of issues, 0 means unlimited
+max-issues: 10
+
+# List of ignored issues
+ignore:
+ - UNEXPECTED_PROPERTY
+
+# List of only issues to consider. Cannot be used along with 'ignore'.
+only:
+ - ACCESSOR_INVALID_FLOAT
+
+# Map of overridden severities. 0 - Error, 1 - Warning, 2 - Info, 3 - Hint
+override:
+ ACCESSOR_INDEX_TRIANGLE_DEGENERATE: 0
\ No newline at end of file
diff --git a/Source/Tests/External/gltf_validator-2.0.0-dev.3.10-win64/docs/validation.schema.json b/Source/Tests/External/gltf_validator-2.0.0-dev.3.10-win64/docs/validation.schema.json
new file mode 100644
index 0000000..fad78a2
--- /dev/null
+++ b/Source/Tests/External/gltf_validator-2.0.0-dev.3.10-win64/docs/validation.schema.json
@@ -0,0 +1,266 @@
+{
+ "$schema": "http://json-schema.org/draft-06/schema#",
+ "title": "Validation Report",
+ "type": "object",
+ "description": "Output of glTF-Validator",
+ "definitions": {
+ "nonNegativeInteger": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "positiveInteger": {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ }
+ },
+ "properties": {
+ "uri": {
+ "type": "string",
+ "format": "uri-reference",
+ "description": "URI of validated asset."
+ },
+ "mimeType": {
+ "description": "MIME type of validated asset. Undefined when file format is not recognized.",
+ "oneOf": [
+ {
+ "const": "model/gltf+json",
+ "description": "glTF asset in plain text form."
+ },
+ {
+ "const": "model/gltf-binary",
+ "description": "glTF asset in GLB container."
+ }
+ ]
+ },
+ "validatorVersion": {
+ "type": "string",
+ "description": "Version string of glTF-Validator. Must follow semver syntax."
+ },
+ "validatedAt": {
+ "description": "UTC timestamp of validation time.",
+ "type": "string",
+ "format": "date-time"
+ },
+ "issues": {
+ "type": "object",
+ "properties": {
+ "numErrors": {
+ "$ref": "#/definitions/nonNegativeInteger"
+ },
+ "numWarnings": {
+ "$ref": "#/definitions/nonNegativeInteger"
+ },
+ "numInfos": {
+ "$ref": "#/definitions/nonNegativeInteger"
+ },
+ "numHints": {
+ "$ref": "#/definitions/nonNegativeInteger"
+ },
+ "messages": {
+ "type": "array",
+ "items": {
+ "properties": {
+ "code": {
+ "type": "string"
+ },
+ "severity": {
+ "oneOf": [
+ {
+ "const": 0,
+ "description": "Error"
+ },
+ {
+ "const": 1,
+ "description": "Warning"
+ },
+ {
+ "const": 2,
+ "description": "Information"
+ },
+ {
+ "const": 3,
+ "description": "Hint"
+ }
+ ]
+ },
+ "pointer": {
+ "description": "JSON Pointer to the object causing the issue.",
+ "type": "string",
+ "format": "json-pointer"
+ },
+ "offset": {
+ "description": "Byte offset in GLB file. Applicable only to GLB issues.",
+ "$ref": "#/definitions/nonNegativeInteger"
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "code",
+ "severity",
+ "message"
+ ],
+ "oneOf": [
+ {
+ "required": [
+ "pointer"
+ ]
+ },
+ {
+ "required": [
+ "offset"
+ ]
+ }
+ ]
+ }
+ },
+ "truncated": {
+ "type": "boolean",
+ "description": "Indicates that validation output is incomplete due to too many messages."
+ }
+ },
+ "required": [
+ "numErrors",
+ "numWarnings",
+ "numInfos",
+ "numHints",
+ "messages",
+ "truncated"
+ ]
+ },
+ "info": {
+ "type": "object",
+ "description": "An object containing various metrics about the validated asset. May be undefined for invalid inputs.",
+ "properties": {
+ "version": {
+ "type": "string",
+ "description": "The glTF version that this asset targets.",
+ "pattern": "^[0-9]+\\.[0-9]+$"
+ },
+ "minVersion": {
+ "type": "string",
+ "description": "The minimum glTF version that this asset targets.",
+ "pattern": "^[0-9]+\\.[0-9]+$"
+ },
+ "generator": {
+ "type": "string",
+ "description": "Tool that generated this glTF model."
+ },
+ "extensionsUsed": {
+ "type": "array",
+ "description": "Names of glTF extensions used somewhere in this asset.",
+ "items": {
+ "type": "string"
+ },
+ "uniqueItems": true,
+ "minItems": 1
+ },
+ "extensionsRequired": {
+ "type": "array",
+ "description": "Names of glTF extensions required to properly load this asset.",
+ "items": {
+ "type": "string"
+ },
+ "uniqueItems": true,
+ "minItems": 1
+ },
+ "resources": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "pointer": {
+ "type": "string",
+ "format": "json-pointer"
+ },
+ "storage": {
+ "oneOf": [
+ {
+ "const": "data-uri",
+ "description": "Resource is stored as Data-URI."
+ },
+ {
+ "const": "buffer-view",
+ "description": "Resource is stored within glTF buffer and accessed via bufferView."
+ },
+ {
+ "const": "glb",
+ "description": "Resource is stored in binary chunk of GLB container."
+ },
+ {
+ "const": "external",
+ "description": "Resource is stored externally."
+ }
+ ]
+ },
+ "mimeType": {
+ "type": "string"
+ },
+ "byteLength": {
+ "$ref": "#/definitions/positiveInteger",
+ "description": "Byte length of the resource. Undefined when the resource wasn't available."
+ },
+ "uri": {
+ "type": "string",
+ "format": "uri-reference",
+ "description": "URI. Defined only for external resources."
+ },
+ "image": {
+ "type": "object",
+ "description": "Image-specific metadata.",
+ "properties": {
+ "width": {
+ "$ref": "#/definitions/positiveInteger"
+ },
+ "height": {
+ "$ref": "#/definitions/positiveInteger"
+ },
+ "format": {
+ "enum": [
+ "rgb",
+ "rgba",
+ "luminance",
+ "luminance-alpha"
+ ]
+ },
+ "primaries": {
+ "enum": [
+ "srgb",
+ "custom"
+ ]
+ },
+ "transfer": {
+ "enum": [
+ "linear",
+ "srgb",
+ "custom"
+ ]
+ },
+ "bits": {
+ "$ref": "#/definitions/positiveInteger"
+ }
+ },
+ "required": [
+ "width",
+ "height"
+ ]
+ }
+ }
+ },
+ "required": [
+ "pointer"
+ ],
+ "minItems": 1
+ }
+ },
+ "required": [
+ "version"
+ ]
+ }
+ },
+ "required": [
+ "validatorVersion",
+ "issues"
+ ]
+}
\ No newline at end of file
diff --git a/Source/Tests/External/gltf_validator-2.0.0-dev.3.10-win64/gltf_validator.exe b/Source/Tests/External/gltf_validator-2.0.0-dev.3.10-win64/gltf_validator.exe
new file mode 100644
index 0000000..77dcc45
--- /dev/null
+++ b/Source/Tests/External/gltf_validator-2.0.0-dev.3.10-win64/gltf_validator.exe
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4388a152ff90b68c6430ae03862e05e257a9d50a500ed7d0eb1cd420dc75ff96
+size 5520896
diff --git a/Source/Tests/MSTestSettings.cs b/Source/Tests/MSTestSettings.cs
new file mode 100644
index 0000000..aaf278c
--- /dev/null
+++ b/Source/Tests/MSTestSettings.cs
@@ -0,0 +1 @@
+[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]
diff --git a/Source/Tests/Tests.csproj b/Source/Tests/Tests.csproj
new file mode 100644
index 0000000..dea6873
--- /dev/null
+++ b/Source/Tests/Tests.csproj
@@ -0,0 +1,38 @@
+
+
+
+ net8.0
+ latest
+ enable
+ enable
+ glTF
+
+
+
+
+
+
+
+
+
+
+
+ %(RecursiveDir)%(Filename)%(Extension)
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Source/glTF-Shell-Extensions.sln b/Source/glTF-Shell-Extensions.sln
index 4e3504d..499fa78 100644
--- a/Source/glTF-Shell-Extensions.sln
+++ b/Source/glTF-Shell-Extensions.sln
@@ -5,10 +5,14 @@ VisualStudioVersion = 17.11.35312.102
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ExplorerCommand", "ExplorerCommand\ExplorerCommand.vcxproj", "{294B1862-6E77-4F1E-A542-85F1FA7F7C05}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "glTF", "glTF\glTF.csproj", "{DB2FA8BC-AE9F-43C5-9EC3-E1E8F7DD0FE8}"
-EndProject
Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "Package", "Package\Package.wapproj", "{13B11981-BD9F-4769-B89E-84271568C258}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "Core\Core.csproj", "{87EBE349-E634-4590-AC30-B4C5D15BDD60}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{3B67A8DD-5B47-4D54-BDB1-64523EACCA94}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "glTF", "glTF\glTF.csproj", "{582200F6-950A-4AA2-B178-8CD4EE6F898C}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
@@ -31,18 +35,6 @@ Global
{294B1862-6E77-4F1E-A542-85F1FA7F7C05}.Release|x64.Build.0 = Release|x64
{294B1862-6E77-4F1E-A542-85F1FA7F7C05}.Release|x86.ActiveCfg = Release|Win32
{294B1862-6E77-4F1E-A542-85F1FA7F7C05}.Release|x86.Build.0 = Release|Win32
- {DB2FA8BC-AE9F-43C5-9EC3-E1E8F7DD0FE8}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {DB2FA8BC-AE9F-43C5-9EC3-E1E8F7DD0FE8}.Debug|ARM64.Build.0 = Debug|ARM64
- {DB2FA8BC-AE9F-43C5-9EC3-E1E8F7DD0FE8}.Debug|x64.ActiveCfg = Debug|x64
- {DB2FA8BC-AE9F-43C5-9EC3-E1E8F7DD0FE8}.Debug|x64.Build.0 = Debug|x64
- {DB2FA8BC-AE9F-43C5-9EC3-E1E8F7DD0FE8}.Debug|x86.ActiveCfg = Debug|x86
- {DB2FA8BC-AE9F-43C5-9EC3-E1E8F7DD0FE8}.Debug|x86.Build.0 = Debug|x86
- {DB2FA8BC-AE9F-43C5-9EC3-E1E8F7DD0FE8}.Release|ARM64.ActiveCfg = Release|ARM64
- {DB2FA8BC-AE9F-43C5-9EC3-E1E8F7DD0FE8}.Release|ARM64.Build.0 = Release|ARM64
- {DB2FA8BC-AE9F-43C5-9EC3-E1E8F7DD0FE8}.Release|x64.ActiveCfg = Release|x64
- {DB2FA8BC-AE9F-43C5-9EC3-E1E8F7DD0FE8}.Release|x64.Build.0 = Release|x64
- {DB2FA8BC-AE9F-43C5-9EC3-E1E8F7DD0FE8}.Release|x86.ActiveCfg = Release|x86
- {DB2FA8BC-AE9F-43C5-9EC3-E1E8F7DD0FE8}.Release|x86.Build.0 = Release|x86
{13B11981-BD9F-4769-B89E-84271568C258}.Debug|ARM64.ActiveCfg = Debug|ARM64
{13B11981-BD9F-4769-B89E-84271568C258}.Debug|ARM64.Build.0 = Debug|ARM64
{13B11981-BD9F-4769-B89E-84271568C258}.Debug|ARM64.Deploy.0 = Debug|ARM64
@@ -61,6 +53,42 @@ Global
{13B11981-BD9F-4769-B89E-84271568C258}.Release|x86.ActiveCfg = Release|x86
{13B11981-BD9F-4769-B89E-84271568C258}.Release|x86.Build.0 = Release|x86
{13B11981-BD9F-4769-B89E-84271568C258}.Release|x86.Deploy.0 = Release|x86
+ {87EBE349-E634-4590-AC30-B4C5D15BDD60}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {87EBE349-E634-4590-AC30-B4C5D15BDD60}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {87EBE349-E634-4590-AC30-B4C5D15BDD60}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {87EBE349-E634-4590-AC30-B4C5D15BDD60}.Debug|x64.Build.0 = Debug|Any CPU
+ {87EBE349-E634-4590-AC30-B4C5D15BDD60}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {87EBE349-E634-4590-AC30-B4C5D15BDD60}.Debug|x86.Build.0 = Debug|Any CPU
+ {87EBE349-E634-4590-AC30-B4C5D15BDD60}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {87EBE349-E634-4590-AC30-B4C5D15BDD60}.Release|ARM64.Build.0 = Release|Any CPU
+ {87EBE349-E634-4590-AC30-B4C5D15BDD60}.Release|x64.ActiveCfg = Release|Any CPU
+ {87EBE349-E634-4590-AC30-B4C5D15BDD60}.Release|x64.Build.0 = Release|Any CPU
+ {87EBE349-E634-4590-AC30-B4C5D15BDD60}.Release|x86.ActiveCfg = Release|Any CPU
+ {87EBE349-E634-4590-AC30-B4C5D15BDD60}.Release|x86.Build.0 = Release|Any CPU
+ {3B67A8DD-5B47-4D54-BDB1-64523EACCA94}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {3B67A8DD-5B47-4D54-BDB1-64523EACCA94}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {3B67A8DD-5B47-4D54-BDB1-64523EACCA94}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {3B67A8DD-5B47-4D54-BDB1-64523EACCA94}.Debug|x64.Build.0 = Debug|Any CPU
+ {3B67A8DD-5B47-4D54-BDB1-64523EACCA94}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {3B67A8DD-5B47-4D54-BDB1-64523EACCA94}.Debug|x86.Build.0 = Debug|Any CPU
+ {3B67A8DD-5B47-4D54-BDB1-64523EACCA94}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {3B67A8DD-5B47-4D54-BDB1-64523EACCA94}.Release|ARM64.Build.0 = Release|Any CPU
+ {3B67A8DD-5B47-4D54-BDB1-64523EACCA94}.Release|x64.ActiveCfg = Release|Any CPU
+ {3B67A8DD-5B47-4D54-BDB1-64523EACCA94}.Release|x64.Build.0 = Release|Any CPU
+ {3B67A8DD-5B47-4D54-BDB1-64523EACCA94}.Release|x86.ActiveCfg = Release|Any CPU
+ {3B67A8DD-5B47-4D54-BDB1-64523EACCA94}.Release|x86.Build.0 = Release|Any CPU
+ {582200F6-950A-4AA2-B178-8CD4EE6F898C}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {582200F6-950A-4AA2-B178-8CD4EE6F898C}.Debug|ARM64.Build.0 = Debug|ARM64
+ {582200F6-950A-4AA2-B178-8CD4EE6F898C}.Debug|x64.ActiveCfg = Debug|x64
+ {582200F6-950A-4AA2-B178-8CD4EE6F898C}.Debug|x64.Build.0 = Debug|x64
+ {582200F6-950A-4AA2-B178-8CD4EE6F898C}.Debug|x86.ActiveCfg = Debug|x86
+ {582200F6-950A-4AA2-B178-8CD4EE6F898C}.Debug|x86.Build.0 = Debug|x86
+ {582200F6-950A-4AA2-B178-8CD4EE6F898C}.Release|ARM64.ActiveCfg = Release|ARM64
+ {582200F6-950A-4AA2-B178-8CD4EE6F898C}.Release|ARM64.Build.0 = Release|ARM64
+ {582200F6-950A-4AA2-B178-8CD4EE6F898C}.Release|x64.ActiveCfg = Release|x64
+ {582200F6-950A-4AA2-B178-8CD4EE6F898C}.Release|x64.Build.0 = Release|x64
+ {582200F6-950A-4AA2-B178-8CD4EE6F898C}.Release|x86.ActiveCfg = Release|x86
+ {582200F6-950A-4AA2-B178-8CD4EE6F898C}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Source/glTF/App.manifest b/Source/glTF/App.manifest
index 6e3532d..f11d757 100644
--- a/Source/glTF/App.manifest
+++ b/Source/glTF/App.manifest
@@ -1,6 +1,6 @@
-
+
diff --git a/Source/glTF/Core/MimeType.cs b/Source/glTF/Core/MimeType.cs
deleted file mode 100644
index f06e328..0000000
--- a/Source/glTF/Core/MimeType.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-using System.IO;
-
-namespace glTF
-{
- internal class MimeType
- {
- public static string ToFileExtension(string mimeType)
- {
- switch (mimeType)
- {
- case "image/png":
- return ".png";
- case "image/jpeg":
- return ".jpg";
- case "image/vnd-ms.dds":
- return ".dds";
- case "image/ktx2":
- return ".ktx2";
- case "image/webp":
- return ".webp";
- }
-
- throw new InvalidDataException($"Unsupported mime type: {mimeType}");
- }
-
- public static string FromFileExtension(string fileExtension)
- {
- switch (fileExtension.ToLower())
- {
- case ".png":
- return "image/png";
- case ".jpg":
- case ".jpeg":
- return "image/jpeg";
- case ".dds":
- return "image/vnd-ms.dds";
- case ".ktx2":
- return "image/ktx2";
- case ".webp":
- return "image/webp";
- }
-
- throw new InvalidDataException($"Unsupported file extension: {fileExtension}");
- }
- }
-}
diff --git a/Source/glTF/Core/Packer.cs b/Source/glTF/Core/Packer.cs
deleted file mode 100644
index bcb5377..0000000
--- a/Source/glTF/Core/Packer.cs
+++ /dev/null
@@ -1,190 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.IO.MemoryMappedFiles;
-using System.Text.Json;
-using System.Text.Json.Nodes;
-
-namespace glTF
-{
- internal class Packer
- {
- public static void Pack(string inputFilePath, string outputFilePath)
- {
- var inputDirectoryPath = Path.GetDirectoryName(inputFilePath);
-
- JsonNode jsonNode;
- using (var jsonStream = File.OpenRead(inputFilePath))
- {
- jsonNode = JsonNode.Parse(jsonStream);
- }
-
- var position = 0;
-
- var memoryMappedFiles = new Dictionary();
- var viewStreams = new List();
-
- var buffers = jsonNode["buffers"]?.AsArray();
- var bufferViews = jsonNode["bufferViews"]?.AsArray();
- var images = jsonNode["images"]?.AsArray();
-
- if (buffers != null)
- {
- for (var index = buffers.Count - 1; index >= 0; index--)
- {
- var buffer = buffers[index];
- var uri = (string)buffer["uri"];
- if (uri != null && Uri.IsWellFormedUriString(uri, UriKind.Relative))
- {
- foreach (var bufferView in bufferViews)
- {
- var bufferIndex = (int)bufferView["buffer"];
- if (bufferIndex == index)
- {
- bufferView["buffer"] = -1;
-
- var byteOffset = (int?)bufferView["byteOffset"] ?? 0;
- bufferView.SetValue("byteOffset", position + byteOffset, 0);
- }
- }
-
- var filePath = Path.Combine(inputDirectoryPath, uri);
- if (!memoryMappedFiles.TryGetValue(filePath, out MemoryMappedFile memoryMappedFile))
- {
- memoryMappedFile = MemoryMappedFile.CreateFromFile(filePath, FileMode.Open);
- memoryMappedFiles.Add(filePath, memoryMappedFile);
- }
-
- var fileLength = Tools.GetFileLength(filePath);
- viewStreams.Add(memoryMappedFile.CreateViewStream(0, fileLength, MemoryMappedFileAccess.Read));
-
- position += fileLength;
- position = Tools.Align(position);
-
- buffers.RemoveAt(index);
- }
- }
- }
-
- if (images != null)
- {
- foreach (var image in images)
- {
- var uri = (string)image["uri"];
- if (uri != null && Uri.IsWellFormedUriString(uri, UriKind.Relative))
- {
- var filePath = Path.Combine(inputDirectoryPath, uri);
- if (!memoryMappedFiles.TryGetValue(filePath, out MemoryMappedFile memoryMappedFile))
- {
- memoryMappedFile = MemoryMappedFile.CreateFromFile(filePath, FileMode.Open);
- memoryMappedFiles.Add(filePath, memoryMappedFile);
- }
-
- var fileLength = Tools.GetFileLength(filePath);
- viewStreams.Add(memoryMappedFile.CreateViewStream(0, fileLength, MemoryMappedFileAccess.Read));
-
- image.AsObject().Remove("uri");
- image["bufferView"] = bufferViews.Count;
- image["mimeType"] = MimeType.FromFileExtension(Path.GetExtension(uri));
-
- position = Tools.Align(position);
-
- var bufferView = new JsonObject
- {
- ["buffer"] = -1,
- ["byteLength"] = fileLength
- };
-
- bufferView.SetValue("byteOffset", position, 0);
- bufferViews.Add((JsonNode)bufferView);
-
- position += fileLength;
- }
- }
- }
-
- if (viewStreams.Count != 0)
- {
- if (buffers == null)
- {
- buffers = new JsonArray();
- jsonNode["buffers"] = buffers;
- }
-
- buffers.Insert(0, new JsonObject
- {
- ["byteLength"] = position
- });
-
- foreach (var bufferView in bufferViews)
- {
- var bufferIndex = (int)bufferView["buffer"];
- bufferView["buffer"] = bufferIndex + 1;
- }
- }
-
- using (var fileStream = File.Create(outputFilePath))
- using (var binaryWriter = new BinaryWriter(fileStream))
- {
- binaryWriter.Write(Binary.Magic);
- binaryWriter.Write(Binary.Version);
-
- var chunksPosition = binaryWriter.BaseStream.Position;
-
- binaryWriter.Write(0U); // length
-
- var jsonChunkPosition = binaryWriter.BaseStream.Position;
-
- binaryWriter.Write(0U); // json chunk length
- binaryWriter.Write(Binary.ChunkFormatJson);
-
- using (var jsonTextWriter = new Utf8JsonWriter(binaryWriter.BaseStream))
- {
- jsonNode.WriteTo(jsonTextWriter);
- }
-
- binaryWriter.BaseStream.Align(0x20);
- var jsonChunkLength = checked((uint)(binaryWriter.BaseStream.Length - jsonChunkPosition)) - Binary.ChunkHeaderLength;
-
- binaryWriter.BaseStream.Seek(jsonChunkPosition, SeekOrigin.Begin);
- binaryWriter.Write(jsonChunkLength);
-
- if (viewStreams.Count != 0)
- {
- binaryWriter.BaseStream.Seek(0, SeekOrigin.End);
- var binChunkPosition = binaryWriter.BaseStream.Position;
-
- binaryWriter.Write(0); // bin chunk length
- binaryWriter.Write(Binary.ChunkFormatBin);
-
- foreach (var viewStream in viewStreams)
- {
- binaryWriter.BaseStream.Align();
- viewStream.CopyTo(binaryWriter.BaseStream);
- }
-
- binaryWriter.BaseStream.Align(0x20);
- var binChunkLength = checked((uint)(binaryWriter.BaseStream.Length - binChunkPosition)) - Binary.ChunkHeaderLength;
-
- binaryWriter.BaseStream.Seek(binChunkPosition, SeekOrigin.Begin);
- binaryWriter.Write(binChunkLength);
- }
-
- var length = checked((uint)binaryWriter.BaseStream.Length);
-
- binaryWriter.BaseStream.Seek(chunksPosition, SeekOrigin.Begin);
- binaryWriter.Write(length);
- }
-
- foreach (var viewStream in viewStreams)
- {
- viewStream.Dispose();
- }
-
- foreach (var memoryMappedFile in memoryMappedFiles.Values)
- {
- memoryMappedFile.Dispose();
- }
- }
- }
-}
\ No newline at end of file
diff --git a/Source/glTF/Extensions/JsonExtensions.cs b/Source/glTF/Extensions/JsonExtensions.cs
deleted file mode 100644
index 63907bb..0000000
--- a/Source/glTF/Extensions/JsonExtensions.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using System.Text.Json.Nodes;
-
-namespace glTF
-{
- internal static class JsonExtensions
- {
- public static void SetValue(this JsonNode jsonNode, string propertyName, int value, int defaultValue)
- {
- if (value.Equals(defaultValue))
- {
- jsonNode.AsObject().Remove(propertyName);
- }
- else
- {
- jsonNode[propertyName] = value;
- }
- }
- }
-}
diff --git a/Source/glTF/UI/PackWindow.xaml b/Source/glTF/UI/PackWindow.xaml
index d66a8c2..899e564 100644
--- a/Source/glTF/UI/PackWindow.xaml
+++ b/Source/glTF/UI/PackWindow.xaml
@@ -6,4 +6,5 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
+
diff --git a/Source/glTF/UI/Win32.cs b/Source/glTF/UI/Win32.cs
index 813e7b0..e52ac5b 100644
--- a/Source/glTF/UI/Win32.cs
+++ b/Source/glTF/UI/Win32.cs
@@ -37,5 +37,4 @@ public struct MONITORINFO
[DllImport("user32.dll", SetLastError = true)]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint flags);
}
-
}
diff --git a/Source/glTF/UI/WindowExtensions.cs b/Source/glTF/UI/WindowExtensions.cs
index 96371d4..a754833 100644
--- a/Source/glTF/UI/WindowExtensions.cs
+++ b/Source/glTF/UI/WindowExtensions.cs
@@ -39,7 +39,15 @@ public static void ApplySettings(this Window window)
public static async Task ShowErrorDialogAsync(this Window window, string message, string title)
{
- var dialog = new ContentDialog()
+ if (window.Content.XamlRoot == null)
+ {
+ var taskCompletionSource = new TaskCompletionSource();
+ (window.Content as FrameworkElement).Loaded += (sender, e) => taskCompletionSource.SetResult();
+ window.Activate();
+ await taskCompletionSource.Task;
+ }
+
+ var dialog = new ContentDialog
{
XamlRoot = window.Content.XamlRoot,
Title = title,
diff --git a/Source/glTF/glTF.csproj b/Source/glTF/glTF.csproj
index cda3c2f..b2e2478 100644
--- a/Source/glTF/glTF.csproj
+++ b/Source/glTF/glTF.csproj
@@ -1,7 +1,7 @@
WinExe
- net8.0-windows10.0.26100.0
+ net8.0-windows10.0.22621.010.0.17763.0glTFApp.manifest
@@ -21,4 +21,8 @@
+
+
+
+
\ No newline at end of file