Merge pull request #1433 from ousttrue/fix/exporter_Extensionused

Fix/exporter extensionused
This commit is contained in:
ousttrue
2021-12-16 20:03:48 +09:00
committed by GitHub
5 changed files with 407 additions and 63 deletions

View File

@@ -138,66 +138,6 @@ namespace UniGLTF
#endregion
#region ToGltf & ToGlb
static Utf8String s_extensions = Utf8String.From("extensions");
static bool UsedExtension(glTF self, string key)
{
if (self.extensionsUsed.Contains(key))
{
return true;
}
return false;
}
static void Traverse(glTF self, JsonNode node, JsonFormatter f, Utf8String parentKey)
{
if (node.IsMap())
{
f.BeginMap();
foreach (var kv in node.ObjectItems())
{
if (parentKey == s_extensions)
{
if (!UsedExtension(self, kv.Key.GetString()))
{
// skip extension not in used
continue;
}
}
f.Key(kv.Key.GetUtf8String());
Traverse(self, kv.Value, f, kv.Key.GetUtf8String());
}
f.EndMap();
}
else if (node.IsArray())
{
f.BeginList();
foreach (var x in node.ArrayItems())
{
Traverse(self, x, f, default(Utf8String));
}
f.EndList();
}
else
{
f.Value(node);
}
}
/// <summary>
/// 出力前に不要な extension を削除する
/// </summary>
/// <param name="self"></param>
/// <param name="json"></param>
/// <returns></returns>
static string RemoveUnusedExtensions(glTF self, string json)
{
var f = new JsonFormatter();
Traverse(self, JsonParser.Parse(json), f, default(Utf8String));
return f.ToString();
}
/// <summary>
/// GLBバイト列
/// </summary>
@@ -207,9 +147,10 @@ namespace UniGLTF
var f = new JsonFormatter();
GltfSerializer.Serialize(f, GLTF);
// remove unused extenions
var json = f.ToString().ParseAsJson().ToString(" ");
RemoveUnusedExtensions(GLTF, json);
json = GltfJsonUtil.FindUsedExtensionsAndUpdateJson(json);
return Glb.Create(json, BinBytes).ToBytes();
}
@@ -234,7 +175,9 @@ namespace UniGLTF
var f = new JsonFormatter();
GltfSerializer.Serialize(f, GLTF);
var json = f.ToString().ParseAsJson().ToString(" ");
RemoveUnusedExtensions(GLTF, json);
json = GltfJsonUtil.FindUsedExtensionsAndUpdateJson(json);
return (json, GLTF.buffers[0]);
}
#endregion

View File

@@ -0,0 +1,212 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UniJSON;
using UnityEngine;
namespace UniGLTF
{
public static class GltfJsonUtil
{
const string EXTENSION_USED_KEY = "extensionUsed";
/// <summary>
/// JsonPath を 再帰的に列挙する
/// object[] の中身は int(array index) or string(object key)
/// </summary>
/// <param name="node"></param>
/// <param name="path"></param>
/// <returns></returns>
public static IEnumerable<object[]> TraverseJsonPath(JsonNode node, List<object> path)
{
if (path == null)
{
path = new List<object>();
}
yield return path.ToArray();
if (node.IsArray())
{
int i = 0;
foreach (var child in node.ArrayItems())
{
path.Add(i);
foreach (var x in TraverseJsonPath(child, path))
{
yield return x;
}
path.RemoveAt(path.Count - 1);
++i;
}
}
else if (node.IsMap())
{
foreach (var kv in node.ObjectItems())
{
path.Add(kv.Key.GetString());
foreach (var x in TraverseJsonPath(kv.Value, path))
{
yield return x;
}
path.RemoveAt(path.Count - 1);
}
}
}
static string DoubleQuote(string src)
{
return $"\"{src}\"";
}
/// <summary>
/// jsonPath が
///
/// [..., "extensions", "EXTENSION_NAME"]
///
/// で有る場合に EXTENSION_NAME を返す。
/// </summary>
/// <param name="jsonPath"></param>
/// <param name="extensionName"></param>
/// <returns></returns>
static bool TryGetExtensionName(object[] path, out string extensionName)
{
if (path.Length >= 2)
{
if (path[path.Length - 2] is string x)
{
if (x == "extensions")
{
if (path[path.Length - 1] is string y)
{
extensionName = y;
return true;
}
else
{
// ありえない。はず
var join = string.Join(", ", path);
Debug.LogWarning($"invalid json path: {join}");
}
}
}
}
extensionName = default;
return false;
}
static void CopyJson(IReadOnlyList<string> extensionUsed, JsonFormatter dst, JsonNode src, int level)
{
if (src.IsArray())
{
dst.BeginList();
foreach (var v in src.ArrayItems())
{
CopyJson(extensionUsed, dst, v, level + 1);
}
dst.EndList();
}
else if (src.IsMap())
{
if (level == 0)
{
// 最上層だけ extensionsUsed の処理をする
var done = false;
dst.BeginMap();
foreach (var kv in src.ObjectItems())
{
var key = kv.Key.GetString();
if (key == EXTENSION_USED_KEY)
{
if (extensionUsed.Count == 0)
{
// skip
}
else
{
dst.Key(key);
// replace
dst.BeginList();
foreach (var ex in extensionUsed)
{
dst.Value(ex);
}
dst.EndList();
// 処理済
}
done = true;
}
else
{
dst.Key(key);
CopyJson(extensionUsed, dst, kv.Value, level + 1);
}
}
if (!done && level == 0 && extensionUsed.Count > 0)
{
// add
dst.Key(EXTENSION_USED_KEY);
dst.BeginList();
foreach (var ex in extensionUsed)
{
dst.Value(ex);
}
dst.EndList();
}
dst.EndMap();
}
else
{
dst.BeginMap();
foreach (var kv in src.ObjectItems())
{
dst.Key(kv.Key.GetUtf8String());
CopyJson(extensionUsed, dst, kv.Value, level + 1);
}
dst.EndMap();
}
}
else
{
// leaf
dst.Value(src);
}
}
/// <summary>
/// https://github.com/KhronosGroup/glTF/blob/main/specification/2.0/schema/glTF.schema.json
///
/// extensionUsed の更新を各拡張自身にやらせるのは無駄だし、手動でコントロールするのも間違いの元である。
/// 完成品の JSON から後付けで作ることにした。
///
/// * Exporter しか使わない処理なので、GC, 処理速度は気にしてない
///
/// </summary>
/// <param name="src"></param>
/// <returns></returns>
public static string FindUsedExtensionsAndUpdateJson(string src)
{
var parsed = src.ParseAsJson();
// unique な extension 名を収集
var used = new HashSet<string>();
foreach (var path in TraverseJsonPath(parsed, null))
{
if (TryGetExtensionName(path, out string extensionName))
{
used.Add(extensionName);
}
}
// json 加工
var f = new JsonFormatter();
CopyJson(used.ToArray(), f, parsed, 0);
// bom無しutf8
var bytes = f.GetStoreBytes();
var utf8 = new UTF8Encoding(false);
return utf8.GetString(bytes.Array, bytes.Offset, bytes.Count);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5a820504e80b57241b7716b5606235f7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,167 @@
using NUnit.Framework;
using UniJSON;
using System.Linq;
namespace UniGLTF
{
public class GltfJsonUtilTests
{
[Test]
public void Update_extensionUsed()
{
var dst = GltfJsonUtil.FindUsedExtensionsAndUpdateJson(@"{
""asset"": {
""generator"": ""COLLADA2GLTF"",
""version"": ""2.0""
},
""scene"": 0,
""scenes"": [
{
""nodes"": [
0
]
}
],
""materials"": [
{
""pbrMetallicRoughness"": {
""baseColorFactor"": [
0.800000011920929,
0.0,
0.0,
1.0
],
""metallicFactor"": 0.0
},
""name"": ""Red"",
""extensions"": {
""KHR_materials_unlit"": {}
}
}
]
}");
var parsed = dst.ParseAsJson();
Assert.AreEqual(new string[] { "KHR_materials_unlit" },
parsed["extensionUsed"].ArrayItems().Select(x => x.GetString()).ToArray());
}
[Test]
public void Replace_extensionUsed()
{
var dst = GltfJsonUtil.FindUsedExtensionsAndUpdateJson(@"{
""asset"": {
""generator"": ""COLLADA2GLTF"",
""version"": ""2.0""
},
""scene"": 0,
""scenes"": [
{
""nodes"": [
0
]
}
],
""extensionUsed"": [""dummy""],
""materials"": [
{
""pbrMetallicRoughness"": {
""baseColorFactor"": [
0.800000011920929,
0.0,
0.0,
1.0
],
""metallicFactor"": 0.0
},
""name"": ""Red"",
""extensions"": {
""KHR_materials_unlit"": {}
}
}
]
}");
var parsed = dst.ParseAsJson();
Assert.AreEqual(new string[] { "KHR_materials_unlit" },
parsed["extensionUsed"].ArrayItems().Select(x => x.GetString()).ToArray());
}
[Test]
public void Empty_extensionUsed()
{
var dst = GltfJsonUtil.FindUsedExtensionsAndUpdateJson(@"{
""asset"": {
""generator"": ""COLLADA2GLTF"",
""version"": ""2.0""
},
""scene"": 0,
""scenes"": [
{
""nodes"": [
0
]
}
],
""extensionUsed"": [""dummy""] ,
""materials"": [
{
""pbrMetallicRoughness"": {
""baseColorFactor"": [
0.800000011920929,
0.0,
0.0,
1.0
],
""metallicFactor"": 0.0
},
""name"": ""Red""
}
]
}");
var parsed = dst.ParseAsJson();
Assert.False(parsed.ContainsKey("extensionUsed"));
}
[Test]
public void Empty2_extensionUsed()
{
var dst = GltfJsonUtil.FindUsedExtensionsAndUpdateJson(@"{
""asset"": {
""generator"": ""COLLADA2GLTF"",
""version"": ""2.0""
},
""scene"": 0,
""scenes"": [
{
""nodes"": [
0
]
}
],
""materials"": [
{
""pbrMetallicRoughness"": {
""baseColorFactor"": [
0.800000011920929,
0.0,
0.0,
1.0
],
""metallicFactor"": 0.0
},
""name"": ""Red""
}
],
""extensionUsed"": [""dummy""]
}");
var parsed = dst.ParseAsJson();
Assert.False(parsed.ContainsKey("extensionUsed"));
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bf03004ca38f9994c8feedbf3afc9352
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: