mirror of
https://github.com/vrm-c/UniVRM.git
synced 2026-08-20 17:45:22 -05:00
Merge pull request #1433 from ousttrue/fix/exporter_Extensionused
Fix/exporter extensionused
This commit is contained in:
@@ -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
|
||||
|
||||
212
Assets/UniGLTF/Runtime/UniGLTF/IO/GltfJsonUtil.cs
Normal file
212
Assets/UniGLTF/Runtime/UniGLTF/IO/GltfJsonUtil.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/UniGLTF/Runtime/UniGLTF/IO/GltfJsonUtil.cs.meta
Normal file
11
Assets/UniGLTF/Runtime/UniGLTF/IO/GltfJsonUtil.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5a820504e80b57241b7716b5606235f7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
167
Assets/UniGLTF/Tests/UniGLTF/GltfJsonUtilTests.cs
Normal file
167
Assets/UniGLTF/Tests/UniGLTF/GltfJsonUtilTests.cs
Normal 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"));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
11
Assets/UniGLTF/Tests/UniGLTF/GltfJsonUtilTests.cs.meta
Normal file
11
Assets/UniGLTF/Tests/UniGLTF/GltfJsonUtilTests.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bf03004ca38f9994c8feedbf3afc9352
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user