diff --git a/Assets/UniGLTF/Editor/Generator.meta b/Assets/UniGLTF/Editor/Generator.meta new file mode 100644 index 000000000..90501e8e6 --- /dev/null +++ b/Assets/UniGLTF/Editor/Generator.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e6ec48f8e3ffe6d4482a26fd34facfd6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Editor/Generator/DeserializerWriter.cs b/Assets/UniGLTF/Editor/Generator/DeserializerWriter.cs new file mode 100644 index 000000000..d5ce549cb --- /dev/null +++ b/Assets/UniGLTF/Editor/Generator/DeserializerWriter.cs @@ -0,0 +1,52 @@ +using System.IO; +using UniGLTF.JsonSchema; +using UniGLTF.JsonSchema.Schemas; + +namespace GenerateUniGLTFSerialization +{ + public static class DeserializerWriter + { + const string Begin = @"// This file is generated from JsonSchema. Don't modify this source code. +using UniJSON; +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UniGLTF.Extensions.$0 { + +public static class GltfDeserializer +{ + +public static bool TryGet(UniGLTF.glTFExtension src, out $0 extension) +{ + if(src is UniGLTF.glTFExtensionImport extensions) + { + foreach(var kv in extensions.ObjectItems()) + { + if(kv.Key.GetUtf8String() == $0.ExtensionNameUtf8) + { + extension = Deserialize(kv.Value); + return true; + } + } + } + + extension = default; + return false; +} + +"; + + const string End = @" +} // GltfDeserializer +} // UniGLTF +"; + + public static void Write(TextWriter w, JsonSchemaSource root, string rootName) + { + w.Write(Begin.Replace("$0", rootName)); + root.Create(true, rootName).GenerateDeserializer(new TraverseContext(w), "Deserialize"); + w.Write(End); + } + } +} diff --git a/Assets/UniGLTF/Editor/Generator/DeserializerWriter.cs.meta b/Assets/UniGLTF/Editor/Generator/DeserializerWriter.cs.meta new file mode 100644 index 000000000..e69bcc5c7 --- /dev/null +++ b/Assets/UniGLTF/Editor/Generator/DeserializerWriter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 60b1c040cdf4bad42a40c8467ae9923b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Editor/Generator/FormatWriter.cs b/Assets/UniGLTF/Editor/Generator/FormatWriter.cs new file mode 100644 index 000000000..d102bd91f --- /dev/null +++ b/Assets/UniGLTF/Editor/Generator/FormatWriter.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Generic; +using System.IO; +using UniGLTF.JsonSchema; +using UniGLTF.JsonSchema.Schemas; + +namespace GenerateUniGLTFSerialization +{ + class FormatWriter + { + TextWriter m_w; + string m_prefix; + + HashSet m_used = new HashSet(); + + FormatWriter(TextWriter writer, string prefix) + { + m_w = writer; + m_prefix = prefix; + } + + // static string UpperCamelCase(string src) + // { + // if(string.IsNullOrEmpty(src)) + // { + // return ""; + // } + // return src.Substring(0, 1).ToUpper() + src.Substring(1); + // } + + // string ClassName(string src) + // { + // if(string.IsNullOrEmpty(src)) + // { + // // root + // return m_prefix; + // } + // else{ + // return String.Join("", src.Split("__").Select(x => UpperCamelCase(x))); + // } + // } + + const string EnumStringAttr = "[JsonSchema(EnumSerializationType = EnumSerializationType.AsString)]"; + + static (string, string) PropType(JsonSchemaBase schema) + { + switch (schema.JsonSchemaType) + { + case JsonSchemaType.String: + case JsonSchemaType.Boolean: + case JsonSchemaType.Integer: + case JsonSchemaType.Number: + case JsonSchemaType.Object: + case JsonSchemaType.Array: + return (null, schema.ValueType); + + case JsonSchemaType.EnumString: + return (EnumStringAttr, schema.ValueType); + } + + throw new NotImplementedException(); + } + + const string FieldIndent = " "; + + void WriteObject(ObjectJsonSchema schema, string rootName = default) + { + if (m_used.Contains(schema.Title)) + { + return; + } + m_used.Add(schema.Title); + + var className = schema.Title; + m_w.Write($@" + public class {className} + {{ +"); + + if (!string.IsNullOrEmpty(rootName)) + { + var indent = " "; + m_w.WriteLine($"{indent}public const string ExtensionName = \"{rootName}\";"); + m_w.WriteLine($"{indent}public static readonly Utf8String ExtensionNameUtf8 = Utf8String.From(ExtensionName);"); + m_w.WriteLine(); + } + + var isFirst = true; + foreach (var kv in schema.Properties) + { + if (isFirst) + { + isFirst = false; + } + else + { + m_w.WriteLine(); + } + if (!string.IsNullOrEmpty(kv.Value.Description)) + { + m_w.WriteLine($"{FieldIndent}// {kv.Value.Description}"); + } + var (attr, propType) = PropType(kv.Value); + if (!string.IsNullOrEmpty(attr)) + { + m_w.WriteLine($"{FieldIndent}{attr}"); + } + m_w.WriteLine($"{FieldIndent}public {propType} {kv.Key.ToUpperCamel()};"); + } + + // close class + m_w.WriteLine(" }"); + } + + void WriteEnumString(EnumStringJsonSchema schema) + { + if (m_used.Contains(schema.Title)) + { + return; + } + m_used.Add(schema.Title); + + var className = schema.Title; + m_w.Write($@" + public enum {className} + {{ +"); + foreach (var value in schema.Values) + { + m_w.WriteLine($" {value},"); + } + + // close + m_w.Write(@" + } +"); + } + + void Traverse(JsonSchemaSource source, string rootName = default) + { + foreach (var child in source.Children()) + { + Traverse(child); + } + + switch (source.type) + { + case JsonSchemaType.Object: + { + var schema = source.Create(true); + if (schema is ObjectJsonSchema obj) + { + if (!string.IsNullOrEmpty(rootName)) + { + obj.Title = rootName; + } + WriteObject(obj, rootName); + } + else if (schema is ExtensionJsonSchema ext) + { + // WriteObject(ext); + } + else + { + throw new Exception(); + } + } + break; + + case JsonSchemaType.EnumString: + WriteEnumString(source.Create(true) as EnumStringJsonSchema); + break; + } + } + + public static void Write(TextWriter w, JsonSchemaSource root, string rootName) + { + w.Write($@"// This file is generated from JsonSchema. Don't modify this source code. +using System; +using System.Collections.Generic; +using UniGLTF; +using UniJSON; + +namespace UniGLTF.Extensions.{rootName} +{{ +"); + + new FormatWriter(w, root.title).Traverse(root, rootName); + + // close namespace + w.WriteLine("}"); + } + } +} diff --git a/Assets/UniGLTF/Editor/Generator/FormatWriter.cs.meta b/Assets/UniGLTF/Editor/Generator/FormatWriter.cs.meta new file mode 100644 index 000000000..ed5d4b36f --- /dev/null +++ b/Assets/UniGLTF/Editor/Generator/FormatWriter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1eff612a2dd70574db807fb58a21212b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Editor/Generator/Generator.cs b/Assets/UniGLTF/Editor/Generator/Generator.cs new file mode 100644 index 000000000..83e9f3499 --- /dev/null +++ b/Assets/UniGLTF/Editor/Generator/Generator.cs @@ -0,0 +1,104 @@ +using System; +using System.IO; +using System.Linq; +using UniGLTF.JsonSchema; + +namespace GenerateUniGLTFSerialization +{ + public class Generator + { + static void ClearFolder(DirectoryInfo dir) + { + Console.WriteLine($"clear: {dir}"); + + foreach (FileInfo file in dir.GetFiles()) + { + file.Delete(); + } + + foreach (DirectoryInfo child in dir.GetDirectories()) + { + child.Delete(true); + } + } + + static string CleanupTitle(string title) + { + if (string.IsNullOrEmpty(title)) + { + return title; + } + var splitted = title.Split().ToList(); + if (splitted.Last() == "extension") + { + splitted.RemoveAt(splitted.Count - 1); + } + return string.Join("", splitted + .Where(x => x.Length > 0) + .Select(x => x.Substring(0, 1).ToUpper() + x.Substring(1))); + } + + static string GetStem(string filename) + { + return filename.Split('.').First(); + } + + public static void GenerateTo(JsonSchemaSource root, DirectoryInfo dir, bool clearFolder) + { + // clear or create folder + if (dir.Exists) + { + if (dir.EnumerateFileSystemInfos().Any()) + { + if (!clearFolder) + { + Console.WriteLine($"{dir} is not empty."); + return; + } + + // clear + ClearFolder(dir); + } + } + else + { + Console.WriteLine($"create: {dir}"); + dir.Create(); + } + + foreach (var s in root.Traverse()) + { + // title を掃除 + s.title = CleanupTitle(s.title); + } + + { + var dst = Path.Combine(dir.FullName, "Format.g.cs"); + Console.WriteLine(dst); + using (var w = new StringWriter()) + { + FormatWriter.Write(w, root, GetStem(root.FilePath.Name)); + File.WriteAllText(dst, w.ToString().Replace("\r\n", "\n")); + } + } + { + var dst = Path.Combine(dir.FullName, "Deserializer.g.cs"); + Console.WriteLine(dst); + using (var w = new StringWriter()) + { + DeserializerWriter.Write(w, root, GetStem(root.FilePath.Name)); + File.WriteAllText(dst, w.ToString().Replace("\r\n", "\n")); + } + } + { + var dst = Path.Combine(dir.FullName, "Serializer.g.cs"); + Console.WriteLine(dst); + using (var w = new StringWriter()) + { + SerializerWriter.Write(w, root, GetStem(root.FilePath.Name)); + File.WriteAllText(dst, w.ToString().Replace("\r\n", "\n")); + } + } + } + } +} diff --git a/Assets/UniGLTF/Editor/Generator/Generator.cs.meta b/Assets/UniGLTF/Editor/Generator/Generator.cs.meta new file mode 100644 index 000000000..4c67f526e --- /dev/null +++ b/Assets/UniGLTF/Editor/Generator/Generator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 550a0a585e47bf44781b5c447169cc33 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Editor/Generator/SerializerWriter.cs b/Assets/UniGLTF/Editor/Generator/SerializerWriter.cs new file mode 100644 index 000000000..cfcb6c593 --- /dev/null +++ b/Assets/UniGLTF/Editor/Generator/SerializerWriter.cs @@ -0,0 +1,52 @@ +using System.IO; +using UniGLTF.JsonSchema; +using UniGLTF.JsonSchema.Schemas; + +namespace GenerateUniGLTFSerialization +{ + public static class SerializerWriter + { + const string Begin = @"// This file is generated from JsonSchema. Don't modify this source code. +using System; +using System.Collections.Generic; +using System.Linq; +using UniJSON; + +namespace UniGLTF.Extensions.$0 { + + static public class GltfSerializer + { + + public static void SerializeTo(ref UniGLTF.glTFExtension dst, $0 extension) + { + if (dst is glTFExtensionImport) + { + throw new NotImplementedException(); + } + + if (!(dst is glTFExtensionExport extensions)) + { + extensions = new glTFExtensionExport(); + dst = extensions; + } + + var f = new JsonFormatter(); + Serialize(f, extension); + extensions.Add($0.ExtensionName, f.GetStoreBytes()); + } + +"; + + const string End = @" + } // class +} // namespace +"; + + public static void Write(TextWriter w, JsonSchemaSource root, string rootName) + { + w.Write(Begin.Replace("$0", rootName)); + root.Create(true, rootName).GenerateSerializer(new TraverseContext(w), "Serialize"); + w.Write(End); + } + } +} diff --git a/Assets/UniGLTF/Editor/Generator/SerializerWriter.cs.meta b/Assets/UniGLTF/Editor/Generator/SerializerWriter.cs.meta new file mode 100644 index 000000000..2155ba1bb --- /dev/null +++ b/Assets/UniGLTF/Editor/Generator/SerializerWriter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8875ecbd55657e148a7483e597058ab3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Editor/JsonSchema.meta b/Assets/UniGLTF/Editor/JsonSchema.meta new file mode 100644 index 000000000..732b440cc --- /dev/null +++ b/Assets/UniGLTF/Editor/JsonSchema.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 734f2ba2c2ed11540ae52c5adb9900f7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Editor/JsonSchema/IndexTargets.cs b/Assets/UniGLTF/Editor/JsonSchema/IndexTargets.cs new file mode 100644 index 000000000..0b5568c42 --- /dev/null +++ b/Assets/UniGLTF/Editor/JsonSchema/IndexTargets.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; + +namespace UniGLTF.JsonSchema +{ + /// + /// JSON上の配列参照を列挙する + /// + public static class IndexTargets + { + public static Dictionary Map = new Dictionary{ + {".accessors[].bufferView", ".bufferViews"}, + {".accessors[].sparse.indices.bufferView", ".bufferViews"}, + {".accessors[].sparse.values.bufferView", ".bufferViews"}, + {".animations[].channels[].sampler", ".animations[{0}].samplers"}, + {".animations[].channels[].target.node", ".nodes"}, + {".animations[].samplers[].input", ".accessors"}, + {".animations[].samplers[].output", ".accessors"}, + {".bufferViews[].buffer", ".buffers"}, + {".images[].bufferView", ".bufferViews"}, + // {".materials[].extensions.KHR_materials_pbrSpecularGlossiness.diffuseTexture.index", ""}, + // {".materials[].extensions.KHR_materials_pbrSpecularGlossiness.specularGlossinessTexture.index", ""}, + {".materials[].pbrMetallicRoughness.baseColorTexture.index", ".textures"}, + {".materials[].pbrMetallicRoughness.metallicRoughnessTexture.index", ".textures"}, + {".materials[].normalTexture.index", ".textures"}, + {".materials[].occlusionTexture.index", ".textures"}, + {".materials[].emissiveTexture.index", ".textures"}, + // {".meshes[].primitives[].extensions.KHR_draco_mesh_compression.bufferView", ""}, + // {".meshes[].primitives[].extensions.KHR_draco_mesh_compression.attributes{}", ""}, + {".meshes[].primitives[].attributes{}", ".accessors"}, + {".meshes[].primitives[].indices", ".accessors"}, + {".meshes[].primitives[].material", ".materials"}, + {".meshes[].primitives[].targets[]{}", ".accessors"}, + {".nodes[].camera", ".cameras"}, + {".nodes[].children[]", ".nodes"}, + {".nodes[].skin", ".skins"}, + {".nodes[].mesh", ".meshes"}, + {".scene", ".scenes"}, + // {".scenes[].extensions.EXT_lights_image_based.light", ""}, + {".scenes[].nodes[]", ".nodes"}, + {".skins[].inverseBindMatrices", ".accessors"}, + {".skins[].skeleton", ".nodes"}, + {".skins[].joints[]", ".nodes"}, + {".textures[].sampler", ".samplers"}, + {".textures[].source", ".images"}, + // VRM + {".extensions.VRM.humanoid.humanBones[].node", ".nodes"}, + {".extensions.VRM.firstPerson.firstPersonBone", ".nodes"}, + {".extensions.VRM.firstPerson.meshAnnotations[].mesh", ".meshes"}, + {".extensions.VRM.blendShapeMaster.blendShapeGroups[].binds[].mesh", ".meshes"}, + // {".extensions.VRM.blendShapeMaster.blendShapeGroups[].binds[].index", ".meshes[i].primitives[*].targets"}, + // {".extensions.VRM.blendShapeMaster.blendShapeGroups[].materialValues[].materialName", ".materials"}, + // {".extensions.VRM.blendShapeMaster.blendShapeGroups[].materialValues[].propertyName", ""}, + {".extensions.VRM.secondaryAnimation.boneGroups[].center", ".nodes"}, + {".extensions.VRM.secondaryAnimation.boneGroups[].bones[]", ".nodes"}, + {".extensions.VRM.secondaryAnimation.boneGroups[].colliderGroups[]", ".extensions.VRM.secondaryAnimation.colliderGroups"}, + {".extensions.VRM.secondaryAnimation.colliderGroups[].node", ".nodes"}, + {".extensions.VRM.materialProperties[].textureProperties{}", ".textures"}, + }; + } +} diff --git a/Assets/UniGLTF/Editor/JsonSchema/IndexTargets.cs.meta b/Assets/UniGLTF/Editor/JsonSchema/IndexTargets.cs.meta new file mode 100644 index 000000000..5f2600ac4 --- /dev/null +++ b/Assets/UniGLTF/Editor/JsonSchema/IndexTargets.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6431a3223ebec22419298a43183912e3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Editor/JsonSchema/JsonSchemaParser.cs b/Assets/UniGLTF/Editor/JsonSchema/JsonSchemaParser.cs new file mode 100644 index 000000000..63ec527cb --- /dev/null +++ b/Assets/UniGLTF/Editor/JsonSchema/JsonSchemaParser.cs @@ -0,0 +1,408 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UniJSON; + +namespace UniGLTF.JsonSchema +{ + public class JsonSchemaParser + { + DirectoryInfo[] m_dir; + Dictionary m_cache = new Dictionary(); + + public JsonSchemaParser(params DirectoryInfo[] dir) + { + m_dir = dir; + } + + public static JsonSchemaSource Parse(string root, string jsonPath = "") + { + // setup + var path = new FileInfo(root); + var parser = new JsonSchemaParser(path.Directory); + + // traverse + return parser.Load(path.Name, jsonPath); + } + + public JsonSchemaSource Load(string fileName, string jsonPath) + { + JsonSchemaSource loaded = null; + foreach (var dir in m_dir) + { + var path = Path.Combine(dir.FullName, fileName); + if (File.Exists(path)) + { + loaded = Load(new FileInfo(path), jsonPath); + break; + } + } + + if (loaded is null) + { + throw new FileNotFoundException(fileName); + } + return loaded; + } + + public JsonSchemaSource Load(FileInfo path, string jsonPath) + { + if (!m_cache.TryGetValue(path, out byte[] bytes)) + { + // Console.WriteLine($"load {path}"); + bytes = File.ReadAllBytes(path.FullName); + m_cache.Add(path, bytes); + } + + { + var jsonSchema = Parse(bytes.ParseAsJson(), jsonPath); + jsonSchema.FilePath = path; + return jsonSchema; + } + } + + void MergeTo(JsonSchemaSource src, JsonSchemaSource dst) + { + if (string.IsNullOrEmpty(dst.title)) + { + dst.title = src.title; + } + if (string.IsNullOrEmpty(dst.description)) + { + dst.description = src.description; + } + if (src.type != JsonSchemaType.Unknown) + { + dst.type = src.type; + } + foreach (var kv in src.EnumerateProperties()) + { + dst.AddProperty(kv.Key, kv.Value); + } + if (src.enumStringValues != null) + { + dst.enumStringValues = src.enumStringValues.ToArray(); + } + } + + JsonSchemaSource Parse(ListTreeNode json, string jsonPath) + { + var source = new JsonSchemaSource + { + JsonPath = jsonPath, + }; + + foreach (var kv in json.ObjectItems()) + { + switch (kv.Key.GetString()) + { + case "$ref": + { + var reference = Load(kv.Value.GetString(), jsonPath); + MergeTo(reference, source); + break; + } + + case "allOf": + // glTF では継承として使われる + { + var reference = AllOf(kv.Value, jsonPath); + MergeTo(reference, source); + break; + } + + case "$schema": + break; + + case "type": + source.type = (JsonSchemaType)Enum.Parse(typeof(JsonSchemaType), kv.Value.GetString(), true); + break; + + case "title": + source.title = kv.Value.GetString(); + break; + + case "description": + source.description = kv.Value.GetString(); + break; + + case "gltf_detailedDescription": + source.gltfDetail = kv.Value.GetString(); + break; + + case "default": + break; + + case "gltf_webgl": + break; + + case "anyOf": + // glTF ではenumとして使われる + ParseAnyOfAsEnum(ref source, kv.Value); + break; + + case "oneOf": + // TODO: union 的な + break; + + case "not": + // TODO: プロパティの両立を禁止する、排他的な + break; + + case "pattern": + source.pattern = kv.Value.GetString(); + break; + + case "format": + // TODO + break; + + case "gltf_uriType": + // TODO + break; + + case "minimum": + if (source.type != JsonSchemaType.Number && source.type != JsonSchemaType.Integer) throw new Exception(); + source.minimum = kv.Value.GetDouble(); + break; + + case "exclusiveMinimum": + if (source.type != JsonSchemaType.Number && source.type != JsonSchemaType.Integer) throw new Exception(); + source.exclusiveMinimum = kv.Value.GetBoolean(); // ? + break; + + case "maximum": + if (source.type != JsonSchemaType.Number && source.type != JsonSchemaType.Integer) throw new Exception(); + source.maximum = kv.Value.GetDouble(); + break; + + case "multipleOf": + if (source.type != JsonSchemaType.Number && source.type != JsonSchemaType.Integer) throw new Exception(); + source.multipleOf = kv.Value.GetDouble(); + break; + + case "properties": + if (source.type != JsonSchemaType.Object) throw new Exception(); + // source.properties = new Dictionary(); + foreach (var prop in kv.Value.ObjectItems()) + { + var propJsonPath = $"{jsonPath}.{prop.Key.GetString()}"; + var propSchema = Parse(prop.Value, propJsonPath); + if (propSchema is null) + { + if (source.baseSchema is null) + { + // add empty object. extras + source.AddProperty(prop.Key.GetString(), new JsonSchemaSource + { + JsonPath = propJsonPath, + type = JsonSchemaType.Object, + }); + } + // else if (source.baseSchema.GetPropertyFromPath(propJsonPath)) + // { + // // ok + // } + else + { + throw new Exception("unknown"); + } + } + else + { + if (source.GetProperty(prop.Key.GetString()) == null) + { + source.AddProperty(prop.Key.GetString(), propSchema); + } + } + } + break; + + case "required": + source.required = kv.Value.ArrayItems().Select(x => x.GetString()).ToArray(); + break; + + case "dependencies": + // Property間の依存関係? + // TODO: + break; + + case "additionalProperties": + if (source.type != JsonSchemaType.Object) throw new Exception(); + if (kv.Value.Value.ValueType == ValueNodeType.Object) + { + source.additionalProperties = Parse(kv.Value, $"{jsonPath}{{}}"); + } + else if (kv.Value.Value.ValueType == ValueNodeType.Boolean && kv.Value.GetBoolean() == false) + { + // skip. do nothing + } + else + { + throw new NotImplementedException(); + } + break; + + case "minProperties": + if (source.type != JsonSchemaType.Object) throw new Exception(); + source.minProperties = kv.Value.GetInt32(); + break; + + case "items": + if (source.type != JsonSchemaType.Array) throw new Exception(); + source.items = Parse(kv.Value, $"{jsonPath}[]"); + break; + + case "uniqueItems": + if (source.type != JsonSchemaType.Array) throw new Exception(); + source.uniqueItems = kv.Value.GetBoolean(); + break; + + case "maxItems": + if (source.type != JsonSchemaType.Array) throw new Exception(); + source.maxItems = kv.Value.GetInt32(); + break; + + case "minItems": + if (source.type != JsonSchemaType.Array) throw new Exception(); + source.minItems = kv.Value.GetInt32(); + break; + + case "enum": + if (source.type == JsonSchemaType.String) + { + ParseStringEnum(ref source, kv.Value); + } + else if (source.type == JsonSchemaType.Integer + || source.type == JsonSchemaType.Number) + { + throw new NotImplementedException(); + } + else + { + throw new NotImplementedException(); + } + break; + + default: + Console.WriteLine($"unknown property: {kv.Key.GetString()} => {kv.Value}"); + break; + } + } + + return source; + } + + void ParseStringEnum(ref JsonSchemaSource source, ListTreeNode json) + { + source.enumStringValues = json.ArrayItems().Select(x => x.GetString()).ToArray(); + source.type = JsonSchemaType.EnumString; + } + + void ParseAnyOfAsEnum(ref JsonSchemaSource source, ListTreeNode json) + { + List values = new List(); + List stringValues = new List(); + List descriptions = new List(); + foreach (var v in json.ArrayItems()) + { + foreach (var kv in v.ObjectItems()) + { + switch (kv.Key.GetString()) + { + case "enum": + { + int i = 0; + foreach (var a in kv.Value.ArrayItems()) + { + switch (a.Value.ValueType) + { + case ValueNodeType.Number: + case ValueNodeType.Integer: + values.Add(a.GetInt32()); + break; + + case ValueNodeType.String: + stringValues.Add(a.GetString()); + break; + + default: + throw new NotImplementedException(); + } + ++i; + } + } + break; + + case "description": + { + descriptions.Add(kv.Value.GetString()); + } + break; + + case "type": + break; + + default: + throw new NotImplementedException(); + } + } + } + + if (stringValues.Count > 0) + { + if (values.Count == 0) + { + source.enumStringValues = stringValues.ToArray(); + source.type = JsonSchemaType.EnumString; + return; + } + } + + if (descriptions.Count == values.Count) + { + source.enumValues = new KeyValuePair[values.Count]; + for (int i = 0; i < values.Count; ++i) + { + source.enumValues[i] = new KeyValuePair + ( + descriptions[i], + values[i] + ); + } + source.type = JsonSchemaType.Enum; + return; + } + + throw new NotImplementedException(); + } + + JsonSchemaSource AllOf(ListTreeNode json, string jsonPath) + { + string refValue = null; + int count = 0; + foreach (var a in json.ArrayItems()) + { + foreach (var kv in a.ObjectItems()) + { + if (kv.Key.GetString() != "$ref") + { + throw new NotImplementedException(); + } + + refValue = kv.Value.GetString(); + + ++count; + } + } + if (count != 1) + { + throw new NotImplementedException(); + } + + var reference = Load(refValue, jsonPath); + return reference; + } + } +} diff --git a/Assets/UniGLTF/Editor/JsonSchema/JsonSchemaParser.cs.meta b/Assets/UniGLTF/Editor/JsonSchema/JsonSchemaParser.cs.meta new file mode 100644 index 000000000..6bc2d99eb --- /dev/null +++ b/Assets/UniGLTF/Editor/JsonSchema/JsonSchemaParser.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5540ebde94dc9924989c2c8066b736d7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Editor/JsonSchema/JsonSchemaSource.cs b/Assets/UniGLTF/Editor/JsonSchema/JsonSchemaSource.cs new file mode 100644 index 000000000..5835f4c68 --- /dev/null +++ b/Assets/UniGLTF/Editor/JsonSchema/JsonSchemaSource.cs @@ -0,0 +1,274 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace UniGLTF.JsonSchema +{ + /// + /// 型が確定する前にパースして値を集める + /// + public class JsonSchemaSource + { + public FileInfo FilePath; + + public string JsonPath; + public static (string, string) SplitParent(string jsonPath) + { + var splitted = jsonPath.Split('.'); + return (String.Join(".", splitted.Take(splitted.Length - 1)), splitted[splitted.Length - 1]); + } + public void AddJsonPath(string jsonPath, JsonSchemaSource source) + { + var (parent, child) = SplitParent(jsonPath); + var parentSchema = this.Get(parent); + var materialExtensions = parentSchema; + source.JsonPath = jsonPath; + materialExtensions.AddProperty(child, source); + } + public JsonSchemaSource Get(string jsonPath) + { + if (JsonPath == jsonPath) + { + return this; + } + + if (jsonPath.StartsWith(JsonPath)) + { + foreach (var child in Children()) + { + var found = child.Get(jsonPath); + if (found != null) + { + return found; + } + } + } + return null; + } + + public JsonSchemaType type; + public string title; + public string description; + public string gltfDetail; + + public JsonSchemaSource baseSchema; + + #region Number + public double? minimum; + public bool exclusiveMinimum; + public double? maximum; + + public double? multipleOf; + #endregion + + #region String + public string pattern; + #endregion + + #region Object + List> m_properties; + + public JsonSchemaSource GetProperty(string name, bool remove = false) + { + if (m_properties is null) + { + return null; + } + for (int i = 0; i < m_properties.Count; ++i) + { + if (m_properties[i].Key == name) + { + var found = m_properties[i].Value; + if (remove) + { + m_properties.RemoveAt(i); + } + return found; + } + } + + return null; + } + + public void AddProperty(string name, JsonSchemaSource prop) + { + if (name is null) + { + throw new ArgumentNullException(); + } + if (prop.type == JsonSchemaType.Unknown) + { + if (name == "extensions" || name == "extras") + { + // return; + prop.type = JsonSchemaType.Object; + } + else + { + throw new NotImplementedException(); + } + } + + if (m_properties is null) + { + m_properties = new List>(); + } + + if (m_properties.Any(x => x.Key == name)) + { + throw new ArgumentException($"{name}: is already exist"); + } + m_properties.Add(new KeyValuePair(name, prop)); + } + + public IEnumerable> EnumerateProperties() + { + if (m_properties != null) + { + foreach (var kv in m_properties) + { + yield return kv; + } + } + } + + public string[] required; + #endregion + + #region Dictionary + public JsonSchemaSource additionalProperties; + public int? minProperties; + #endregion + + #region Array + public JsonSchemaSource items; + public int? minItems; + public int? maxItems; + public bool? uniqueItems; + #endregion + + #region Enum + public KeyValuePair[] enumValues; + public string[] enumStringValues; + #endregion + + public IEnumerable Children() + { + if (m_properties != null) + { + foreach (var kv in m_properties) + { + yield return kv.Value; + } + } + else if (additionalProperties != null) + { + yield return additionalProperties; + } + else if (items != null) + { + if (type != JsonSchemaType.Array) + { + throw new NotImplementedException(); + } + yield return items; + } + } + + public IEnumerable Traverse() + { + yield return this; + + if (m_properties != null) + { + foreach (var kv in m_properties) + { + foreach (var x in kv.Value.Traverse()) + { + yield return x; + } + } + } + else if (additionalProperties != null) + { + foreach (var x in additionalProperties.Traverse()) + { + yield return x; + } + } + else if (items != null) + { + foreach (var x in items.Traverse()) + { + yield return x; + } + } + } + + public Schemas.JsonSchemaBase Create(bool useUpperCamelName, string rootName = default) + { + // if (baseSchema != null) + // { + // baseSchema.MergeTo(this); + // } + + if (baseSchema != null) + { + if (type == JsonSchemaType.Unknown) + { + type = baseSchema.type; + } + } + + switch (type) + { + case JsonSchemaType.Object: + if (this.JsonPath.EndsWith(".extensions") || this.JsonPath.EndsWith(".extras")) + { + return new Schemas.ExtensionJsonSchema(this); + } + + if ((m_properties != null && m_properties.Any()) || additionalProperties is null) + { + var obj = new Schemas.ObjectJsonSchema(this, useUpperCamelName); + if (!string.IsNullOrEmpty(rootName)) + { + obj.Title = rootName; + } + return obj; + } + else + { + return new Schemas.DictionaryJsonSchema(this, useUpperCamelName); + } + case JsonSchemaType.Array: + return new Schemas.ArrayJsonSchema(this, useUpperCamelName); + case JsonSchemaType.Boolean: + return new Schemas.BoolJsonSchema(this); + case JsonSchemaType.String: + return new Schemas.StringJsonSchema(this); + case JsonSchemaType.Number: + return new Schemas.NumberJsonSchema(this); + case JsonSchemaType.Integer: + return new Schemas.IntegerJsonSchema(this); + case JsonSchemaType.Enum: + return new Schemas.EnumJsonSchema(this); + case JsonSchemaType.EnumString: + return new Schemas.EnumStringJsonSchema(this); + default: + return null; + } + } + + public void Dump(string indent = "") + { + Console.WriteLine($"{indent}{JsonPath}: {type}"); + + foreach (var x in Children()) + { + x.Dump(indent + " "); + } + } + } +} diff --git a/Assets/UniGLTF/Editor/JsonSchema/JsonSchemaSource.cs.meta b/Assets/UniGLTF/Editor/JsonSchema/JsonSchemaSource.cs.meta new file mode 100644 index 000000000..e04c56711 --- /dev/null +++ b/Assets/UniGLTF/Editor/JsonSchema/JsonSchemaSource.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 901c2cd9dc849e94296e50965f438e0e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Editor/JsonSchema/JsonSchemaType.cs b/Assets/UniGLTF/Editor/JsonSchema/JsonSchemaType.cs new file mode 100644 index 000000000..0ca2d553d --- /dev/null +++ b/Assets/UniGLTF/Editor/JsonSchema/JsonSchemaType.cs @@ -0,0 +1,15 @@ +namespace UniGLTF.JsonSchema +{ + public enum JsonSchemaType + { + Unknown, + Object, + Array, + String, + Number, + Integer, + Boolean, + Enum, + EnumString, + } +} diff --git a/Assets/UniGLTF/Editor/JsonSchema/JsonSchemaType.cs.meta b/Assets/UniGLTF/Editor/JsonSchema/JsonSchemaType.cs.meta new file mode 100644 index 000000000..41e914516 --- /dev/null +++ b/Assets/UniGLTF/Editor/JsonSchema/JsonSchemaType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e52b7d2274918294ebac38aba0c9fb1c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Editor/JsonSchema/Schemas.meta b/Assets/UniGLTF/Editor/JsonSchema/Schemas.meta new file mode 100644 index 000000000..3f09e6d7f --- /dev/null +++ b/Assets/UniGLTF/Editor/JsonSchema/Schemas.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 707d9adcac44ca2409a9903f2e25f3df +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Editor/JsonSchema/Schemas/ArrayJsonSchema.cs b/Assets/UniGLTF/Editor/JsonSchema/Schemas/ArrayJsonSchema.cs new file mode 100644 index 000000000..1eb46b6fd --- /dev/null +++ b/Assets/UniGLTF/Editor/JsonSchema/Schemas/ArrayJsonSchema.cs @@ -0,0 +1,151 @@ +using System; +using System.IO; + +namespace UniGLTF.JsonSchema.Schemas +{ + public class ArrayJsonSchema : JsonSchemaBase + { + public readonly JsonSchemaBase Items; + public readonly bool UniqueItems; + public readonly int MinItems; + public readonly int? MaxItems; + + public ArrayJsonSchema(in JsonSchemaSource source, bool useUpperCamelName) : base(source) + { + Items = source.items.Create(useUpperCamelName); + Items.IsArrayItem = true; + UniqueItems = source.uniqueItems.GetValueOrDefault(); + MinItems = source.minItems.GetValueOrDefault(); + if (source.maxItems.HasValue) + { + MaxItems = source.maxItems.Value; + } + } + + public override bool IsInline => false; + + bool ItemsIsPrimitiveType => Items is PrimitiveJsonSchemaBase; + + public override string ValueType + { + get + { + if (ItemsIsPrimitiveType) + { + return $"{Items.ValueType}[]"; + } + else + { + return $"List<{Items.ValueType}>"; + } + } + } + + public override string GenerateDeserializerCall(string callName, string argName) + { + return $"{callName}({argName})"; + } + + public override void GenerateDeserializer(TraverseContext writer, string callName) + { + if (writer.Used.Contains(callName)) + { + return; + } + writer.Used.Add(callName); + + var itemCallName = callName + "_ITEM"; + + if (ItemsIsPrimitiveType) + { + + writer.Write(@" +public static $0 $2(ListTreeNode parsed) +{ + var value = new $1[parsed.GetArrayCount()]; + int i=0; + foreach(var x in parsed.ArrayItems()) + { + value[i++] = $3; + } + return value; +} +" + .Replace("$0", ValueType) + .Replace("$1", Items.ValueType) + .Replace("$2", callName) + .Replace("$3", Items.GenerateDeserializerCall(itemCallName, "x")) + ); + + } + else + { + writer.Write(@" +public static $0 $2(ListTreeNode parsed) +{ + var value = new $1(); + foreach(var x in parsed.ArrayItems()) + { + value.Add($3); + } + return value; +} +" + .Replace("$0", ValueType) + .Replace("$1", ValueType) + .Replace("$2", callName) + .Replace("$3", Items.GenerateDeserializerCall(itemCallName, "x")) + ); + + } + + if (!Items.IsInline) + { + Items.GenerateDeserializer(writer, itemCallName); + } + } + + public override string CreateSerializationCondition(string argName) + { + return $"{argName}!=null&&{argName}.Count()>={MinItems}"; + } + + public override string GenerateSerializerCall(string callName, string argName) + { + return $"{callName}(f, {argName})"; + } + + public override void GenerateSerializer(TraverseContext writer, string callName) + { + if (writer.Used.Contains(callName)) + { + return; + } + writer.Used.Add(callName); + + var itemCallName = callName + "_ITEM"; + writer.Write($@" +public static void {callName}(JsonFormatter f, {ValueType} value) +{{ + f.BeginList(); + + foreach(var item in value) + {{ + " +); + + writer.Write($"{Items.GenerateSerializerCall(itemCallName, "item")};\n"); + + writer.Write(@" + } + f.EndList(); +} +"); + + if (!Items.IsInline) + { + Items.GenerateSerializer(writer, itemCallName); + } + } + } +} diff --git a/Assets/UniGLTF/Editor/JsonSchema/Schemas/ArrayJsonSchema.cs.meta b/Assets/UniGLTF/Editor/JsonSchema/Schemas/ArrayJsonSchema.cs.meta new file mode 100644 index 000000000..a8649fcb1 --- /dev/null +++ b/Assets/UniGLTF/Editor/JsonSchema/Schemas/ArrayJsonSchema.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dd2ee0b1bf849a94bb504ba32e107418 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Editor/JsonSchema/Schemas/DictionaryJsonSchema.cs b/Assets/UniGLTF/Editor/JsonSchema/Schemas/DictionaryJsonSchema.cs new file mode 100644 index 000000000..e90b74d07 --- /dev/null +++ b/Assets/UniGLTF/Editor/JsonSchema/Schemas/DictionaryJsonSchema.cs @@ -0,0 +1,36 @@ +using System; + +namespace UniGLTF.JsonSchema.Schemas +{ + public class DictionaryJsonSchema : JsonSchemaBase + { + public readonly JsonSchemaBase AdditionalProperties; + + public readonly int MinProperties; + + public DictionaryJsonSchema(in JsonSchemaSource source, bool useUpperCamelName) : base(source) + { + AdditionalProperties = source.additionalProperties.Create(useUpperCamelName); + MinProperties = source.minProperties.GetValueOrDefault(); + } + + public override string ValueType => throw new NotImplementedException(); + + public override bool IsInline => throw new NotImplementedException(); + + public override string CreateSerializationCondition(string argName) + { + throw new NotImplementedException(); + } + + public override string GenerateDeserializerCall(string callName, string argName) + { + throw new NotImplementedException(); + } + + public override string GenerateSerializerCall(string callName, string argName) + { + throw new NotImplementedException(); + } + } +} diff --git a/Assets/UniGLTF/Editor/JsonSchema/Schemas/DictionaryJsonSchema.cs.meta b/Assets/UniGLTF/Editor/JsonSchema/Schemas/DictionaryJsonSchema.cs.meta new file mode 100644 index 000000000..d51459a0a --- /dev/null +++ b/Assets/UniGLTF/Editor/JsonSchema/Schemas/DictionaryJsonSchema.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4aa7a0339438a054781dbdf96e97ae9e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Editor/JsonSchema/Schemas/EnumJsonSchema.cs b/Assets/UniGLTF/Editor/JsonSchema/Schemas/EnumJsonSchema.cs new file mode 100644 index 000000000..47c0e63c2 --- /dev/null +++ b/Assets/UniGLTF/Editor/JsonSchema/Schemas/EnumJsonSchema.cs @@ -0,0 +1,90 @@ +using System; +using System.Linq; + +namespace UniGLTF.JsonSchema.Schemas +{ + public struct EnumValue + { + public string Name; + public int Value; + + public override string ToString() + { + return $"{Name}={Value}"; + } + } + + public class EnumJsonSchema : JsonSchemaBase + { + public readonly EnumValue[] Values; + + public EnumJsonSchema(in JsonSchemaSource source) : base(source) + { + Values = source.enumValues.Select(x => new EnumValue + { + Name = x.Key, + Value = x.Value + }).ToArray(); + } + + public override string ValueType => Title; + + public override bool IsInline => true; + + public override string GenerateDeserializerCall(string callName, string argName) + { + return $"({ValueType})argName"; + } + + public override string CreateSerializationCondition(string argName) + { + return "true"; + } + + public override string GenerateSerializerCall(string callName, string argName) + { + return $"f.Value((int){argName})"; + } + + public override string ToString() + { + var values = string.Join(", ", Values); + return $"{base.ToString()} {{{values}}}"; + } + } + + public class EnumStringJsonSchema : JsonSchemaBase + { + public readonly String[] Values; + + public EnumStringJsonSchema(in JsonSchemaSource source) : base(source) + { + Values = source.enumStringValues; + } + + public override string ValueType => Title; + + public override bool IsInline => true; + + public override string GenerateDeserializerCall(string callName, string argName) + { + return $"({ValueType})Enum.Parse(typeof({ValueType}), {argName}.GetString(), true)"; + } + + public override string CreateSerializationCondition(string argName) + { + return "true"; + } + + public override string GenerateSerializerCall(string callName, string argName) + { + return $"f.Value({argName}.ToString())"; + } + + public override string ToString() + { + var values = string.Join(", ", Values); + return $"{base.ToString()} {{{values}}}"; + } + } +} diff --git a/Assets/UniGLTF/Editor/JsonSchema/Schemas/EnumJsonSchema.cs.meta b/Assets/UniGLTF/Editor/JsonSchema/Schemas/EnumJsonSchema.cs.meta new file mode 100644 index 000000000..aab8c405f --- /dev/null +++ b/Assets/UniGLTF/Editor/JsonSchema/Schemas/EnumJsonSchema.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1479130f877f4d24f8ff772a56ad631e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Editor/JsonSchema/Schemas/ExtensionJsonSchema.cs b/Assets/UniGLTF/Editor/JsonSchema/Schemas/ExtensionJsonSchema.cs new file mode 100644 index 000000000..11cd401e1 --- /dev/null +++ b/Assets/UniGLTF/Editor/JsonSchema/Schemas/ExtensionJsonSchema.cs @@ -0,0 +1,31 @@ +namespace UniGLTF.JsonSchema.Schemas +{ + /// + /// glTF の extensions, extras を処理するための専用クラス + /// + public class ExtensionJsonSchema : JsonSchemaBase + { + public ExtensionJsonSchema(in JsonSchemaSource source) : base(source) + { + } + + public override string ValueType => "glTFExtension"; + + public override bool IsInline => true; + + public override string GenerateDeserializerCall(string callName, string argName) + { + return $"new glTFExtensionImport({argName})"; + } + + public override string CreateSerializationCondition(string argName) + { + return $"{argName}!=null"; + } + + public override string GenerateSerializerCall(string callName, string argName) + { + return $"{argName}.Serialize(f)"; + } + } +} diff --git a/Assets/UniGLTF/Editor/JsonSchema/Schemas/ExtensionJsonSchema.cs.meta b/Assets/UniGLTF/Editor/JsonSchema/Schemas/ExtensionJsonSchema.cs.meta new file mode 100644 index 000000000..85d917b63 --- /dev/null +++ b/Assets/UniGLTF/Editor/JsonSchema/Schemas/ExtensionJsonSchema.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 754fd3156f24fc648bfe7cb766cf88f0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Editor/JsonSchema/Schemas/JsonSchemaBase.cs b/Assets/UniGLTF/Editor/JsonSchema/Schemas/JsonSchemaBase.cs new file mode 100644 index 000000000..cf28224e5 --- /dev/null +++ b/Assets/UniGLTF/Editor/JsonSchema/Schemas/JsonSchemaBase.cs @@ -0,0 +1,104 @@ +using System; +using System.IO; +using System.Text; + +namespace UniGLTF.JsonSchema.Schemas +{ + public abstract class JsonSchemaBase + { + public readonly string JsonPath; + + public string ClassName => JsonPath + .Replace(".", "__") + .Replace("[]", "_ITEM") + .Replace("{}", "_PROP") + ; + + public readonly JsonSchemaType JsonSchemaType; + public string Title; + public readonly string Description; + + /// HardCoding + public string HardCode; + + public JsonSchemaBase(in JsonSchemaSource source) + { + JsonPath = source.JsonPath; + JsonSchemaType = source.type; + Title = source.title; + Description = source.description; + } + + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append("["); + sb.Append(JsonSchemaType); + sb.Append("]"); + if (!string.IsNullOrEmpty(Title)) + { + sb.Append($" \"{Title}\""); + } + return sb.ToString(); + } + + public bool IsArrayItem; + + /// + /// CSharpの型 + /// + /// + public abstract string ValueType { get; } + + /// + /// Use or not GenerateDeserializer and GenerateSerializer + /// + public abstract bool IsInline { get; } + + /// + /// Deserializer の呼び出し + /// + /// + /// + /// + public abstract string GenerateDeserializerCall(string callName, string argName); + + /// + /// Deserializer の実装 + /// + /// + /// + public virtual void GenerateDeserializer(TraverseContext writer, string callName) + { + throw new NotImplementedException(); + } + + /// + /// Serialize 時に出力するか否か + /// + /// * null や -1 などの無効な値のキーをスキップするために使う + /// + /// + /// + /// + public abstract string CreateSerializationCondition(string argName); + + /// + /// Serializer の呼び出し + /// + /// + /// + /// + public abstract string GenerateSerializerCall(string callName, string argName); + + /// + /// Serializer 実装 + /// + /// + /// + public virtual void GenerateSerializer(TraverseContext writer, string callName) + { + throw new NotImplementedException(); + } + } +} diff --git a/Assets/UniGLTF/Editor/JsonSchema/Schemas/JsonSchemaBase.cs.meta b/Assets/UniGLTF/Editor/JsonSchema/Schemas/JsonSchemaBase.cs.meta new file mode 100644 index 000000000..d7a04d665 --- /dev/null +++ b/Assets/UniGLTF/Editor/JsonSchema/Schemas/JsonSchemaBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5dd0100b5b0d18f45ad1485ee493b439 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Editor/JsonSchema/Schemas/JsonSchemaBaseExtensions.cs b/Assets/UniGLTF/Editor/JsonSchema/Schemas/JsonSchemaBaseExtensions.cs new file mode 100644 index 000000000..a5a344040 --- /dev/null +++ b/Assets/UniGLTF/Editor/JsonSchema/Schemas/JsonSchemaBaseExtensions.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace UniGLTF.JsonSchema.Schemas +{ + public static class JsonSchemaBaseExtensions + { + + + } +} diff --git a/Assets/UniGLTF/Editor/JsonSchema/Schemas/JsonSchemaBaseExtensions.cs.meta b/Assets/UniGLTF/Editor/JsonSchema/Schemas/JsonSchemaBaseExtensions.cs.meta new file mode 100644 index 000000000..78e4da652 --- /dev/null +++ b/Assets/UniGLTF/Editor/JsonSchema/Schemas/JsonSchemaBaseExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e345d604804282c4881ea5b2e580b237 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Editor/JsonSchema/Schemas/ObjectJsonSchema.cs b/Assets/UniGLTF/Editor/JsonSchema/Schemas/ObjectJsonSchema.cs new file mode 100644 index 000000000..6bd0fe746 --- /dev/null +++ b/Assets/UniGLTF/Editor/JsonSchema/Schemas/ObjectJsonSchema.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace UniGLTF.JsonSchema.Schemas +{ + public class ObjectJsonSchema : JsonSchemaBase + { + public string[] Required; + + public readonly Dictionary Properties = new Dictionary(); + + bool m_useUpperCamelName; + + public ObjectJsonSchema(in JsonSchemaSource source, bool useUpperCamelName) : base(source) + { + m_useUpperCamelName = useUpperCamelName; + + foreach (var kv in source.EnumerateProperties()) + { + var prop = kv.Value.Create(useUpperCamelName); + if (prop is null) + { + throw new NotImplementedException(); + } + + var key = kv.Key; + Properties.Add(key, prop); + } + Required = source.required; + } + + public override string ToString() + { + var values = ""; + if (Required != null && Required.Any()) + { + values = $" require: {{{string.Join(", ", Required)}}}"; + } + return $"{base.ToString()}{values}"; + } + + public override string ValueType => Title; + + public override bool IsInline => false; + + public override string GenerateDeserializerCall(string callName, string argName) + { + return $"{callName}({argName})"; + } + + public override void GenerateDeserializer(TraverseContext writer, string callName) + { + if (writer.Used.Contains(callName)) + { + return; + } + writer.Used.Add(callName); + + writer.Write(@" +public static $0 $2(ListTreeNode parsed) +{ + var value = new $0(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); +" +.Replace("$0", ValueType) +.Replace("$2", callName) +); + + foreach (var kv in Properties) + { + writer.Write(@" + if(key==""$0""){ + value.$2 = $1; + continue; + } +" +.Replace("$0", kv.Key) +.Replace("$1", kv.Value.GenerateDeserializerCall($"Deserialize_{kv.Key.ToUpperCamel()}", "kv.Value")) +.Replace("$2", kv.Key.ToUpperCamel()) +); + } + + writer.Write(@" + } + return value; +} +"); + + foreach (var kv in Properties) + { + if (!kv.Value.IsInline) + { + kv.Value.GenerateDeserializer(writer, $"Deserialize_{kv.Key.ToUpperCamel()}"); + } + } + } + + public override string CreateSerializationCondition(string argName) + { + return $"{argName}!=null"; + } + + /// + /// シリアライザーのコード生成 + /// + /// ObjectのFieldのみ値によって、出力するか否かの判定が必用。 + /// + /// 例: 空文字列は出力しない + /// + /// + /// + /// + public override void GenerateSerializer(TraverseContext writer, string callName) + { + if (writer.Used.Contains(callName)) + { + return; + } + writer.Used.Add(callName); + + writer.Write($@" +public static void {callName}(JsonFormatter f, {ValueType} value) +{{ + f.BeginMap(); + +" +); + + foreach (var kv in Properties) + { + var valueName = $"value.{kv.Key.ToUpperCamel()}"; + var condition = ""; + writer.Write($@" + if({kv.Value.CreateSerializationCondition(valueName)}{condition}){{ + f.Key(""{kv.Key}""); + {kv.Value.GenerateSerializerCall($"Serialize_{kv.Key.ToUpperCamel()}", valueName)}; + }} +"); + } + + writer.Write(@" + f.EndMap(); +} +"); + + foreach (var kv in Properties) + { + if (!kv.Value.IsInline) + { + kv.Value.GenerateSerializer(writer, $"Serialize_{kv.Key.ToUpperCamel()}"); + } + } + } + + public override string GenerateSerializerCall(string callName, string argName) + { + return $"{callName}(f, {argName})"; + } + } +} diff --git a/Assets/UniGLTF/Editor/JsonSchema/Schemas/ObjectJsonSchema.cs.meta b/Assets/UniGLTF/Editor/JsonSchema/Schemas/ObjectJsonSchema.cs.meta new file mode 100644 index 000000000..2ac78faaa --- /dev/null +++ b/Assets/UniGLTF/Editor/JsonSchema/Schemas/ObjectJsonSchema.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ae9dcedb49a42f9449672e00b34e8598 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Editor/JsonSchema/Schemas/PrimitiveJsonSchema.cs b/Assets/UniGLTF/Editor/JsonSchema/Schemas/PrimitiveJsonSchema.cs new file mode 100644 index 000000000..ebc7b75a2 --- /dev/null +++ b/Assets/UniGLTF/Editor/JsonSchema/Schemas/PrimitiveJsonSchema.cs @@ -0,0 +1,117 @@ +using System; + +namespace UniGLTF.JsonSchema.Schemas +{ + public abstract class PrimitiveJsonSchemaBase : JsonSchemaBase + { + protected PrimitiveJsonSchemaBase(in JsonSchemaSource source) : base(source) + { } + + public override bool IsInline => true; + + public override string CreateSerializationCondition(string argName) + { + if (IsArrayItem) + { + throw new NotImplementedException(); + } + else + { + return $"{argName}.HasValue"; + } + } + + public override string GenerateSerializerCall(string callName, string argName) + { + if (IsArrayItem) + { + return $"f.Value({argName})"; + } + else + { + return $"f.Value({argName}.GetValueOrDefault())"; + } + } + } + + public class BoolJsonSchema : PrimitiveJsonSchemaBase + { + public override string ValueType => IsArrayItem ? "bool" : "bool?"; + + public BoolJsonSchema(in JsonSchemaSource src) : base(src) + { + } + + public override string GenerateDeserializerCall(string callName, string argName) + { + return $"{argName}.GetBoolean()"; + } + } + + public class IntegerJsonSchema : PrimitiveJsonSchemaBase + { + public readonly int? Minimum; + public readonly bool ExclusiveMinimum; + public readonly int? Maximum; + public readonly int? MultipleOf; + + public string IndexTargetJsonPath; + + public IntegerJsonSchema(in JsonSchemaSource source) : base(source) + { + if (source.minimum.HasValue) + { + Minimum = (int)source.minimum.Value; + } + ExclusiveMinimum = source.exclusiveMinimum; + if (source.maximum.HasValue) + { + Maximum = (int)source.maximum.Value; + } + if (source.multipleOf.HasValue) + { + MultipleOf = (int)source.multipleOf.Value; + } + } + + public override string ValueType => IsArrayItem ? "int" : "int?"; + + public override string GenerateDeserializerCall(string callName, string argName) + { + return $"{argName}.GetInt32()"; + } + } + + public class NumberJsonSchema : PrimitiveJsonSchemaBase + { + public readonly double? Minimum; + public readonly bool ExclusiveMinimum; + public readonly double? Maximum; + public readonly double? MultipleOf; + + public NumberJsonSchema(in JsonSchemaSource source) : base(source) + { + if (source.minimum.HasValue) + { + Minimum = source.minimum.Value; + } + ExclusiveMinimum = source.exclusiveMinimum; + if (source.maximum.HasValue) + { + Maximum = source.maximum.Value; + } + if (source.multipleOf.HasValue) + { + MultipleOf = source.multipleOf.Value; + } + } + + public override string ValueType => IsArrayItem ? "float" : "float?"; + + public override string GenerateDeserializerCall(string callName, string argName) + { + return $"{argName}.GetSingle()"; + } + } + +} diff --git a/Assets/UniGLTF/Editor/JsonSchema/Schemas/PrimitiveJsonSchema.cs.meta b/Assets/UniGLTF/Editor/JsonSchema/Schemas/PrimitiveJsonSchema.cs.meta new file mode 100644 index 000000000..b794e0c42 --- /dev/null +++ b/Assets/UniGLTF/Editor/JsonSchema/Schemas/PrimitiveJsonSchema.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e64153bf38e165643bb501c5967734d2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Editor/JsonSchema/Schemas/StringJsonSchema.cs b/Assets/UniGLTF/Editor/JsonSchema/Schemas/StringJsonSchema.cs new file mode 100644 index 000000000..ee2f6ac4a --- /dev/null +++ b/Assets/UniGLTF/Editor/JsonSchema/Schemas/StringJsonSchema.cs @@ -0,0 +1,31 @@ +namespace UniGLTF.JsonSchema.Schemas +{ + public class StringJsonSchema : JsonSchemaBase + { + public readonly string Pattern; + + public StringJsonSchema(in JsonSchemaSource source) : base(source) + { + Pattern = source.pattern; + } + + public override string ValueType => "string"; + + public override bool IsInline => true; + + public override string CreateSerializationCondition(string argName) + { + return $"!string.IsNullOrEmpty({argName})"; + } + + public override string GenerateDeserializerCall(string callName, string argName) + { + return $"{argName}.GetString()"; + } + + public override string GenerateSerializerCall(string callName, string argName) + { + return $"f.Value({argName})"; + } + } +} diff --git a/Assets/UniGLTF/Editor/JsonSchema/Schemas/StringJsonSchema.cs.meta b/Assets/UniGLTF/Editor/JsonSchema/Schemas/StringJsonSchema.cs.meta new file mode 100644 index 000000000..ada6f5be3 --- /dev/null +++ b/Assets/UniGLTF/Editor/JsonSchema/Schemas/StringJsonSchema.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b5c3f6cf80367e542b1464a316fe868b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Editor/JsonSchema/Schemas/TraverseContext.cs b/Assets/UniGLTF/Editor/JsonSchema/Schemas/TraverseContext.cs new file mode 100644 index 000000000..a8aea95cb --- /dev/null +++ b/Assets/UniGLTF/Editor/JsonSchema/Schemas/TraverseContext.cs @@ -0,0 +1,22 @@ +using System.IO; +using System.Collections.Generic; + +namespace UniGLTF.JsonSchema.Schemas +{ + public class TraverseContext + { + public readonly TextWriter Writer; + + public readonly HashSet Used = new HashSet(); + + public TraverseContext(TextWriter writer) + { + Writer = writer; + } + + public void Write(string s) + { + Writer.Write(s); + } + } +} diff --git a/Assets/UniGLTF/Editor/JsonSchema/Schemas/TraverseContext.cs.meta b/Assets/UniGLTF/Editor/JsonSchema/Schemas/TraverseContext.cs.meta new file mode 100644 index 000000000..21993f5dd --- /dev/null +++ b/Assets/UniGLTF/Editor/JsonSchema/Schemas/TraverseContext.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f169b3199e32ab24c82c6968e6113f14 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Editor/JsonSchema/StringExtensions.cs b/Assets/UniGLTF/Editor/JsonSchema/StringExtensions.cs new file mode 100644 index 000000000..127a1c401 --- /dev/null +++ b/Assets/UniGLTF/Editor/JsonSchema/StringExtensions.cs @@ -0,0 +1,10 @@ +namespace UniGLTF.JsonSchema +{ + public static class StringExtensions + { + public static string ToUpperCamel(this string key) + { + return key.Substring(0, 1).ToUpper() + key.Substring(1); + } + } +} diff --git a/Assets/UniGLTF/Editor/JsonSchema/StringExtensions.cs.meta b/Assets/UniGLTF/Editor/JsonSchema/StringExtensions.cs.meta new file mode 100644 index 000000000..6bb1f5a9e --- /dev/null +++ b/Assets/UniGLTF/Editor/JsonSchema/StringExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 79c912c0da0179f42889a1746c8d4741 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/MeshUtility/Editor/MeshUtility.Editor.asmdef b/Assets/UniGLTF/MeshUtility/Editor/MeshUtility.Editor.asmdef index e07e9f2cf..aaba87cbc 100644 --- a/Assets/UniGLTF/MeshUtility/Editor/MeshUtility.Editor.asmdef +++ b/Assets/UniGLTF/MeshUtility/Editor/MeshUtility.Editor.asmdef @@ -2,6 +2,7 @@ "name": "MeshUtility.Editor", "references": [ "MeshUtility", + "VrmLib", "ShaderProperty.Runtime" ], "optionalUnityReferences": [], diff --git a/Assets/UniGLTF/Runtime/Extensions/ArrayExtensions.cs b/Assets/UniGLTF/Runtime/Extensions/ArrayExtensions.cs index e28f4461e..8b0f2774b 100644 --- a/Assets/UniGLTF/Runtime/Extensions/ArrayExtensions.cs +++ b/Assets/UniGLTF/Runtime/Extensions/ArrayExtensions.cs @@ -99,13 +99,6 @@ namespace UniGLTF return size; } - public static Byte[] ToArray(this ArraySegment src) - { - var dst = new byte[src.Count]; - Array.Copy(src.Array, src.Offset, dst, 0, src.Count); - return dst; - } - public static T[] SelectInplace(this T[] src, Func pred) { for (int i = 0; i < src.Length; ++i) @@ -116,7 +109,7 @@ namespace UniGLTF } public static void Copy(ArraySegment src, ArraySegment dst) - where TFrom: struct + where TFrom : struct where TTo : struct { var bytes = new byte[src.Count * Marshal.SizeOf(typeof(TFrom))]; diff --git a/Assets/UniGLTF/Runtime/UniGLTF/Format/ExtensionsAndExtras/KHR_materials_unlit.cs b/Assets/UniGLTF/Runtime/UniGLTF/Format/ExtensionsAndExtras/KHR_materials_unlit.cs index e412515bc..90f81fb29 100644 --- a/Assets/UniGLTF/Runtime/UniGLTF/Format/ExtensionsAndExtras/KHR_materials_unlit.cs +++ b/Assets/UniGLTF/Runtime/UniGLTF/Format/ExtensionsAndExtras/KHR_materials_unlit.cs @@ -11,7 +11,7 @@ namespace UniGLTF public static readonly Utf8String ExtensionNameUtf8 = Utf8String.From(ExtensionName); - static readonly byte[] Raw = new byte[] { (byte)'{', (byte)'}' }; + public static readonly byte[] Raw = new byte[] { (byte)'{', (byte)'}' }; public static glTFMaterial CreateDefault() { diff --git a/Assets/UniGLTF/Runtime/UniGLTF/Format/ExtensionsAndExtras/KHR_texture_transform.cs b/Assets/UniGLTF/Runtime/UniGLTF/Format/ExtensionsAndExtras/KHR_texture_transform.cs index e90ed8d8d..c48bd4b1e 100644 --- a/Assets/UniGLTF/Runtime/UniGLTF/Format/ExtensionsAndExtras/KHR_texture_transform.cs +++ b/Assets/UniGLTF/Runtime/UniGLTF/Format/ExtensionsAndExtras/KHR_texture_transform.cs @@ -73,7 +73,7 @@ namespace UniGLTF public static bool TryGet(glTFTextureInfo info, out glTF_KHR_texture_transform t) { - if (info.extras is glTFExtensionImport imported) + if (info != null && info.extras is glTFExtensionImport imported) { foreach (var kv in imported.ObjectItems()) { diff --git a/Assets/VRM/Runtime/Format/VRMSpecVersion.cs b/Assets/VRM/Runtime/Format/VRMSpecVersion.cs index 95872ee4e..01392c17c 100644 --- a/Assets/VRM/Runtime/Format/VRMSpecVersion.cs +++ b/Assets/VRM/Runtime/Format/VRMSpecVersion.cs @@ -2,6 +2,9 @@ using System; namespace VRM { + /// + /// 0系では運用されなかった。基本的に凍結。今後も変更しない + /// public class VRMSpecVersion { public const int Major = 0; diff --git a/Assets/VRM/Runtime/IO/VRMImporterContext.cs b/Assets/VRM/Runtime/IO/VRMImporterContext.cs index 9921b6ba2..5c018092f 100644 --- a/Assets/VRM/Runtime/IO/VRMImporterContext.cs +++ b/Assets/VRM/Runtime/IO/VRMImporterContext.cs @@ -41,10 +41,13 @@ namespace VRM if (glTF_VRM_extensions.TryDeserilize(GLTF.extensions, out glTF_VRM_extensions vrm)) { VRM = vrm; + // override material importer + SetMaterialImporter(new VRMMaterialImporter(this, VRM.materialProperties)); + } + else + { + throw new KeyNotFoundException("not vrm0"); } - - // override material importer - SetMaterialImporter(new VRMMaterialImporter(this, VRM.materialProperties)); } #region OnLoad diff --git a/Assets/VRM10.meta b/Assets/VRM10.meta new file mode 100644 index 000000000..3d5c5d632 --- /dev/null +++ b/Assets/VRM10.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4fcd733d82a0cc54f9b4c88f8ee97155 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor.meta b/Assets/VRM10/Editor.meta new file mode 100644 index 000000000..79495e16c --- /dev/null +++ b/Assets/VRM10/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7d2fdd9f95a38e641a1b3f56cf5eaf16 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/Components.meta b/Assets/VRM10/Editor/Components.meta new file mode 100644 index 000000000..af07ad9a9 --- /dev/null +++ b/Assets/VRM10/Editor/Components.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fbb107c1888c945489ac2c567af3385e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/Components/Attribute.meta b/Assets/VRM10/Editor/Components/Attribute.meta new file mode 100644 index 000000000..9030e509f --- /dev/null +++ b/Assets/VRM10/Editor/Components/Attribute.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 165ab9c04d7db604586dd5175eec153f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/Components/Attribute/ReadOnlyDrawer.cs b/Assets/VRM10/Editor/Components/Attribute/ReadOnlyDrawer.cs new file mode 100644 index 000000000..f94f3cc74 --- /dev/null +++ b/Assets/VRM10/Editor/Components/Attribute/ReadOnlyDrawer.cs @@ -0,0 +1,26 @@ +using UnityEditor; +using UnityEngine; + +namespace UniVRM10 +{ + public class ReadOnlyAttribute : PropertyAttribute + { + + } + + [CustomPropertyDrawer(typeof(ReadOnlyAttribute))] + public class ReadOnlyDrawer : PropertyDrawer + { + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) + { + return EditorGUI.GetPropertyHeight(property, label, true); + } + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + GUI.enabled = false; + EditorGUI.PropertyField(position, property, label, true); + GUI.enabled = true; + } + } +} \ No newline at end of file diff --git a/Assets/VRM10/Editor/Components/Attribute/ReadOnlyDrawer.cs.meta b/Assets/VRM10/Editor/Components/Attribute/ReadOnlyDrawer.cs.meta new file mode 100644 index 000000000..b5cbb78aa --- /dev/null +++ b/Assets/VRM10/Editor/Components/Attribute/ReadOnlyDrawer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 06a80a5d47e6b31458e2714ed692e1a8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/Components/Expression.meta b/Assets/VRM10/Editor/Components/Expression.meta new file mode 100644 index 000000000..cfb1a56f8 --- /dev/null +++ b/Assets/VRM10/Editor/Components/Expression.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4dcc84ae9f5201a479ca8df8fc991824 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/Components/Expression/ExpressionEditorBase.cs b/Assets/VRM10/Editor/Components/Expression/ExpressionEditorBase.cs new file mode 100644 index 000000000..b4317464e --- /dev/null +++ b/Assets/VRM10/Editor/Components/Expression/ExpressionEditorBase.cs @@ -0,0 +1,298 @@ +using MeshUtility; +using UnityEditor; +using UnityEngine; + +namespace UniVRM10 +{ + /// + /// Prefabをインスタンス化してPreviewに表示する + /// + /// * https://github.com/Unity-Technologies/UnityCsReference/blob/11bcfd801fccd2a52b09bb6fd636c1ddcc9f1705/Editor/Mono/Inspector/ModelInspector.cs + /// + /// + public abstract class ExpressionEditorBase : Editor + { + /// + /// PreviewRenderUtilityを管理する。 + /// + /// * PreviewRenderUtility.m_cameraのUnityVersionによる切り分け + /// + /// + PreviewFaceRenderer m_renderer; + + /// + /// Prefabをインスタンス化したシーンを管理する。 + /// + /// * ExpressionのBake + /// * MaterialMorphの適用 + /// * Previewカメラのコントロール + /// * Previewライティングのコントロール + /// + /// + PreviewSceneManager m_scene; + protected PreviewSceneManager PreviewSceneManager + { + get { return m_scene; } + } + + /// + /// Previewシーンに表示するPrefab + /// + GameObject m_prefab; + protected GameObject Prefab + { + get { return m_prefab; } + private set + { + if (m_prefab == value) return; + + //Debug.LogFormat("Prefab = {0}", value); + m_prefab = value; + + if (m_scene != null) + { + //Debug.LogFormat("OnDestroy"); + GameObject.DestroyImmediate(m_scene.gameObject); + m_scene = null; + } + + if (m_prefab != null) + { + m_scene = UniVRM10.PreviewSceneManager.GetOrCreate(m_prefab); + if (m_scene != null) + { + m_scene.gameObject.SetActive(false); + } + + Bake(); + } + } + } + + protected abstract VRM10Expression CurrentExpression(); + + /// + /// Preview シーンに Expression を適用する + /// + protected void Bake() + { + if (m_scene != null) + { + //Debug.Log("Bake"); + m_scene.Bake(CurrentExpression(), 1.0f); + } + } + + protected virtual GameObject GetPrefab() + { + var assetPath = AssetDatabase.GetAssetPath(target); + if (string.IsNullOrEmpty(assetPath)) + { + return null; + } + + var mainObject = AssetDatabase.LoadMainAssetAtPath(assetPath); + if (mainObject != null) + { + //return mainObject; + } + + var prefab = AssetDatabase.LoadAssetAtPath(assetPath); + if (prefab != null) return prefab; + + var parent = UnityPath.FromUnityPath(assetPath).Parent; + var prefabPath = parent.Parent.Child(parent.FileNameWithoutExtension + ".prefab"); + prefab = UnityEditor.AssetDatabase.LoadAssetAtPath(prefabPath.Value); + if (prefab != null) return prefab; + + var parentParent = UnityPath.FromUnityPath(assetPath).Parent.Parent; + var vrmPath = parent.Parent.Child(parent.FileNameWithoutExtension + ".vrm"); + prefab = UnityEditor.AssetDatabase.LoadAssetAtPath(vrmPath.Value); + + return prefab; + } + + protected virtual void OnEnable() + { + m_renderer = new PreviewFaceRenderer(); + + Prefab = GetPrefab(); + } + + protected virtual void OnDisable() + { + if (m_renderer != null) + { + m_renderer.Dispose(); + m_renderer = null; + } + } + + protected virtual void OnDestroy() + { + if (m_scene != null) + { + //Debug.LogFormat("OnDestroy"); + m_scene.Clean(); + GameObject.DestroyImmediate(m_scene.gameObject); + m_scene = null; + } + } + + protected static void Separator() + { + EditorGUILayout.Space(); + EditorGUILayout.BeginHorizontal(); + //GUILayout.Space(); + GUILayout.Box("", GUILayout.ExpandWidth(true), GUILayout.Height(1)); + EditorGUILayout.EndHorizontal(); + EditorGUILayout.Space(); + } + + public override void OnInspectorGUI() + { + //base.OnInspectorGUI(); + + Prefab = (GameObject)EditorGUILayout.ObjectField("Preview Prefab", Prefab, typeof(GameObject), false); + + //Separator(); + } + + private static int sliderHash = "Slider".GetHashCode(); + float m_yaw = 180.0f; + float m_pitch; + Vector3 m_position = new Vector3(0, 0, -0.8f); + + // very important to override this, it tells Unity to render an ObjectPreview at the bottom of the inspector + public override bool HasPreviewGUI() { return true; } + + public RenderTexture PreviewTexture; + + // the main ObjectPreview function... it's called constantly, like other IMGUI On*GUI() functions + public override void OnPreviewGUI(Rect r, GUIStyle background) + { + // if this is happening, you have bigger problems + if (!ShaderUtil.hardwareSupportsRectRenderTexture) + { + if (Event.current.type == EventType.Repaint) + { + EditorGUI.DropShadowLabel(new Rect(r.x, r.y, r.width, 40f), + "Mesh preview requires\nrender texture support"); + } + return; + } + + var src = r; + + var min = Mathf.Min(r.width, r.height); + r.width = min; + r.height = min; + r.x = src.x + (src.width - min) / 2; + r.y = src.y + (src.height - min) / 2; + + //previewDir = Drag2D(previewDir, r); + { + int controlId = GUIUtility.GetControlID(sliderHash, FocusType.Passive); + Event e = Event.current; + switch (e.GetTypeForControl(controlId)) + { + case EventType.MouseDown: + if (r.Contains(e.mousePosition) && (double)r.width > 50.0) + { + GUIUtility.hotControl = controlId; + e.Use(); + EditorGUIUtility.SetWantsMouseJumping(1); + break; + } + break; + + case EventType.MouseUp: + if (GUIUtility.hotControl == controlId) + GUIUtility.hotControl = 0; + EditorGUIUtility.SetWantsMouseJumping(0); + break; + + case EventType.MouseDrag: + if (GUIUtility.hotControl == controlId) + { + if (e.button == 2) + { + var shift = e.delta * (!e.shift ? 1f : 3f) / Mathf.Min(r.width, r.height); + m_position.x -= shift.x; + m_position.y += shift.y; + e.Use(); + GUI.changed = true; + } + else if ( + e.button == 0 || + e.button == 1) + { + var shift = e.delta * (!e.shift ? 1f : 3f) / Mathf.Min(r.width, r.height) * 140f; + m_yaw += shift.x; + m_pitch += shift.y; + m_pitch = Mathf.Clamp(m_pitch, -90f, 90f); + e.Use(); + GUI.changed = true; + } + break; + } + break; + + case EventType.ScrollWheel: + //Debug.LogFormat("wheel: {0}", current.delta); + if (r.Contains(e.mousePosition)) + { + if (e.delta.y > 0) + { + m_position.z *= 1.1f; + Repaint(); + } + else if (e.delta.y < 0) + { + m_position.z *= 0.9f; + Repaint(); + } + } + break; + } + //return scrollPosition; + } + //Debug.LogFormat("{0}", previewDir); + + if (Event.current.type != EventType.Repaint) + { + // if we don't need to update yet, then don't + return; + } + + if (m_renderer != null && m_scene != null) + { + PreviewTexture = m_renderer.Render(r, background, m_scene, m_yaw, m_pitch, m_position) as RenderTexture; + if (PreviewTexture != null) + { + // draw the RenderTexture in the ObjectPreview pane + GUI.DrawTexture(r, PreviewTexture, ScaleMode.StretchToFill, false); + } + } + } + + public override string GetInfoString() + { + var expression = CurrentExpression(); + if (expression == null) + { + return "no expression"; + } + + var key = ExpressionKey.CreateFromClip(expression); + if (key.Preset != VrmLib.ExpressionPreset.Custom) + { + return string.Format("Preset: {0}", key.Preset); + } + else + { + return string.Format("Custom: {0}", key.Name); + } + } + } +} diff --git a/Assets/VRM10/Editor/Components/Expression/ExpressionEditorBase.cs.meta b/Assets/VRM10/Editor/Components/Expression/ExpressionEditorBase.cs.meta new file mode 100644 index 000000000..9e2ce3b38 --- /dev/null +++ b/Assets/VRM10/Editor/Components/Expression/ExpressionEditorBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6a64fbc26ee3c274fbfd9bce32f0b5dd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/Components/Expression/ExpressionEditorHelper.cs b/Assets/VRM10/Editor/Components/Expression/ExpressionEditorHelper.cs new file mode 100644 index 000000000..d14f699ed --- /dev/null +++ b/Assets/VRM10/Editor/Components/Expression/ExpressionEditorHelper.cs @@ -0,0 +1,245 @@ +using System; +using UnityEditor; +using UnityEngine; + + +namespace UniVRM10 +{ + public static class ExpressionEditorHelper + { + public static bool StringPopup(Rect rect, SerializedProperty prop, string[] options, out int newIndex) + { + if (options == null) + { + newIndex = -1; + return false; + } + + var oldIndex = Array.IndexOf(options, prop.stringValue); + newIndex = EditorGUI.Popup(rect, oldIndex, options); + if (newIndex != oldIndex && newIndex >= 0 && newIndex < options.Length) + { + prop.stringValue = options[newIndex]; + return true; + } + else + { + return false; + } + } + + public static T EnumPopup(Rect rect, T value) where T : Enum + { + return (T)EditorGUI.EnumPopup(rect, (Enum)value); + } + + public static bool IntPopup(Rect rect, SerializedProperty prop, string[] options, out int newIndex) + { + if (options == null) + { + newIndex = -1; + return false; + } + + var oldIndex = prop.intValue; + newIndex = EditorGUI.Popup(rect, oldIndex, options); + if (newIndex != oldIndex && newIndex >= 0 && newIndex < options.Length) + { + prop.intValue = newIndex; + return true; + } + else + { + return false; + } + } + + public static bool FloatSlider(Rect rect, SerializedProperty prop, float maxValue) + { + var oldValue = prop.floatValue; + var newValue = EditorGUI.Slider(rect, prop.floatValue, 0, 100f); + if (newValue != oldValue) + { + prop.floatValue = newValue; + return true; + } + else + { + return false; + } + } + + public static bool ColorProp(Rect rect, SerializedProperty prop) + { + var oldValue = (Color)prop.vector4Value; + var newValue = EditorGUI.ColorField(rect, prop.displayName, oldValue); + if (newValue != oldValue) + { + prop.vector4Value = newValue; + return true; + } + else + { + return false; + } + } + + public static bool UVProp(Rect rect, SerializedProperty prop) + { + var oldValue = prop.vector2Value; + var newValue = EditorGUI.Vector2Field(rect, prop.displayName, oldValue); + if (newValue != oldValue) + { + prop.vector2Value = newValue; + return true; + } + else + { + return false; + } + } + + static Rect AdvanceRect(ref float x, float y, float w, float h) + { + var rect = new Rect(x, y, w, h); + x += w; + return rect; + } + + static float[] v2 = new float[2]; + static GUIContent[] l2 = new GUIContent[]{ + new GUIContent("x"), + new GUIContent("y") + }; + static Vector4 TilingOffset(Rect rect, string label, Vector4 src) + { + /* + var style = new GUIStyle() + { + alignment = TextAnchor.MiddleRight, + }; + */ + + var quad = (rect.width - 56); + var x = rect.x; + //EditorGUIUtility.labelWidth = 18; + + EditorGUI.LabelField(AdvanceRect(ref x, rect.y, 40, rect.height), "Tiling"); + v2[0] = src.x; + v2[1] = src.y; + EditorGUI.MultiFloatField(AdvanceRect(ref x, rect.y, quad, rect.height), l2, v2); + src.x = v2[0]; + src.y = v2[1]; + + //EditorGUI.LabelField(AdvanceRect(ref x, rect.y, quad, rect.height), "Y", style); + //src.y = EditorGUI.FloatField(AdvanceRect(ref x, rect.y, quad, rect.height), "Y", src.y); + + rect.y += EditorGUIUtility.singleLineHeight; + x = rect.x; + EditorGUI.LabelField(AdvanceRect(ref x, rect.y, 40, rect.height), "Offset"); + v2[0] = src.z; + v2[1] = src.w; + EditorGUI.MultiFloatField(AdvanceRect(ref x, rect.y, quad, rect.height), l2, v2); + src.z = v2[0]; + src.w = v2[1]; + + //EditorGUI.LabelField(AdvanceRect(ref x, rect.y, quad * 2, rect.height), "Offset X", style); + //src.z = EditorGUI.FloatField(AdvanceRect(ref x, rect.y, quad, rect.height), "X", src.z); + + //EditorGUI.LabelField(AdvanceRect(ref x, rect.y, quad, rect.height), "Y", style); + //src.w = EditorGUI.FloatField(AdvanceRect(ref x, rect.y, quad, rect.height), "Y", src.w); + + return src; + } + + static bool OffsetProp(Rect rect, SerializedProperty prop) + { + var oldValue = prop.vector4Value; + //var newValue = EditorGUI.Vector4Field(rect, prop.displayName, oldValue); + var newValue = TilingOffset(rect, prop.displayName, oldValue); + if (newValue != oldValue) + { + prop.vector4Value = newValue; + return true; + } + else + { + return false; + } + } + } + + /// https://gist.github.com/gszauer/7799899 + public class TextureScale + { + private static Color[] texColors; + private static Color[] newColors; + private static int w; + private static float ratioX; + private static float ratioY; + private static int w2; + + public static void Scale(Texture2D tex, int newWidth, int newHeight) + { + texColors = tex.GetPixels(); + newColors = new Color[newWidth * newHeight]; + ratioX = 1.0f / ((float)newWidth / (tex.width - 1)); + ratioY = 1.0f / ((float)newHeight / (tex.height - 1)); + w = tex.width; + w2 = newWidth; + + BilinearScale(0, newHeight); + + tex.Resize(newWidth, newHeight); + tex.SetPixels(newColors); + tex.Apply(); + } + + private static void BilinearScale(int start, int end) + { + for (var y = start; y < end; y++) + { + int yFloor = (int)Mathf.Floor(y * ratioY); + var y1 = yFloor * w; + var y2 = (yFloor + 1) * w; + var yw = y * w2; + + for (var x = 0; x < w2; x++) + { + int xFloor = (int)Mathf.Floor(x * ratioX); + var xLerp = x * ratioX - xFloor; + newColors[yw + x] = ColorLerpUnclamped(ColorLerpUnclamped(texColors[y1 + xFloor], texColors[y1 + xFloor + 1], xLerp), + ColorLerpUnclamped(texColors[y2 + xFloor], texColors[y2 + xFloor + 1], xLerp), + y * ratioY - yFloor); + } + } + } + + private static Color ColorLerpUnclamped(Color c1, Color c2, float value) + { + return new Color(c1.r + (c2.r - c1.r) * value, + c1.g + (c2.g - c1.g) * value, + c1.b + (c2.b - c1.b) * value, + c1.a + (c2.a - c1.a) * value); + } + + /// http://light11.hatenadiary.com/entry/2018/04/19/194015 + public static Texture2D GetResized(Texture2D texture, int width, int height) + { + // リサイズ後のサイズを持つRenderTextureを作成して書き込む + var rt = RenderTexture.GetTemporary(width, height); + Graphics.Blit(texture, rt); + + // リサイズ後のサイズを持つTexture2Dを作成してRenderTextureから書き込む + var preRT = RenderTexture.active; + RenderTexture.active = rt; + var ret = new Texture2D(width, height); + ret.ReadPixels(new Rect(0, 0, width, height), 0, 0); + ret.Apply(); + RenderTexture.active = preRT; + + RenderTexture.ReleaseTemporary(rt); + return ret; + } + } +} diff --git a/Assets/VRM10/Editor/Components/Expression/ExpressionEditorHelper.cs.meta b/Assets/VRM10/Editor/Components/Expression/ExpressionEditorHelper.cs.meta new file mode 100644 index 000000000..1ac65abfb --- /dev/null +++ b/Assets/VRM10/Editor/Components/Expression/ExpressionEditorHelper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dfbaf4fb19a693244b0fde44542b18b8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/Components/Expression/ExpressionSelector.cs b/Assets/VRM10/Editor/Components/Expression/ExpressionSelector.cs new file mode 100644 index 000000000..3de3fdffb --- /dev/null +++ b/Assets/VRM10/Editor/Components/Expression/ExpressionSelector.cs @@ -0,0 +1,138 @@ +using System; +using System.Linq; +using UnityEngine; +using UnityEditor; +using System.IO; + +namespace UniVRM10 +{ + /// + /// ExpressionAvatarEditorの部品 + /// + class ExpressionClipSelector + { + VRM10ExpressionAvatar m_avatar; + + int m_mode; + static readonly string[] MODES = new string[]{ + "Button", + "List" + }; + + ReorderableExpressionList m_clipList; + + public VRM10Expression GetSelected() + { + if (m_avatar == null || m_avatar.Clips == null || m_avatar.Clips.Count == 0) + { + return null; + } + if (m_selectedIndex < 0 || m_selectedIndex >= m_avatar.Clips.Count) + { + return null; + } + return m_avatar.Clips[m_selectedIndex]; + } + + public event Action Selected; + void RaiseSelected(int index) + { + m_clipList.Select(index); + var clip = GetSelected(); + var handle = Selected; + if (handle == null) + { + return; + } + handle(clip); + } + + int m_selectedIndex; + int SelectedIndex + { + get { return m_selectedIndex; } + set + { + // これで更新するべし + if (m_selectedIndex == value) return; + m_selectedIndex = value; + RaiseSelected(value); + } + } + + public ExpressionClipSelector(VRM10ExpressionAvatar avatar, SerializedObject serializedObject) + { + avatar.RemoveNullClip(); + + m_avatar = avatar; + + var prop = serializedObject.FindProperty("Clips"); + m_clipList = new ReorderableExpressionList(serializedObject, prop, avatar); + m_clipList.Selected += (selected) => + { + SelectedIndex = avatar.Clips.IndexOf(selected); + }; + } + + public void GUI() + { + EditorGUILayout.Space(); + EditorGUILayout.LabelField("Select Expression", EditorStyles.boldLabel); + + m_mode = GUILayout.Toolbar(m_mode, MODES); + switch (m_mode) + { + case 0: + SelectGUI(); + break; + + case 1: + m_clipList.GUI(); + break; + + default: + throw new NotImplementedException(); + } + + } + + void SelectGUI() + { + if (m_avatar != null && m_avatar.Clips != null) + { + var array = m_avatar.Clips + .Select(x => x != null + ? ExpressionKey.CreateFromClip(x).ToString() + : "null" + ).ToArray(); + SelectedIndex = GUILayout.SelectionGrid(SelectedIndex, array, 4); + } + + if (GUILayout.Button("Add Expression")) + { + var dir = Path.GetDirectoryName(AssetDatabase.GetAssetPath(m_avatar)); + var path = EditorUtility.SaveFilePanel( + "Create Expression", + dir, + string.Format("Expression#{0}.asset", m_avatar.Clips.Count), + "asset"); + if (!string.IsNullOrEmpty(path)) + { + var clip = VRM10ExpressionAvatar.CreateExpression(path.ToUnityRelativePath()); + //clip.Prefab = AssetDatabase.LoadAssetAtPath(AssetDatabase.GetAssetPath(target)); + + m_avatar.Clips.Add(clip); + } + } + } + + public void DuplicateWarn() + { + var key = ExpressionKey.CreateFromClip(GetSelected()); + if (m_avatar.Clips.Where(x => key.Match(x)).Count() > 1) + { + EditorGUILayout.HelpBox("duplicate clip: " + key, MessageType.Error); + } + } + } +} diff --git a/Assets/VRM10/Editor/Components/Expression/ExpressionSelector.cs.meta b/Assets/VRM10/Editor/Components/Expression/ExpressionSelector.cs.meta new file mode 100644 index 000000000..d6d2bac84 --- /dev/null +++ b/Assets/VRM10/Editor/Components/Expression/ExpressionSelector.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6d4d5da207577eb48ad767ff59bcf8b4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/Components/Expression/PreviewFaceRenderer.cs b/Assets/VRM10/Editor/Components/Expression/PreviewFaceRenderer.cs new file mode 100644 index 000000000..4020fcf06 --- /dev/null +++ b/Assets/VRM10/Editor/Components/Expression/PreviewFaceRenderer.cs @@ -0,0 +1,168 @@ +using System; +using UnityEditor; +using UnityEngine; + + +namespace UniVRM10 +{ + /// + /// based + /// + /// * https://gist.github.com/radiatoryang/a2282d44ba71848e498bb2e03da98991 + /// + + /// + /// PreviewRenderUtilityを管理する + /// PreviewSceneをレンダリングする + /// + public class PreviewFaceRenderer : IDisposable + { + PreviewRenderUtility m_previewUtility; + public Camera PreviewCamera + { + get + { +#if UNITY_2017_1_OR_NEWER + return m_previewUtility.camera; +#else + return m_previewUtility.m_Camera; +#endif + } + } + + public Light[] PreviewLights + { + get + { +#if UNITY_2017_1_OR_NEWER + return m_previewUtility.lights; +#else + return m_previewUtility.m_Light; +#endif + } + } + + public void SetAmbientColor(Color color) + { +#if UNITY_2017_1_OR_NEWER + m_previewUtility.ambientColor = color; +#else + // ? +#endif + } + + public PreviewFaceRenderer() + { + m_previewUtility = new PreviewRenderUtility(); + + foreach (var light in PreviewLights) + { + if (light == null) continue; + light.intensity = 0f; + } + + if (PreviewLights.Length > 0 && PreviewLights[0] != null) + { + PreviewLights[0].intensity = 1f; + PreviewLights[0].transform.rotation = Quaternion.Euler(20f, 200f, 0); + PreviewLights[0].color = new Color(1f, 1f, 1f, 1f); + } + + SetAmbientColor(new Color(0.1f, 0.1f, 0.1f, 1f)); + } + + class FogScope : IDisposable + { + bool fog; + + public FogScope() + { + fog = RenderSettings.fog; // ... let's remember the current fog setting... + // we are technically rendering everything in the scene, so scene fog might affect it... + Unsupported.SetRenderSettingsUseFogNoDirty(false); // ... and then temporarily turn it off + } + + public void Dispose() + { + Unsupported.SetRenderSettingsUseFogNoDirty(fog); + } + } + + //const float FACTOR = 0.1f; + + public Texture Render(Rect r, GUIStyle background, PreviewSceneManager scene, + float yaw, float pitch, Vector3 position) + { + if (scene == null) return null; + + using (var fog = new FogScope()) + { + m_previewUtility.BeginPreview(r, background); // set up the PreviewRenderUtility's mini internal scene + + // setup the ObjectPreview's camera + scene.SetupCamera(PreviewCamera, scene.TargetPosition, yaw, pitch, position); + + foreach (var item in scene.EnumRenderItems) + { + // now, actually render out the RenderTexture + //RenderMeshPreview(previewMesh, skinMeshRender.sharedMaterials); + // submesh support, in case the mesh is made of multiple parts + int subMeshCount = item.Mesh.subMeshCount; + for (int i = 0; i < subMeshCount; i++) + { + m_previewUtility.DrawMesh(item.Mesh, + item.Position, item.Rotation, + item.Materials[i], i); + } + } + + // VERY IMPORTANT: this manually tells the camera to render and produce the render texture + PreviewCamera.Render(); + //m_previewUtility.Render(false, false); + + // reset the scene's fog from before + return m_previewUtility.EndPreview(); + } + } + + #region IDisposable Support + private bool disposedValue = false; // 重複する呼び出しを検出するには + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // TODO: マネージ状態を破棄します (マネージ オブジェクト)。 + if (this.m_previewUtility != null) + { + this.m_previewUtility.Cleanup(); + this.m_previewUtility = null; + } + } + + // TODO: アンマネージ リソース (アンマネージ オブジェクト) を解放し、下のファイナライザーをオーバーライドします。 + // TODO: 大きなフィールドを null に設定します。 + + disposedValue = true; + } + } + + // TODO: 上の Dispose(bool disposing) にアンマネージ リソースを解放するコードが含まれる場合にのみ、ファイナライザーをオーバーライドします。 + // ~PreviewFaceRenderer() { + // // このコードを変更しないでください。クリーンアップ コードを上の Dispose(bool disposing) に記述します。 + // Dispose(false); + // } + + // このコードは、破棄可能なパターンを正しく実装できるように追加されました。 + public void Dispose() + { + // このコードを変更しないでください。クリーンアップ コードを上の Dispose(bool disposing) に記述します。 + Dispose(true); + // TODO: 上のファイナライザーがオーバーライドされる場合は、次の行のコメントを解除してください。 + // GC.SuppressFinalize(this); + } + #endregion + } +} diff --git a/Assets/VRM10/Editor/Components/Expression/PreviewFaceRenderer.cs.meta b/Assets/VRM10/Editor/Components/Expression/PreviewFaceRenderer.cs.meta new file mode 100644 index 000000000..38da77a05 --- /dev/null +++ b/Assets/VRM10/Editor/Components/Expression/PreviewFaceRenderer.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 9de8d16a3d572ea4da3fc4a69ba61964 +timeCreated: 1522931965 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/Components/Expression/ReorderableExpressionList.cs b/Assets/VRM10/Editor/Components/Expression/ReorderableExpressionList.cs new file mode 100644 index 000000000..fc6143be5 --- /dev/null +++ b/Assets/VRM10/Editor/Components/Expression/ReorderableExpressionList.cs @@ -0,0 +1,83 @@ +using System; +using System.IO; +using UnityEditor; +using UnityEditorInternal; + +namespace UniVRM10 +{ + class ReorderableExpressionList + { + ReorderableList m_list; + + public event Action Selected; + void RaiseSelected(VRM10Expression selected) + { + var handler = Selected; + if (handler == null) + { + return; + } + handler(selected); + } + + public void Select(int index) + { + m_list.index = index; + } + + public ReorderableExpressionList(SerializedObject serializedObject, SerializedProperty prop, UnityEngine.Object target) + { + m_list = new ReorderableList(serializedObject, prop); + + m_list.drawHeaderCallback = (rect) => + EditorGUI.LabelField(rect, "Expressions"); + + m_list.drawElementCallback = (rect, index, isActive, isFocused) => + { + var element = prop.GetArrayElementAtIndex(index); + rect.height -= 4; + rect.y += 2; + EditorGUI.PropertyField(rect, element); + }; + + m_list.onAddCallback += (list) => + { + // Add slot + prop.arraySize++; + // select last item + list.index = prop.arraySize - 1; + // get last item + var element = prop.GetArrayElementAtIndex(list.index); + element.objectReferenceValue = null; + + var dir = Path.GetDirectoryName(AssetDatabase.GetAssetPath(target)); + var path = EditorUtility.SaveFilePanel( + "Create Expression", + dir, + string.Format("Expression#{0}.asset", list.count), + "asset"); + if (!string.IsNullOrEmpty(path)) + { + var clip = VRM10ExpressionAvatar.CreateExpression(path.ToUnityRelativePath()); + //clip.Prefab = AssetDatabase.LoadAssetAtPath(AssetDatabase.GetAssetPath(target)); + + element.objectReferenceValue = clip; + } + }; + + m_list.onSelectCallback += (list) => + { + var a = list.serializedProperty; + var selected = a.GetArrayElementAtIndex(list.index); + RaiseSelected((VRM10Expression)selected.objectReferenceValue); + }; + + //m_clipList.onCanRemoveCallback += list => true; + } + + public void GUI() + { + m_list.DoLayoutList(); + } + } +} diff --git a/Assets/VRM10/Editor/Components/Expression/ReorderableExpressionList.cs.meta b/Assets/VRM10/Editor/Components/Expression/ReorderableExpressionList.cs.meta new file mode 100644 index 000000000..55b093fb4 --- /dev/null +++ b/Assets/VRM10/Editor/Components/Expression/ReorderableExpressionList.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a5a5707cea4226d40af879c4c9778002 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/Components/Expression/ReorderableMaterialColorBindingList.cs b/Assets/VRM10/Editor/Components/Expression/ReorderableMaterialColorBindingList.cs new file mode 100644 index 000000000..75f843228 --- /dev/null +++ b/Assets/VRM10/Editor/Components/Expression/ReorderableMaterialColorBindingList.cs @@ -0,0 +1,87 @@ +using System; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; + + +namespace UniVRM10 +{ + public class ReorderableMaterialColorBindingList + { + ReorderableList m_list; + SerializedProperty m_serializedProperty; + bool m_changed; + public ReorderableMaterialColorBindingList(SerializedObject serializedObject, string[] materialNames, int height) + { + m_serializedProperty = serializedObject.FindProperty(nameof(VRM10Expression.MaterialColorBindings)); + m_list = new ReorderableList(serializedObject, m_serializedProperty); + m_list.elementHeight = height * 3; + m_list.drawElementCallback = + (rect, index, isActive, isFocused) => + { + var element = m_serializedProperty.GetArrayElementAtIndex(index); + rect.height -= 4; + rect.y += 2; + if (DrawMaterialValueBinding(rect, element, materialNames, height)) + { + m_changed = true; + } + }; + } + + /// + /// Material List のElement描画 + /// + static bool DrawMaterialValueBinding(Rect position, SerializedProperty property, + string[] materialNames, int height) + { + bool changed = false; + if (materialNames != null) + { + // Material を選択する + var y = position.y; + var rect = new Rect(position.x, y, position.width, height); + int materialIndex; + if (ExpressionEditorHelper.StringPopup(rect, property.FindPropertyRelative(nameof(MaterialColorBinding.MaterialName)), materialNames, out materialIndex)) + { + changed = true; + } + + y += height; + rect = new Rect(position.x, y, position.width, height); + + // 対象のプロパティを enum から選択する + var bindTypeProp = property.FindPropertyRelative("BindType"); + var bindTypes = (VrmLib.MaterialBindType[])Enum.GetValues(typeof(VrmLib.MaterialBindType)); + var bindType = bindTypes[bindTypeProp.enumValueIndex]; + var newBindType = ExpressionEditorHelper.EnumPopup(rect, bindType); + if (newBindType != bindType) + { + bindTypeProp.enumValueIndex = Array.IndexOf(bindTypes, newBindType); + changed = true; + } + + // 目標の色 + y += height; + rect = new Rect(position.x, y, position.width, height); + if (ExpressionEditorHelper.ColorProp(rect, property.FindPropertyRelative(nameof(MaterialColorBinding.TargetValue)))) + { + changed = true; + } + } + return changed; + } + + public bool Draw() + { + m_changed = false; + m_list.DoLayoutList(); + if (GUILayout.Button("Clear MaterialColor")) + { + m_changed = true; + m_serializedProperty.arraySize = 0; + } + return m_changed; + } + } +} diff --git a/Assets/VRM10/Editor/Components/Expression/ReorderableMaterialColorBindingList.cs.meta b/Assets/VRM10/Editor/Components/Expression/ReorderableMaterialColorBindingList.cs.meta new file mode 100644 index 000000000..4d366d723 --- /dev/null +++ b/Assets/VRM10/Editor/Components/Expression/ReorderableMaterialColorBindingList.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e6f6fcbf32b1c674aa717d3a7daabf23 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/Components/Expression/ReorderableMaterialUVBindingList.cs b/Assets/VRM10/Editor/Components/Expression/ReorderableMaterialUVBindingList.cs new file mode 100644 index 000000000..be19f8503 --- /dev/null +++ b/Assets/VRM10/Editor/Components/Expression/ReorderableMaterialUVBindingList.cs @@ -0,0 +1,95 @@ +using System; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; + + +namespace UniVRM10 +{ + public class ReorderableMaterialUVBindingList + { + ReorderableList m_list; + SerializedProperty m_serializedProperty; + bool m_changed; + + public ReorderableMaterialUVBindingList(SerializedObject serializedObject, string[] materialNames, int height) + { + m_serializedProperty = serializedObject.FindProperty(nameof(VRM10Expression.MaterialUVBindings)); + m_list = new ReorderableList(serializedObject, m_serializedProperty); + m_list.elementHeight = height * 3; + m_list.drawElementCallback = + (rect, index, isActive, isFocused) => + { + var element = m_serializedProperty.GetArrayElementAtIndex(index); + rect.height -= 4; + rect.y += 2; + if (DrawMaterialUVBinding(rect, element, materialNames, height)) + { + m_changed = true; + } + }; + m_list.onAddCallback = (m_list) => + { + // first add one element + m_serializedProperty.arraySize++; + + // then get that element + var newIndex = m_serializedProperty.arraySize - 1; + var newElement = m_serializedProperty.GetArrayElementAtIndex(newIndex); + + // now reset all properties like + var scaling = newElement.FindPropertyRelative(nameof(MaterialUVBinding.Scaling)); + scaling.vector2Value = Vector2.one; + }; + } + + /// + /// Material List のElement描画 + /// + static bool DrawMaterialUVBinding(Rect position, SerializedProperty property, + string[] materialNames, int height) + { + bool changed = false; + if (materialNames != null) + { + // Materialを選択する + var y = position.y; + var rect = new Rect(position.x, y, position.width, height); + int materialIndex; + if (ExpressionEditorHelper.StringPopup(rect, property.FindPropertyRelative(nameof(MaterialUVBinding.MaterialName)), materialNames, out materialIndex)) + { + changed = true; + } + + // offset + y += height; + rect = new Rect(position.x, y, position.width, height); + if (ExpressionEditorHelper.UVProp(rect, property.FindPropertyRelative(nameof(MaterialUVBinding.Offset)))) + { + changed = true; + } + + // scale + y += height; + rect = new Rect(position.x, y, position.width, height); + if (ExpressionEditorHelper.UVProp(rect, property.FindPropertyRelative(nameof(MaterialUVBinding.Scaling)))) + { + changed = true; + } + } + return changed; + } + + public bool Draw() + { + m_changed = false; + m_list.DoLayoutList(); + if (GUILayout.Button("Clear MaterialUV")) + { + m_changed = true; + m_serializedProperty.arraySize = 0; + } + return m_changed; + } + } +} diff --git a/Assets/VRM10/Editor/Components/Expression/ReorderableMaterialUVBindingList.cs.meta b/Assets/VRM10/Editor/Components/Expression/ReorderableMaterialUVBindingList.cs.meta new file mode 100644 index 000000000..3c0c7ba13 --- /dev/null +++ b/Assets/VRM10/Editor/Components/Expression/ReorderableMaterialUVBindingList.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 38d1e54e633fb654c812d4718e27c71b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/Components/Expression/ReorderableMorphTargetBindingList.cs b/Assets/VRM10/Editor/Components/Expression/ReorderableMorphTargetBindingList.cs new file mode 100644 index 000000000..ebceda9b9 --- /dev/null +++ b/Assets/VRM10/Editor/Components/Expression/ReorderableMorphTargetBindingList.cs @@ -0,0 +1,118 @@ +using System; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; + + +namespace UniVRM10 +{ + public class ReorderableMorphTargetBindingList + { + ReorderableList m_ValuesList; + SerializedProperty m_valuesProp; + bool m_changed; + + public ReorderableMorphTargetBindingList(SerializedObject serializedObject, PreviewSceneManager previewSceneManager, int height) + { + m_valuesProp = serializedObject.FindProperty(nameof(VRM10Expression.MorphTargetBindings)); + m_ValuesList = new ReorderableList(serializedObject, m_valuesProp); + m_ValuesList.elementHeight = height * 3; + m_ValuesList.drawElementCallback = + (rect, index, isActive, isFocused) => + { + var element = m_valuesProp.GetArrayElementAtIndex(index); + rect.height -= 4; + rect.y += 2; + if (DrawMorphTargetBinding(rect, element, previewSceneManager, height)) + { + m_changed = true; + } + }; + } + + /// + /// MorphTarget List のElement描画 + /// + static bool DrawMorphTargetBinding(Rect position, SerializedProperty property, + PreviewSceneManager scene, int height) + { + bool changed = false; + if (scene != null) + { + var y = position.y; + var rect = new Rect(position.x, y, position.width, height); + int pathIndex; + if (ExpressionEditorHelper.StringPopup(rect, property.FindPropertyRelative(nameof(MorphTargetBinding.RelativePath)), scene.SkinnedMeshRendererPathList, out pathIndex)) + { + changed = true; + } + + y += height; + rect = new Rect(position.x, y, position.width, height); + int morphTargetIndex; + if (ExpressionEditorHelper.IntPopup(rect, property.FindPropertyRelative(nameof(MorphTargetBinding.Index)), scene.GetBlendShapeNames(pathIndex), out morphTargetIndex)) + { + changed = true; + } + + y += height; + rect = new Rect(position.x, y, position.width, height); + if (ExpressionEditorHelper.FloatSlider(rect, property.FindPropertyRelative(nameof(MorphTargetBinding.Weight)), 100)) + { + changed = true; + } + } + return changed; + } + + public void SetValues(MorphTargetBinding[] bindings) + { + m_valuesProp.ClearArray(); + m_valuesProp.arraySize = bindings.Length; + for (int i = 0; i < bindings.Length; ++i) + { + var item = m_valuesProp.GetArrayElementAtIndex(i); + + var endProperty = item.GetEndProperty(); + while (item.NextVisible(true)) + { + if (SerializedProperty.EqualContents(item, endProperty)) + { + break; + } + + switch (item.name) + { + case nameof(MorphTargetBinding.RelativePath): + item.stringValue = bindings[i].RelativePath; + break; + + case nameof(MorphTargetBinding.Index): + item.intValue = bindings[i].Index; + break; + + case nameof(MorphTargetBinding.Weight): + item.floatValue = bindings[i].Weight; + break; + + default: + throw new Exception(); + } + } + } + + } + + public bool Draw() + { + m_changed = false; + m_ValuesList.DoLayoutList(); + if (GUILayout.Button("Clear BlendShape")) + { + m_changed = true; + m_valuesProp.arraySize = 0; + } + return m_changed; + } + } +} diff --git a/Assets/VRM10/Editor/Components/Expression/ReorderableMorphTargetBindingList.cs.meta b/Assets/VRM10/Editor/Components/Expression/ReorderableMorphTargetBindingList.cs.meta new file mode 100644 index 000000000..159bb3796 --- /dev/null +++ b/Assets/VRM10/Editor/Components/Expression/ReorderableMorphTargetBindingList.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b82126a68b53be745936ee0115e3708a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/Components/Expression/SerializedExpressionEditor.cs b/Assets/VRM10/Editor/Components/Expression/SerializedExpressionEditor.cs new file mode 100644 index 000000000..cbf8f212b --- /dev/null +++ b/Assets/VRM10/Editor/Components/Expression/SerializedExpressionEditor.cs @@ -0,0 +1,299 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace UniVRM10 +{ + /// + /// Expression カスタムエディタの実装 + /// + public class SerializedExpressionEditor + { + VRM10Expression m_targetObject; + + SerializedObject m_serializedObject; + + #region Properties + SerializedProperty m_thumbnail; + SerializedProperty m_expressionNameProp; + SerializedProperty m_presetProp; + + SerializedProperty m_isBinaryProp; + + public bool IsBinary => m_isBinaryProp.boolValue; + + SerializedProperty m_ignoreBlinkProp; + SerializedProperty m_ignoreLookAtProp; + SerializedProperty m_ignoreMouthProp; + #endregion + + ReorderableMorphTargetBindingList m_morphTargetBindings; + ReorderableMaterialColorBindingList m_materialColorBindings; + ReorderableMaterialUVBindingList m_materialUVBindings; + + #region Editor values + + bool m_changed; + + public struct EditorStatus + { + public int Mode; + public bool MorphTargetFoldout; + public bool AdvancedFoldout; + + public static EditorStatus Default => new EditorStatus + { + MorphTargetFoldout = true, + }; + } + EditorStatus m_status = EditorStatus.Default; + + public EditorStatus Status => m_status; + + static string[] MODES = new[]{ + "MorphTarget", + "Material Color", + "Material UV" + }; + + PreviewMeshItem[] m_items; + #endregion + + public SerializedExpressionEditor(SerializedObject serializedObject, + PreviewSceneManager previewSceneManager) : this( + serializedObject, (VRM10Expression)serializedObject.targetObject, previewSceneManager, EditorStatus.Default) + { } + + public SerializedExpressionEditor(VRM10Expression expression, + PreviewSceneManager previewSceneManager, EditorStatus status) : this( + new SerializedObject(expression), expression, previewSceneManager, status) + { } + + public SerializedExpressionEditor(SerializedObject serializedObject, VRM10Expression targetObject, + PreviewSceneManager previewSceneManager, EditorStatus status) + { + m_status = status; + this.m_serializedObject = serializedObject; + this.m_targetObject = targetObject; + + m_expressionNameProp = serializedObject.FindProperty("expressionName"); + m_presetProp = serializedObject.FindProperty("Preset"); + m_isBinaryProp = serializedObject.FindProperty("IsBinary"); + m_ignoreBlinkProp = serializedObject.FindProperty("IgnoreBlink"); + m_ignoreLookAtProp = serializedObject.FindProperty("IgnoreLookAt"); + m_ignoreMouthProp = serializedObject.FindProperty("IgnoreMouth"); + + m_morphTargetBindings = new ReorderableMorphTargetBindingList(serializedObject, previewSceneManager, 20); + m_materialColorBindings = new ReorderableMaterialColorBindingList(serializedObject, previewSceneManager?.MaterialNames, 20); + m_materialUVBindings = new ReorderableMaterialUVBindingList(serializedObject, previewSceneManager?.MaterialNames, 20); + + m_items = previewSceneManager.EnumRenderItems + .Where(x => x.SkinnedMeshRenderer != null) + .ToArray(); + } + + public bool Draw(out VRM10Expression bakeValue) + { + m_changed = false; + + m_serializedObject.Update(); + + // Readonly の Expression 参照 + GUI.enabled = false; + EditorGUILayout.ObjectField("Current clip", + m_targetObject, typeof(VRM10Expression), false); + GUI.enabled = true; + + EditorGUILayout.PropertyField(m_expressionNameProp, true); + EditorGUILayout.PropertyField(m_presetProp, true); + + m_status.MorphTargetFoldout = CustomUI.Foldout(Status.MorphTargetFoldout, "MorphTarget"); + if (Status.MorphTargetFoldout) + { + EditorGUI.indentLevel++; + var changed = MorphTargetBindsGUI(); + if (changed) + { + string maxWeightName; + var bindings = GetBindings(out maxWeightName); + m_morphTargetBindings.SetValues(bindings); + + m_changed = true; + } + EditorGUI.indentLevel--; + } + + m_status.AdvancedFoldout = CustomUI.Foldout(Status.AdvancedFoldout, "Advanced"); + if (Status.AdvancedFoldout) + { + EditorGUI.indentLevel++; + + // v0.45 Added. Binary flag + EditorGUILayout.PropertyField(m_isBinaryProp, true); + + // v1.0 Ignore State + EditorGUILayout.PropertyField(m_ignoreBlinkProp, true); + EditorGUILayout.PropertyField(m_ignoreLookAtProp, true); + EditorGUILayout.PropertyField(m_ignoreMouthProp, true); + + EditorGUILayout.Space(); + m_status.Mode = GUILayout.Toolbar(Status.Mode, MODES); + switch (Status.Mode) + { + case 0: + // MorphTarget + { + if (m_morphTargetBindings.Draw()) + { + m_changed = true; + } + } + break; + + case 1: + // Material + { + if (m_materialColorBindings.Draw()) + { + m_changed = true; + } + } + break; + + case 2: + // MaterialUV + { + if (m_materialUVBindings.Draw()) + { + m_changed = true; + } + } + break; + } + + EditorGUI.indentLevel--; + } + + m_serializedObject.ApplyModifiedProperties(); + + bakeValue = m_targetObject; + return m_changed; + } + + List m_meshFolds = new List(); + bool MorphTargetBindsGUI() + { + bool changed = false; + int foldIndex = 0; + // すべてのSkinnedMeshRendererを列挙する + foreach (var renderer in m_items.Select(x => x.SkinnedMeshRenderer)) + { + var mesh = renderer.sharedMesh; + if (mesh != null && mesh.blendShapeCount > 0) + { + //var relativePath = UniGLTF.UnityExtensions.RelativePathFrom(renderer.transform, m_target.transform); + //EditorGUILayout.LabelField(m_target.name + "/" + item.Path); + + if (foldIndex >= m_meshFolds.Count) + { + m_meshFolds.Add(false); + } + m_meshFolds[foldIndex] = EditorGUILayout.Foldout(m_meshFolds[foldIndex], renderer.name); + if (m_meshFolds[foldIndex]) + { + //EditorGUI.indentLevel += 1; + for (int i = 0; i < mesh.blendShapeCount; ++i) + { + var src = renderer.GetBlendShapeWeight(i); + var dst = EditorGUILayout.Slider(mesh.GetBlendShapeName(i), src, 0, 100.0f); + if (dst != src) + { + renderer.SetBlendShapeWeight(i, dst); + changed = true; + } + } + //EditorGUI.indentLevel -= 1; + } + ++foldIndex; + } + } + return changed; + } + + MorphTargetBinding[] GetBindings(out string _maxWeightName) + { + var maxWeight = 0.0f; + var maxWeightName = ""; + // weightのついたblendShapeを集める + var values = m_items + .SelectMany(x => + { + var mesh = x.SkinnedMeshRenderer.sharedMesh; + + var relativePath = x.Path; + + var list = new List(); + if (mesh != null) + { + for (int i = 0; i < mesh.blendShapeCount; ++i) + { + var weight = x.SkinnedMeshRenderer.GetBlendShapeWeight(i); + if (weight == 0) + { + continue; + } + var name = mesh.GetBlendShapeName(i); + if (weight > maxWeight) + { + maxWeightName = name; + maxWeight = weight; + } + list.Add(new MorphTargetBinding + { + Index = i, + RelativePath = relativePath, + Weight = weight + }); + } + } + return list; + }).ToArray() + ; + _maxWeightName = maxWeightName; + return values; + } + } + + /// http://tips.hecomi.com/entry/2016/10/15/004144 + public static class CustomUI + { + public static bool Foldout(bool display, string title) + { + var style = new GUIStyle("ShurikenModuleTitle"); + style.font = new GUIStyle(EditorStyles.label).font; + style.border = new RectOffset(15, 7, 4, 4); + style.fixedHeight = 22; + style.contentOffset = new Vector2(20f, -2f); + + var rect = GUILayoutUtility.GetRect(16f, 22f, style); + GUI.Box(rect, title, style); + + var e = Event.current; + + var toggleRect = new Rect(rect.x + 4f, rect.y + 2f, 13f, 13f); + if (e.type == EventType.Repaint) + { + EditorStyles.foldout.Draw(toggleRect, false, false, display, false); + } + + if (e.type == EventType.MouseDown && rect.Contains(e.mousePosition)) + { + display = !display; + e.Use(); + } + + return display; + } + } +} diff --git a/Assets/VRM10/Editor/Components/Expression/SerializedExpressionEditor.cs.meta b/Assets/VRM10/Editor/Components/Expression/SerializedExpressionEditor.cs.meta new file mode 100644 index 000000000..12959d8ca --- /dev/null +++ b/Assets/VRM10/Editor/Components/Expression/SerializedExpressionEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7b0f700f0a01ce24dbc7f7d0b684cc03 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/Components/Expression/VRM10ExpressionAvatarEditor.cs b/Assets/VRM10/Editor/Components/Expression/VRM10ExpressionAvatarEditor.cs new file mode 100644 index 000000000..96633587b --- /dev/null +++ b/Assets/VRM10/Editor/Components/Expression/VRM10ExpressionAvatarEditor.cs @@ -0,0 +1,78 @@ +using UnityEditor; + +namespace UniVRM10 +{ + [CustomEditor(typeof(VRM10ExpressionAvatar))] + public class ExpressionAvatarEditor : ExpressionEditorBase + { + /// + /// ExpressionAvatar から 編集対象の Expression を選択する + /// + ExpressionClipSelector m_selector; + ExpressionClipSelector Selector + { + get + { + if (m_selector == null) + { + m_selector = new ExpressionClipSelector((VRM10ExpressionAvatar)target, serializedObject); + m_selector.Selected += OnSelected; + OnSelected(m_selector.GetSelected()); + } + return m_selector; + } + } + + /// + /// 選択中の Expression のエディタ + /// + SerializedExpressionEditor m_serializedEditor; + + protected override VRM10Expression CurrentExpression() + { + return Selector.GetSelected(); + } + + void OnSelected(VRM10Expression clip) + { + if (PreviewSceneManager == null) + { + m_serializedEditor = null; + return; + } + + if (clip != null) + { + // select clip + var status = SerializedExpressionEditor.EditorStatus.Default; + if (m_serializedEditor != null) + { + status = m_serializedEditor.Status; + } + m_serializedEditor = new SerializedExpressionEditor(clip, PreviewSceneManager, status); + } + else + { + // clear selection + m_serializedEditor = null; + PreviewSceneManager.Bake(default, 1.0f); + } + } + + public override void OnInspectorGUI() + { + base.OnInspectorGUI(); + + // selector + Selector.GUI(); + + // editor + if (m_serializedEditor != null) + { + Separator(); + m_serializedEditor.Draw(out VRM10Expression bakeValue); + PreviewSceneManager.Bake(bakeValue, 1.0f); + } + } + } +} diff --git a/Assets/VRM10/Editor/Components/Expression/VRM10ExpressionAvatarEditor.cs.meta b/Assets/VRM10/Editor/Components/Expression/VRM10ExpressionAvatarEditor.cs.meta new file mode 100644 index 000000000..552c7e0e6 --- /dev/null +++ b/Assets/VRM10/Editor/Components/Expression/VRM10ExpressionAvatarEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 821786654431c304187c2c85ebea264f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/Components/Expression/VRM10ExpressionEditor.cs b/Assets/VRM10/Editor/Components/Expression/VRM10ExpressionEditor.cs new file mode 100644 index 000000000..33a620467 --- /dev/null +++ b/Assets/VRM10/Editor/Components/Expression/VRM10ExpressionEditor.cs @@ -0,0 +1,123 @@ +using System; +using System.IO; +using MeshUtility; +using UnityEditor; +using UnityEngine; + + +namespace UniVRM10 +{ + [CustomEditor(typeof(VRM10Expression))] + public class ExpressionEditor : ExpressionEditorBase + { + SerializedExpressionEditor m_serializedEditor; + + VRM10Expression m_target; + protected override VRM10Expression CurrentExpression() + { + return m_target; + } + + protected override GameObject GetPrefab() + { + return m_target.Prefab; + } + + protected override void OnEnable() + { + m_target = (VRM10Expression)target; + + base.OnEnable(); + } + + float m_previewSlider = 1.0f; + + static Texture2D SaveResizedImage(RenderTexture rt, UnityPath path, int size) + { + var tex = new Texture2D(rt.width, rt.height, TextureFormat.RGB24, false); + RenderTexture.active = rt; + tex.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0); + tex.Apply(); + + //TextureScale.Scale(tex, size, size); + tex = TextureScale.GetResized(tex, size, size); + + byte[] bytes; + switch (path.Extension.ToLower()) + { + case ".png": + bytes = tex.EncodeToPNG(); + break; + + case ".jpg": + bytes = tex.EncodeToJPG(); + break; + + default: + throw new Exception(); + } + + if (Application.isPlaying) + { + UnityEngine.Object.Destroy(tex); + } + else + { + UnityEngine.Object.DestroyImmediate(tex); + } + File.WriteAllBytes(path.FullPath, bytes); + + path.ImportAsset(); + return path.LoadAsset(); + } + + public override void OnInspectorGUI() + { + if (PreviewSceneManager == null) + { + return; + } + serializedObject.Update(); + + if (m_serializedEditor == null) + { + m_serializedEditor = new SerializedExpressionEditor(serializedObject, PreviewSceneManager); + } + + EditorGUILayout.BeginHorizontal(); + + var changed = false; + EditorGUILayout.BeginVertical(); + base.OnInspectorGUI(); + EditorGUILayout.LabelField("Preview Weight"); + var previewSlider = EditorGUILayout.Slider(m_previewSlider, 0, 1.0f); + + EditorGUILayout.EndVertical(); + + if (m_serializedEditor.IsBinary) + { + previewSlider = Mathf.Round(previewSlider); + } + + if (previewSlider != m_previewSlider) + { + m_previewSlider = previewSlider; + changed = true; + } + + EditorGUILayout.EndHorizontal(); + Separator(); + // EditorGUILayout.Space(); + + if (m_serializedEditor.Draw(out VRM10Expression bakeValue)) + { + changed = true; + } + + if (changed && PreviewSceneManager != null) + { + PreviewSceneManager.Bake(bakeValue, m_previewSlider); + } + } + } +} diff --git a/Assets/VRM10/Editor/Components/Expression/VRM10ExpressionEditor.cs.meta b/Assets/VRM10/Editor/Components/Expression/VRM10ExpressionEditor.cs.meta new file mode 100644 index 000000000..bf5603ac9 --- /dev/null +++ b/Assets/VRM10/Editor/Components/Expression/VRM10ExpressionEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0565a023d1e0a114691e3e6f10e59039 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/Components/FirstPerson.meta b/Assets/VRM10/Editor/Components/FirstPerson.meta new file mode 100644 index 000000000..af8a45f62 --- /dev/null +++ b/Assets/VRM10/Editor/Components/FirstPerson.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 33642286ccbb52542afb65da8ab0a667 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/Components/FirstPerson/RendererFirstPersonFlagsDrawer.cs b/Assets/VRM10/Editor/Components/FirstPerson/RendererFirstPersonFlagsDrawer.cs new file mode 100644 index 000000000..6020db328 --- /dev/null +++ b/Assets/VRM10/Editor/Components/FirstPerson/RendererFirstPersonFlagsDrawer.cs @@ -0,0 +1,40 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + + +namespace UniVRM10 +{ + [CustomPropertyDrawer(typeof(RendererFirstPersonFlags))] + public class RendererFirstPersonFlagsDrawer : PropertyDrawer + { + static Rect LeftSide(Rect position, float width) + { + return new Rect(position.x, position.y, position.width-width, position.height); + } + static Rect RightSide(Rect position, float width) + { + return new Rect(position.x + (position.width-width), position.y, width, position.height); + } + + public override void OnGUI(Rect position, + SerializedProperty property, GUIContent label) + { + var rendererProp = property.FindPropertyRelative("Renderer"); + var flagProp = property.FindPropertyRelative("FirstPersonFlag"); + + const float WIDTH = 140.0f; + EditorGUI.PropertyField(LeftSide(position, WIDTH), rendererProp, new GUIContent(""), true); + EditorGUI.PropertyField(RightSide(position, WIDTH), flagProp, new GUIContent(""), true); + } + + /* + public override float GetPropertyHeight(SerializedProperty property, + GUIContent label) + { + return 60.0f; + } + */ + } +} diff --git a/Assets/VRM10/Editor/Components/FirstPerson/RendererFirstPersonFlagsDrawer.cs.meta b/Assets/VRM10/Editor/Components/FirstPerson/RendererFirstPersonFlagsDrawer.cs.meta new file mode 100644 index 000000000..2b7afb22f --- /dev/null +++ b/Assets/VRM10/Editor/Components/FirstPerson/RendererFirstPersonFlagsDrawer.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: eba8708502533514b9fe5211d55eb7df +timeCreated: 1520848617 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/Components/SpringBone.meta b/Assets/VRM10/Editor/Components/SpringBone.meta new file mode 100644 index 000000000..0e6c12cb5 --- /dev/null +++ b/Assets/VRM10/Editor/Components/SpringBone.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1ac8223d120f39b4a9734061b3a598aa +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/Components/SpringBone/CustomInspector.cs b/Assets/VRM10/Editor/Components/SpringBone/CustomInspector.cs new file mode 100644 index 000000000..fb8d83a76 --- /dev/null +++ b/Assets/VRM10/Editor/Components/SpringBone/CustomInspector.cs @@ -0,0 +1,59 @@ +using System; +using UnityEditor; +using UnityEngine; + +namespace UniVRM10 +{ + struct CustomInspector : IDisposable + { + SerializedObject serializedObject; + int m_depth; + + Func m_callback; + + public CustomInspector(SerializedObject so, int depth = 0, Func callback = null) + { + m_depth = depth; + m_callback = callback; + serializedObject = so; + serializedObject.Update(); + } + + public void OnInspectorGUI() + { + int currentDepth = 0; + for (var iterator = serializedObject.GetIterator(); iterator.NextVisible(true);) + { + var isCollapsed = currentDepth < iterator.depth; + if (isCollapsed) + { + continue; + } + +#if DEBUG + // Debug.Log($"{iterator.propertyPath}({iterator.propertyType})"); +#endif + + if (m_callback is null || !m_callback(iterator)) + { + EditorGUI.indentLevel = iterator.depth + m_depth; + EditorGUILayout.PropertyField(iterator, false); + } + + if (iterator.isExpanded) + { + currentDepth = iterator.depth + 1; + } + else + { + currentDepth = iterator.depth; + } + } + } + + public void Dispose() + { + serializedObject.ApplyModifiedProperties(); + } + } +} diff --git a/Assets/VRM10/Editor/Components/SpringBone/CustomInspector.cs.meta b/Assets/VRM10/Editor/Components/SpringBone/CustomInspector.cs.meta new file mode 100644 index 000000000..a85877f7e --- /dev/null +++ b/Assets/VRM10/Editor/Components/SpringBone/CustomInspector.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2539e35b249361d40aad74d271dcb34e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/Components/SpringBone/VRMSpringBoneColliderGroupEditor.cs b/Assets/VRM10/Editor/Components/SpringBone/VRMSpringBoneColliderGroupEditor.cs new file mode 100644 index 000000000..b982d82f0 --- /dev/null +++ b/Assets/VRM10/Editor/Components/SpringBone/VRMSpringBoneColliderGroupEditor.cs @@ -0,0 +1,81 @@ +using System.Linq; +using UnityEditor; +using UnityEngine; + + +namespace UniVRM10 +{ + [CustomEditor(typeof(VRMSpringBoneColliderGroup))] + public class VRMSpringBoneColliderGroupEditor : Editor + { + VRMSpringBoneColliderGroup m_target; + + private void OnEnable() + { + m_target = (VRMSpringBoneColliderGroup)target; + } + + private void OnSceneGUI() + { + Undo.RecordObject(m_target, "VRMSpringBoneColliderGroupEditor"); + + Handles.matrix = m_target.transform.localToWorldMatrix; + Gizmos.color = Color.green; + + bool changed = false; + + foreach (var x in m_target.Colliders) + { + var offset = Handles.PositionHandle(x.Offset, Quaternion.identity); + if (offset != x.Offset) + { + changed = true; + x.Offset = offset; + } + } + + if (changed) + { + EditorUtility.SetDirty(m_target); + } + } + + [MenuItem("CONTEXT/VRM10SpringBoneColliderGroup/X Mirror")] + private static void InvertOffsetX(MenuCommand command) + { + var target = command.context as VRMSpringBoneColliderGroup; + if (target == null) return; + + Undo.RecordObject(target, "X Mirror"); + + foreach (var sphereCollider in target.Colliders) + { + var offset = sphereCollider.Offset; + offset.x *= -1f; + sphereCollider.Offset = offset; + } + } + + [MenuItem("CONTEXT/VRM10SpringBoneColliderGroup/Sort Colliders by Radius")] + private static void SortByRadius(MenuCommand command) + { + var target = command.context as VRMSpringBoneColliderGroup; + if (target == null) return; + + Undo.RecordObject(target, "Sort Colliders by Radius"); + + target.Colliders = target.Colliders.OrderBy(x => -x.Radius).ToArray(); + } + + [MenuItem("CONTEXT/VRM10SpringBoneColliderGroup/Sort Colliders by Offset Y")] + private static void SortByOffsetY(MenuCommand command) + { + var target = command.context as VRMSpringBoneColliderGroup; + if (target == null) return; + + Undo.RecordObject(target, "Sort Colliders by Offset Y"); + + target.Colliders = target.Colliders.OrderBy(x => -x.Offset.y).ToArray(); + } + } +} diff --git a/Assets/VRM10/Editor/Components/SpringBone/VRMSpringBoneColliderGroupEditor.cs.meta b/Assets/VRM10/Editor/Components/SpringBone/VRMSpringBoneColliderGroupEditor.cs.meta new file mode 100644 index 000000000..00b5aa5c0 --- /dev/null +++ b/Assets/VRM10/Editor/Components/SpringBone/VRMSpringBoneColliderGroupEditor.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: a092aa1d5a21feb4db6523a3284ca561 +timeCreated: 1519735770 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/Components/SpringBone/VRMSpringBoneView.cs b/Assets/VRM10/Editor/Components/SpringBone/VRMSpringBoneView.cs new file mode 100644 index 000000000..e69acddb6 --- /dev/null +++ b/Assets/VRM10/Editor/Components/SpringBone/VRMSpringBoneView.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using UnityEditor.IMGUI.Controls; + +namespace UniVRM10 +{ + class VRMSpringBoneTreeView : TreeView + + { + VRM10Controller m_root; + + Dictionary m_boneMap = new Dictionary(); + + public bool TryGetSpringBone(int id, out VRMSpringBone bone) + { + return m_boneMap.TryGetValue(id, out bone); + } + + public VRMSpringBoneTreeView(TreeViewState treeViewState, VRM10Controller root) + : base(treeViewState) + { + m_root = root; + Reload(); + } + + protected override bool CanMultiSelect(TreeViewItem item) + { + return false; + } + + protected override TreeViewItem BuildRoot() + { + m_boneMap.Clear(); + var root = new TreeViewItem { id = 0, depth = -1, displayName = m_root.name }; + var allItems = new List(); + var bones = m_root.GetComponentsInChildren(); + var id = 1; + for (int i = 0; i < bones.Length; ++i, ++id) + { + var bone = bones[i]; + + m_boneMap.Add(id, bone); + + allItems.Add(new TreeViewItem + { + id = id, + displayName = bone.name, + }); + } + // Utility method that initializes the TreeViewItem.children and -parent for all items. + SetupParentsAndChildrenFromDepths(root, allItems); + + // Return root of the tree + return root; + } + } +} diff --git a/Assets/VRM10/Editor/Components/SpringBone/VRMSpringBoneView.cs.meta b/Assets/VRM10/Editor/Components/SpringBone/VRMSpringBoneView.cs.meta new file mode 100644 index 000000000..a68f49aca --- /dev/null +++ b/Assets/VRM10/Editor/Components/SpringBone/VRMSpringBoneView.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cd352799c630a8d49813a5c4d937a847 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/Components/SpringBone/VRMSpringBoneWindow.cs b/Assets/VRM10/Editor/Components/SpringBone/VRMSpringBoneWindow.cs new file mode 100644 index 000000000..21f772519 --- /dev/null +++ b/Assets/VRM10/Editor/Components/SpringBone/VRMSpringBoneWindow.cs @@ -0,0 +1,111 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEditor.IMGUI.Controls; +using UnityEngine; + + +namespace UniVRM10 +{ + public class VRMSpringBoneWindow : EditorWindow + { + [MenuItem("VRM/UniVRM-" + UniVRM10.VRMVersion.VERSION + "/VRMSpringBoneWindow")] + public static void ShowEditorWindow() + { + var wnd = GetWindow(); + wnd.titleContent = new GUIContent("VRMSpringBoneWindow"); + } + + void OnEnable() + { + minSize = new Vector2(100, 100); + maxSize = new Vector2(4000, 4000); + Selection.selectionChanged += OnSelectionChanged; + } + + void OnDisable() + { + Selection.selectionChanged -= OnSelectionChanged; + } + + VRM10Controller m_currentRoot; + + [SerializeField] + TreeViewState m_treeViewState; + VRMSpringBoneTreeView m_treeView; + + Rect m_treeRect; + Rect m_inspectorRect; + Vector2 m_inspectorScrollPos; + + void OnGUI() + { + if (m_treeView is null) + { + return; + } + + // bone selector + Rect fullRect = GUILayoutUtility.GetRect(0, 100000, 0, 10000); + var treeRect = new Rect(fullRect.x, fullRect.y, fullRect.width, EditorGUIUtility.singleLineHeight * 3); + var inspectorRect = new Rect(fullRect.x, treeRect.y + treeRect.height, fullRect.width, fullRect.height - treeRect.height); + if (Event.current.type == EventType.Repaint) + { + m_treeRect = treeRect; + m_inspectorRect = inspectorRect; + // Debug.Log($"{m_treeRect}, {m_inspectorRect}"); + } + + m_treeView.OnGUI(m_treeRect); + + GUILayout.BeginArea(m_inspectorRect); + m_inspectorScrollPos = GUILayout.BeginScrollView(m_inspectorScrollPos, GUI.skin.box); + if (m_treeViewState.selectedIDs.Any()) + { + // selected な SpringBone の Inspector を描画する + if (m_treeView.TryGetSpringBone(m_treeViewState.selectedIDs[0], out VRMSpringBone bone)) + { + // Debug.Log(bone); + using (var inspector = new CustomInspector(new SerializedObject(bone))) + { + inspector.OnInspectorGUI(); + } + } + } + GUILayout.EndScrollView(); + GUILayout.EndArea(); + } + + void OnSelectionChanged() + { + var go = Selection.activeObject as GameObject; + if (go is null) + { + return; + } + + var meta = go.GetComponentInParent(); + if (meta == m_currentRoot) + { + return; + } + + m_currentRoot = meta; + if (m_currentRoot is null) + { + m_treeViewState = null; + m_treeView = null; + } + else + { + // update treeview + Debug.Log(m_currentRoot); + m_treeViewState = new TreeViewState(); + m_treeView = new VRMSpringBoneTreeView(m_treeViewState, m_currentRoot); + } + + Repaint(); + } + } +} diff --git a/Assets/VRM10/Editor/Components/SpringBone/VRMSpringBoneWindow.cs.meta b/Assets/VRM10/Editor/Components/SpringBone/VRMSpringBoneWindow.cs.meta new file mode 100644 index 000000000..b92fd0895 --- /dev/null +++ b/Assets/VRM10/Editor/Components/SpringBone/VRMSpringBoneWindow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6903dd580e905a243b9841fafe3871bb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/Components/VRM10ControllerEditor.cs b/Assets/VRM10/Editor/Components/VRM10ControllerEditor.cs new file mode 100644 index 000000000..bc61fdc02 --- /dev/null +++ b/Assets/VRM10/Editor/Components/VRM10ControllerEditor.cs @@ -0,0 +1,226 @@ +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; +using System.Linq; + + +namespace UniVRM10 +{ + [CustomEditor(typeof(VRM10Controller))] + public class VRM10ControllerEditor : Editor + { + VRM10Controller m_target; + SkinnedMeshRenderer[] m_renderers; + Dictionary m_expressionKeyWeights = new Dictionary(); + + public class ExpressionSlider + { + Dictionary m_expressionKeys; + ExpressionKey m_key; + + public ExpressionSlider(Dictionary expressionKeys, ExpressionKey key) + { + m_expressionKeys = expressionKeys; + m_key = key; + } + + public KeyValuePair Slider() + { + var oldValue = m_expressionKeys[m_key]; + var enable = GUI.enabled; + GUI.enabled = Application.isPlaying; + var newValue = EditorGUILayout.Slider(m_key.ToString(), oldValue, 0, 1.0f); + GUI.enabled = enable; + return new KeyValuePair(m_key, newValue); + } + } + List m_sliders; + + void OnEnable() + { + m_target = (VRM10Controller)target; + if (m_target.Expression.ExpressionAvatar != null && m_target.Expression.ExpressionAvatar.Clips != null) + { + m_expressionKeyWeights = m_target.Expression.ExpressionAvatar.Clips.ToDictionary(x => ExpressionKey.CreateFromClip(x), x => 0.0f); + m_sliders = m_target.Expression.ExpressionAvatar.Clips + .Where(x => x != null) + .Select(x => new ExpressionSlider(m_expressionKeyWeights, ExpressionKey.CreateFromClip(x))) + .ToList() + ; + } + + if (m_target.Meta) + { + m_metaEditor = Editor.CreateEditor(m_target.Meta); + } + m_controller = serializedObject.FindProperty(nameof(m_target.Controller)); + m_expression = serializedObject.FindProperty(nameof(m_target.Expression)); + m_lookAt = serializedObject.FindProperty(nameof(m_target.LookAt)); + m_firstPerson = serializedObject.FindProperty(nameof(m_target.FirstPerson)); + m_asset = serializedObject.FindProperty(nameof(m_target.ModelAsset)); + } + + void OnDisable() + { + if (m_metaEditor) + { + UnityEditor.Editor.DestroyImmediate(m_metaEditor); + m_metaEditor = null; + } + } + + enum Tabs + { + Meta, + Controller, + Expression, + LookAt, + FirstPerson, + Assets, + } + Tabs _tab; + + Editor m_metaEditor; + SerializedProperty m_controller; + SerializedProperty m_expression; + SerializedProperty m_lookAt; + SerializedProperty m_firstPerson; + SerializedProperty m_asset; + + public override void OnInspectorGUI() + { + { + var backup = GUI.enabled; + GUI.enabled = true; + _tab = MeshUtility.TabBar.OnGUI(_tab); + GUI.enabled = backup; + } + + serializedObject.Update(); + + // base.OnInspectorGUI(); + switch (_tab) + { + case Tabs.Meta: + m_metaEditor?.OnInspectorGUI(); + break; + + case Tabs.Controller: + RecursiveProperty(m_controller); + break; + + case Tabs.Expression: + ExpressionGUI(); + RecursiveProperty(m_expression); + break; + + case Tabs.LookAt: + RecursiveProperty(m_lookAt); + break; + + case Tabs.FirstPerson: + RecursiveProperty(m_firstPerson); + break; + + case Tabs.Assets: + RecursiveProperty(m_asset); + break; + } + + serializedObject.ApplyModifiedProperties(); + } + + void ExpressionGUI() + { + EditorGUILayout.Space(); + EditorGUILayout.LabelField("IgnoreStatus", EditorStyles.boldLabel); + EditorGUI.BeginDisabledGroup(true); + EditorGUILayout.Toggle("Ignore Blink", m_target.Expression.IgnoreBlink); + EditorGUILayout.Toggle("Ignore Look At", m_target.Expression.IgnoreLookAt); + EditorGUILayout.Toggle("Ignore Mouth", m_target.Expression.IgnoreMouth); + EditorGUI.EndDisabledGroup(); + + if (!Application.isPlaying) + { + EditorGUILayout.HelpBox("Enable when playing", MessageType.Info); + } + + if (m_target.Expression.ExpressionAvatar == null) + { + return; + } + + if (m_sliders != null) + { + var sliders = m_sliders.Select(x => x.Slider()); + foreach (var slider in sliders) + { + m_expressionKeyWeights[slider.Key] = slider.Value; + } + m_target.Expression.SetValues(m_expressionKeyWeights.Select(x => new KeyValuePair(x.Key, x.Value))); + } + } + + static void RecursiveProperty(SerializedProperty property) + { + var depth = property.depth; + var iterator = property.Copy(); + for (var enterChildren = true; iterator.NextVisible(enterChildren); enterChildren = false) + { + if (iterator.depth < depth) + return; + + depth = iterator.depth; + + using (new EditorGUI.DisabledScope("m_Script" == iterator.propertyPath)) + EditorGUILayout.PropertyField(iterator, true); + } + } + + void OnSceneGUI() + { + OnSceneGUIOffset(); + if (!Application.isPlaying) + { + // offset + var p = m_target.LookAt.OffsetFromHead; + Handles.Label(m_target.Head.position, $"fromHead: [{p.x:0.00}, {p.y:0.00}, {p.z:0.00}]"); + } + else + { + m_target.LookAt.OnSceneGUILookAt(m_target.Head); + } + } + + void OnSceneGUIOffset() + { + var component = target as VRM10Controller; + if (!component.LookAt.DrawGizmo) + { + return; + } + + var head = component.Head; + if (head == null) + { + return; + } + + EditorGUI.BeginChangeCheck(); + + var worldOffset = head.localToWorldMatrix.MultiplyPoint(component.LookAt.OffsetFromHead); + worldOffset = Handles.PositionHandle(worldOffset, head.rotation); + + Handles.DrawDottedLine(head.position, worldOffset, 5); + Handles.SphereHandleCap(0, head.position, Quaternion.identity, 0.01f, Event.current.type); + Handles.SphereHandleCap(0, worldOffset, Quaternion.identity, 0.01f, Event.current.type); + + if (EditorGUI.EndChangeCheck()) + { + Undo.RecordObject(component, "Changed FirstPerson"); + + component.LookAt.OffsetFromHead = head.worldToLocalMatrix.MultiplyPoint(worldOffset); + } + } + } +} diff --git a/Assets/VRM10/Editor/Components/VRM10ControllerEditor.cs.meta b/Assets/VRM10/Editor/Components/VRM10ControllerEditor.cs.meta new file mode 100644 index 000000000..56180f5f3 --- /dev/null +++ b/Assets/VRM10/Editor/Components/VRM10ControllerEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1aa2c8486d14a1f4cbe81ff2fb5d0438 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/GeneratorMenu.cs b/Assets/VRM10/Editor/GeneratorMenu.cs new file mode 100644 index 000000000..d2680c55d --- /dev/null +++ b/Assets/VRM10/Editor/GeneratorMenu.cs @@ -0,0 +1,61 @@ +using System.IO; +using UnityEditor; +using UnityEngine; + +namespace UniVRM10 +{ + /// + /// JsonSchema から vrm10 のシリアライザーを生成する。 + /// + /// * https://github.com/KhronosGroup/glTF + /// * https://github.com/vrm-c/vrm-specification + /// + /// を UniVRM の隣に clone しておくこと。 + /// + /// * UniVRM + /// * glTF + /// * vrm-specification + /// + /// + public static class Menu + { + [MenuItem("VRM/UniVRM-" + UniVRM10.VRMVersion.VERSION + "/Generate from JsonSchema")] + public static void Main() + { + var root = new DirectoryInfo(Path.GetFullPath(Path.Combine(Application.dataPath, "../../"))); + + var gltf = new FileInfo(Path.Combine(root.FullName, "glTF/specification/2.0/schema/glTF.schema.json")); + + var args = new string[] + { + // VRMC_vrm + "vrm-specification/specification/VRMC_vrm-1.0_draft/schema/VRMC_vrm.schema.json", + "UniVRM/Assets/VRM10/Runtime/Format/Vrm", // dst + // VRMC_constraints + "vrm-specification/specification/VRMC_constraints-1.0_draft/schema/VRMC_constraints.schema.json", + "UniVRM/Assets/VRM10/Runtime/Format/Constraints", // dst + // + "vrm-specification/specification/VRMC_materials_mtoon-1.0_draft/schema/VRMC_materials_mtoon.schema.json", + "UniVRM/Assets/VRM10/Runtime/Format/MaterialsMToon", // dst + // + "vrm-specification/specification/VRMC_node_collider_1.0_draft/schema/VRMC_node_collider.json", + "UniVRM/Assets/VRM10/Runtime/Format/NodeCollider", // dst + // + "vrm-specification/specification/VRMC_springBone-1.0_draft/schema/VRMC_springBone.schema.json", + "UniVRM/Assets/VRM10/Runtime/Format/SpringBone", // dst + }; + + for (int i = 0; i < args.Length; i += 2) + { + var extensionSchemaPath = new FileInfo(Path.Combine(root.FullName, args[i])); + var parser = new UniGLTF.JsonSchema.JsonSchemaParser(gltf.Directory, extensionSchemaPath.Directory); + var extensionSchema = parser.Load(extensionSchemaPath, ""); + // extensionSchema.Dump(); + + var dst = new DirectoryInfo(Path.Combine(root.FullName, args[i + 1])); + Debug.Log(dst); + GenerateUniGLTFSerialization.Generator.GenerateTo(extensionSchema, dst, clearFolder: true); + } + } + } +} diff --git a/Assets/VRM10/Editor/GeneratorMenu.cs.meta b/Assets/VRM10/Editor/GeneratorMenu.cs.meta new file mode 100644 index 000000000..da8113e9c --- /dev/null +++ b/Assets/VRM10/Editor/GeneratorMenu.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7f8755f9d806837408cd04ebb587489c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/PackageResource.cs b/Assets/VRM10/Editor/PackageResource.cs new file mode 100644 index 000000000..5e9a187e2 --- /dev/null +++ b/Assets/VRM10/Editor/PackageResource.cs @@ -0,0 +1,44 @@ +using UnityEditor; +using UnityEngine; + +namespace UniVRM10 +{ + /// + /// + /// + public static class PackageResource + { + /// + /// Local時のAssetPath + /// + public const string LocalBase = "Assets/VRM10"; + + /// + /// UPM参照時のAssetPath + /// + public const string PackageBase = "Packages/com.vrmc.univrm"; + + /// + /// Try local then try package. + /// + /// + /// + /// + public static T ResourceLocalOrUPM(string relpath) where T : UnityEngine.Object + { + var path = $"{LocalBase}/{relpath}"; + var asset = AssetDatabase.LoadAssetAtPath(path); + if (asset is null) + { + // Debug.LogWarning($"fail to LoadAssetAtPath: {path}"); + path = $"{PackageResource.PackageBase}/{relpath}"; + asset = AssetDatabase.LoadAssetAtPath(path); + } + // if (asset is null) + // { + // Debug.LogWarning($"fail to LoadAssetAtPath: {path}"); + // } + return asset; + } + } +} diff --git a/Assets/VRM10/Editor/PackageResource.cs.meta b/Assets/VRM10/Editor/PackageResource.cs.meta new file mode 100644 index 000000000..f6b7d3266 --- /dev/null +++ b/Assets/VRM10/Editor/PackageResource.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6d0de6998a8026c4ca69e7a146b135f9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/ScriptedImporter.meta b/Assets/VRM10/Editor/ScriptedImporter.meta new file mode 100644 index 000000000..f502f1ee0 --- /dev/null +++ b/Assets/VRM10/Editor/ScriptedImporter.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d8ff621b39332124aa9ebb273ff09a89 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/VRM10.Editor.asmdef b/Assets/VRM10/Editor/VRM10.Editor.asmdef new file mode 100644 index 000000000..eb354e990 --- /dev/null +++ b/Assets/VRM10/Editor/VRM10.Editor.asmdef @@ -0,0 +1,20 @@ +{ + "name": "VRM10.Editor", + "references": [ + "VRM10", + "VrmLib", + "MeshUtility", + "MeshUtility.Editor", + "UniGLTF.Editor" + ], + "optionalUnityReferences": [], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [] +} \ No newline at end of file diff --git a/Assets/VRM10/Editor/VRM10.Editor.asmdef.meta b/Assets/VRM10/Editor/VRM10.Editor.asmdef.meta new file mode 100644 index 000000000..fb2020c19 --- /dev/null +++ b/Assets/VRM10/Editor/VRM10.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 6aae90b2207f6fe4fa38ba53e3ab92ef +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/VRM10MetaObjectEditor.cs b/Assets/VRM10/Editor/VRM10MetaObjectEditor.cs new file mode 100644 index 000000000..9a1f06bbd --- /dev/null +++ b/Assets/VRM10/Editor/VRM10MetaObjectEditor.cs @@ -0,0 +1,285 @@ +using UnityEditor; +using UnityEngine; +using MeshUtility.M17N; + +namespace UniVRM10 +{ + [CustomEditor(typeof(VRM10MetaObject))] + public class VRMM10etaObjectEditor : Editor + { + class ValidateProperty + { + public SerializedProperty m_prop; + + public delegate (string, MessageType) Validator(SerializedProperty prop); + Validator m_validator; + + public ValidateProperty(SerializedProperty prop, Validator validator) + { + m_prop = prop; + m_validator = validator; + } + + public void OnGUI() + { + // var old = m_prop.stringValue; + if (m_prop.propertyType == SerializedPropertyType.Generic) + { + if (m_prop.arrayElementType != null) + { + EditorGUILayout.LabelField(m_prop.name); + + var depth = m_prop.depth; + var iterator = m_prop.Copy(); + for (var enterChildren = true; iterator.NextVisible(enterChildren); enterChildren = false) + { + if (iterator.depth < depth) + break; + + depth = iterator.depth; + + // using (new EditorGUI.DisabledScope("m_Script" == iterator.propertyPath)) + EditorGUILayout.PropertyField(iterator, true); + } + } + else + { + throw new System.NotImplementedException(); + } + } + else + { + EditorGUILayout.PropertyField(m_prop); + } + var (msg, msgType) = m_validator(m_prop); + if (!string.IsNullOrEmpty(msg)) + { + EditorGUILayout.HelpBox(msg, msgType); + } + // return old != m_prop.stringValue; + } + } + + VRM10MetaObject m_target; + SerializedProperty m_Script; + SerializedProperty m_exporterVersion; + SerializedProperty m_thumbnail; + ValidateProperty m_title; + ValidateProperty m_version; + ValidateProperty m_author; + ValidateProperty m_contact; + ValidateProperty m_reference; + + SerializedProperty m_AllowedUser; + SerializedProperty m_ViolentUssage; + SerializedProperty m_SexualUssage; + SerializedProperty m_CommercialUssage; + SerializedProperty m_PoliticalOrReligiousUsage; + SerializedProperty m_OtherPermissionUrl; + + SerializedProperty m_LicenseType; + SerializedProperty m_OtherLicenseUrl; + + static string RequiredMessage(string name) + { + switch (MeshUtility.M17N.Getter.Lang) + { + case MeshUtility.M17N.Languages.ja: + return $"必須項目。{name} を入力してください"; + + case MeshUtility.M17N.Languages.en: + return $"{name} is required"; + + default: + throw new System.NotImplementedException(); + } + } + + private void OnEnable() + { + if (target == null) + { + return; + } + m_target = (VRM10MetaObject)target; + + m_Script = serializedObject.FindProperty("m_Script"); + m_exporterVersion = serializedObject.FindProperty(nameof(m_target.ExporterVersion)); + m_thumbnail = serializedObject.FindProperty(nameof(m_target.Thumbnail)); + + m_title = new ValidateProperty(serializedObject.FindProperty(nameof(m_target.Name)), prop => + { + if (string.IsNullOrEmpty(prop.stringValue)) + { + return (RequiredMessage(prop.name), MessageType.Error); + } + return ("", MessageType.None); + }); + m_version = new ValidateProperty(serializedObject.FindProperty(nameof(m_target.Version)), prop => + { + // if (string.IsNullOrEmpty(prop.stringValue)) + // { + // return (RequiredMessage(prop.name), MessageType.Error); + // } + return ("", MessageType.None); + }); + m_author = new ValidateProperty(serializedObject.FindProperty(nameof(m_target.Authors)), prop => + { + if (prop.arraySize == 0) + { + return (RequiredMessage(prop.name), MessageType.Error); + } + return ("", MessageType.None); + }); + m_contact = new ValidateProperty(serializedObject.FindProperty(nameof(m_target.ContactInformation)), prop => + { + return ("", MessageType.None); + }); + m_reference = new ValidateProperty(serializedObject.FindProperty(nameof(m_target.Reference)), prop => + { + return ("", MessageType.None); + }); + + m_AllowedUser = serializedObject.FindProperty(nameof(m_target.AllowedUser)); + m_ViolentUssage = serializedObject.FindProperty(nameof(m_target.ViolentUsage)); + m_SexualUssage = serializedObject.FindProperty(nameof(m_target.SexualUsage)); + m_CommercialUssage = serializedObject.FindProperty(nameof(m_target.CommercialUsage)); + m_PoliticalOrReligiousUsage = serializedObject.FindProperty(nameof(m_target.PoliticalOrReligiousUsage)); + m_OtherPermissionUrl = serializedObject.FindProperty(nameof(m_target.OtherLicenseUrl)); + + // m_LicenseType = serializedObject.FindProperty(nameof(m_target.)); + + m_OtherLicenseUrl = serializedObject.FindProperty(nameof(m_target.OtherLicenseUrl)); + } + + enum MessageKeys + { + [LangMsg(Languages.ja, "アバターの人格に関する許諾範囲")] + [LangMsg(Languages.en, "Personation / Characterization Permission")] + PERSONATION, + + [LangMsg(Languages.ja, "アバターに人格を与えることの許諾範囲")] + [LangMsg(Languages.en, "A person who can perform with this avatar")] + ALLOWED_USER, + + [LangMsg(Languages.ja, "このアバターを用いて暴力表現を演じることの許可")] + [LangMsg(Languages.en, "Violent acts using this avatar")] + VIOLENT_USAGE, + + [LangMsg(Languages.ja, "このアバターを用いて性的表現を演じることの許可")] + [LangMsg(Languages.en, "Sexuality acts using this avatar")] + SEXUAL_USAGE, + + [LangMsg(Languages.ja, "商用利用の許可")] + [LangMsg(Languages.en, "For commercial use")] + COMMERCIAL_USAGE, + + [LangMsg(Languages.ja, "再配布・改変に関する許諾範囲")] + [LangMsg(Languages.en, "Redistribution / Modifications License")] + REDISTRIBUTION_MODIFICATIONS, + + // [LangMsg(Languages.ja, "")] + // [LangMsg(Languages.en, "")] + } + + static string Msg(MessageKeys key) + { + return MeshUtility.M17N.Getter.Msg(key); + } + + bool m_foldoutInfo = true; + bool m_foldoutPermission = true; + bool m_foldoutDistribution = true; + + public override void OnInspectorGUI() + { + serializedObject.Update(); + + if (VRMVersion.IsNewer(m_exporterVersion.stringValue)) + { + EditorGUILayout.HelpBox("Check UniVRM new version. https://github.com/dwango/UniVRM/releases", MessageType.Warning); + } + + // texture + EditorGUILayout.BeginHorizontal(); + { + EditorGUILayout.BeginVertical(); + GUI.enabled = false; + EditorGUILayout.PropertyField(m_exporterVersion); + GUI.enabled = true; + EditorGUILayout.PropertyField(m_thumbnail); + EditorGUILayout.EndVertical(); + m_thumbnail.objectReferenceValue = TextureField("", (Texture2D)m_thumbnail.objectReferenceValue, 100); + } + EditorGUILayout.EndHorizontal(); + + m_foldoutInfo = EditorGUILayout.Foldout(m_foldoutInfo, "Information"); + if (m_foldoutInfo) + { + m_title.OnGUI(); + m_version.OnGUI(); + m_author.OnGUI(); + m_contact.OnGUI(); + m_reference.OnGUI(); + } + // EditorGUILayout.LabelField("License ", EditorStyles.boldLabel); + m_foldoutPermission = EditorGUILayout.Foldout(m_foldoutPermission, Msg(MessageKeys.PERSONATION)); + if (m_foldoutPermission) + { + var backup = EditorGUIUtility.labelWidth; + RightFixedPropField(m_AllowedUser, Msg(MessageKeys.ALLOWED_USER)); + RightFixedPropField(m_ViolentUssage, Msg(MessageKeys.VIOLENT_USAGE)); + RightFixedPropField(m_SexualUssage, Msg(MessageKeys.SEXUAL_USAGE)); + RightFixedPropField(m_CommercialUssage, Msg(MessageKeys.COMMERCIAL_USAGE)); + EditorGUILayout.PropertyField(m_OtherPermissionUrl, new GUIContent("Other License Url")); + EditorGUIUtility.labelWidth = backup; + } + + m_foldoutDistribution = EditorGUILayout.Foldout(m_foldoutDistribution, Msg(MessageKeys.REDISTRIBUTION_MODIFICATIONS)); + if (m_foldoutDistribution) + { + // var licenseType = m_LicenseType; + // EditorGUILayout.PropertyField(licenseType); + // if ((LicenseType)licenseType.intValue == LicenseType.Other) + // { + // EditorGUILayout.PropertyField(m_OtherLicenseUrl); + // } + } + + serializedObject.ApplyModifiedProperties(); + } + + static (Rect, Rect) FixedRight(Rect r, int width) + { + if (width > r.width) + { + width = (int)r.width; + } + return ( + new Rect(r.x, r.y, r.width - width, r.height), + new Rect(r.x + r.width - width, r.y, width, r.height) + ); + } + + static void RightFixedPropField(SerializedProperty prop, string label) + { + var r = GUILayoutUtility.GetRect(GUIContent.none, GUIStyle.none, GUILayout.Height(EditorGUIUtility.singleLineHeight)); + var (left, right) = FixedRight(r, 64); + // Debug.Log($"{left}, {right}"); + EditorGUI.LabelField(left, label); + EditorGUI.PropertyField(right, prop, new GUIContent(""), false); + } + + private static Texture2D TextureField(string name, Texture2D texture, int size) + { + GUILayout.BeginHorizontal(); + var style = new GUIStyle(GUI.skin.label); + style.alignment = TextAnchor.UpperCenter; + //style.fixedWidth = size; + GUILayout.Label(name, style); + var result = (Texture2D)EditorGUILayout.ObjectField(texture, typeof(Texture2D), false, GUILayout.Width(size), GUILayout.Height(size)); + GUILayout.EndVertical(); + return result; + } + } +} diff --git a/Assets/VRM10/Editor/VRM10MetaObjectEditor.cs.meta b/Assets/VRM10/Editor/VRM10MetaObjectEditor.cs.meta new file mode 100644 index 000000000..8134e5641 --- /dev/null +++ b/Assets/VRM10/Editor/VRM10MetaObjectEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 825701d5bd059444ca9fb17f5e283608 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/Vrm10ExportDialog.cs b/Assets/VRM10/Editor/Vrm10ExportDialog.cs new file mode 100644 index 000000000..db9821ad1 --- /dev/null +++ b/Assets/VRM10/Editor/Vrm10ExportDialog.cs @@ -0,0 +1,356 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using MeshUtility; +using UnityEditor; +using UnityEngine; +using VrmLib; + +namespace UniVRM10 +{ + public class VRM10ExportDialog : EditorWindow + { + const string CONVERT_HUMANOID_KEY = VRMVersion.MENU + "/Export VRM-1.0"; + + [MenuItem(CONVERT_HUMANOID_KEY, false, 1)] + private static void ExportFromMenu() + { + var window = (VRM10ExportDialog)GetWindow(typeof(VRM10ExportDialog)); + window.titleContent = new GUIContent("VRM-1.0 Exporter"); + window.Show(); + } + + enum Tabs + { + Meta, + Mesh, + ExportSettings, + } + Tabs _tab; + + MeshUtility.ExporterDialogState m_state; + + VRM10MetaObject m_meta; + VRM10MetaObject Meta + { + get { return m_meta; } + set + { + if (value != null && AssetDatabase.IsSubAsset(value)) + { + Debug.Log("copy VRM10MetaObject"); + value.CopyTo(m_tmpMeta); + return; + } + + if (m_meta == value) + { + return; + } + if (m_metaEditor != null) + { + UnityEditor.Editor.DestroyImmediate(m_metaEditor); + m_metaEditor = null; + } + m_meta = value; + } + } + VRM10MetaObject m_tmpMeta; + + Editor m_metaEditor; + + void OnEnable() + { + // Debug.Log("OnEnable"); + Undo.willFlushUndoRecord += Repaint; + Selection.selectionChanged += Repaint; + + m_tmpMeta = ScriptableObject.CreateInstance(); + + m_state = new MeshUtility.ExporterDialogState(); + m_state.ExportRootChanged += (root) => + { + // update meta + if (root == null) + { + Meta = null; + } + else + { + var controller = root.GetComponent(); + if (controller != null) + { + Meta = controller.Meta; + } + else + { + Meta = null; + } + + // default setting + // m_settings.PoseFreeze = + // MeshUtility.Validators.HumanoidValidator.HasRotationOrScale(root) + // || m_meshes.Meshes.Any(x => x.ExportBlendShapeCount > 0 && !x.HasSkinning) + // ; + } + + Repaint(); + }; + m_state.ExportRoot = Selection.activeObject as GameObject; + } + + void OnDisable() + { + m_state.Dispose(); + + // Debug.Log("OnDisable"); + Selection.selectionChanged -= Repaint; + Undo.willFlushUndoRecord -= Repaint; + } + + public delegate Vector2 BeginVerticalScrollViewFunc(Vector2 scrollPosition, bool alwaysShowVertical, GUIStyle verticalScrollbar, GUIStyle background, params GUILayoutOption[] options); + static BeginVerticalScrollViewFunc s_func; + static BeginVerticalScrollViewFunc BeginVerticalScrollView + { + get + { + if (s_func == null) + { + var methods = typeof(EditorGUILayout).GetMethods(BindingFlags.Static | BindingFlags.NonPublic).Where(x => x.Name == "BeginVerticalScrollView").ToArray(); + var method = methods.First(x => x.GetParameters()[1].ParameterType == typeof(bool)); + s_func = (BeginVerticalScrollViewFunc)method.CreateDelegate(typeof(BeginVerticalScrollViewFunc)); + } + return s_func; + } + } + private Vector2 m_ScrollPosition; + + IEnumerable ValidatorFactory() + { + yield return MeshUtility.Validators.HierarchyValidator.Validate; + if (!m_state.ExportRoot) + { + yield break; + } + + // MeshUtility.Validators.HumanoidValidator.EnableFreeze = false; + // yield return MeshUtility.Validators.HumanoidValidator.Validate; + + // yield return VRMExporterValidator.Validate; + // yield return VRMSpringBoneValidator.Validate; + + // var firstPerson = m_state.ExportRoot.GetComponent(); + // if (firstPerson != null) + // { + // yield return firstPerson.Validate; + // } + + // var proxy = m_state.ExportRoot.GetComponent(); + // if (proxy != null) + // { + // yield return proxy.Validate; + // } + + var meta = Meta ? Meta : m_tmpMeta; + yield return meta.Validate; + } + + private void OnGUI() + { + // ArgumentException: Getting control 1's position in a group with only 1 controls when doing repaint Aborting + // Validation により GUI の表示項目が変わる場合があるので、 + // EventType.Layout と EventType.Repaint 間で内容が変わらないようしている。 + if (Event.current.type == EventType.Layout) + { + m_state.Validate(ValidatorFactory()); + } + + EditorGUIUtility.labelWidth = 150; + + // lang + MeshUtility.M17N.Getter.OnGuiSelectLang(); + + EditorGUILayout.LabelField("ExportRoot"); + { + m_state.ExportRoot = (GameObject)EditorGUILayout.ObjectField(m_state.ExportRoot, typeof(GameObject), true); + } + + // Render contents using Generic Inspector GUI + m_ScrollPosition = BeginVerticalScrollView(m_ScrollPosition, false, GUI.skin.verticalScrollbar, "OL Box"); + GUIUtility.GetControlID(645789, FocusType.Passive); + + bool modified = ScrollArea(); + + EditorGUILayout.EndScrollView(); + + // Create and Other Buttons + { + // errors + GUILayout.BeginVertical(); + // GUILayout.FlexibleSpace(); + + { + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + GUI.enabled = m_state.Validations.All(x => x.CanExport); + + if (GUILayout.Button("Export", GUILayout.MinWidth(100))) + { + OnExportClicked(m_state.ExportRoot); + Close(); + GUIUtility.ExitGUI(); + } + GUI.enabled = true; + + GUILayout.EndHorizontal(); + } + GUILayout.EndVertical(); + } + + GUILayout.Space(8); + + if (modified) + { + m_state.Invalidate(); + } + } + + bool ScrollArea() + { + // + // Validation + // + foreach (var v in m_state.Validations) + { + v.DrawGUI(); + if (v.ErrorLevel == MeshUtility.ErrorLevels.Critical) + { + // Export UI を表示しない + return false; + } + } + + if (m_tmpMeta == null) + { + // disabled + return false; + } + + // tabbar + _tab = MeshUtility.TabBar.OnGUI(_tab); + switch (_tab) + { + case Tabs.Meta: + if (m_metaEditor == null) + { + if (m_meta != null) + { + m_metaEditor = Editor.CreateEditor(Meta); + } + else + { + m_metaEditor = Editor.CreateEditor(m_tmpMeta); + } + } + m_metaEditor.OnInspectorGUI(); + break; + + case Tabs.ExportSettings: + // m_settingsInspector.OnInspectorGUI(); + break; + + case Tabs.Mesh: + // m_meshesInspector.OnInspectorGUI(); + break; + } + + return true; + } + + string m_logLabel; + + const string EXTENSION = ".vrm"; + private static string m_lastExportDir; + void OnExportClicked(GameObject root) + { + m_logLabel = ""; + + string directory; + if (string.IsNullOrEmpty(m_lastExportDir)) + directory = Directory.GetParent(Application.dataPath).ToString(); + else + directory = m_lastExportDir; + + // save dialog + var path = EditorUtility.SaveFilePanel( + "Save vrm", + directory, + root.name + EXTENSION, + EXTENSION.Substring(1)); + if (string.IsNullOrEmpty(path)) + { + return; + } + m_lastExportDir = Path.GetDirectoryName(path).Replace("\\", "/"); + + m_logLabel += $"export...\n"; + + try + { + var exporter = new UniVRM10.RuntimeVrmConverter(); + var model = exporter.ToModelFrom10(root, Meta ? Meta : m_tmpMeta); + + // if (MeshUtility.Validators.HumanoidValidator.HasRotationOrScale(root)) + // { + // // 正規化 + // m_logLabel += $"normalize...\n"; + // var modifier = new ModelModifier(model); + // modifier.SkinningBake(); + // } + + // 右手系に変換 + m_logLabel += $"convert to right handed coordinate...\n"; + model.ConvertCoordinate(VrmLib.Coordinates.Gltf, ignoreVrm: false); + + var exportedBytes = GetGlb(model); + m_logLabel += $"write to {path}...\n"; + File.WriteAllBytes(path, exportedBytes); + Debug.Log("exportedBytes: " + exportedBytes.Length); + + var assetPath = ToAssetPath(path); + if (!string.IsNullOrEmpty(assetPath)) + { + AssetDatabase.ImportAsset(assetPath); + } + } + catch (Exception ex) + { + m_logLabel += ex.ToString(); + // rethrow + throw; + } + } + + static byte[] GetGlb(VrmLib.Model model) + { + // export vrm-1.0 + var exporter = new UniVRM10.Vrm10Exporter(); + var option = new VrmLib.ExportArgs + { + // vrm = false + }; + var glbBytes10 = exporter.Export(model, option); + // ? + var glb10 = VrmLib.Glb.Parse(glbBytes10); + return glb10.ToBytes(); + } + + static string ToAssetPath(string path) + { + var assetPath = UnityPath.FromFullpath(path); + return assetPath.Value; + } + } +} diff --git a/Assets/VRM10/Editor/Vrm10ExportDialog.cs.meta b/Assets/VRM10/Editor/Vrm10ExportDialog.cs.meta new file mode 100644 index 000000000..ebfa9e1fd --- /dev/null +++ b/Assets/VRM10/Editor/Vrm10ExportDialog.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e9ade01036e113c46a73f05c06ce57ce +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/VrmPreference.cs b/Assets/VRM10/Editor/VrmPreference.cs new file mode 100644 index 000000000..ca26b190b --- /dev/null +++ b/Assets/VRM10/Editor/VrmPreference.cs @@ -0,0 +1,47 @@ +using UnityEngine; +using UnityEditor; +using System.Linq; + +namespace UniVRM10 +{ + public static class VrmInfo + { + const string KEY_STOP_VRMASSETPOSTPROCESSOR = "StopVrmAssetPostProcessor"; + const string ASSETPOSTPROCESSOR_STOP_SYMBOL = "VRM_STOP_ASSETPOSTPROCESSOR"; + + [PreferenceItem("UniVRM")] + private static void OnPreferenceGUI() + { + EditorGUI.BeginChangeCheck(); + + var target = EditorUserBuildSettings.selectedBuildTargetGroup; + var current = PlayerSettings.GetScriptingDefineSymbolsForGroup(target).Split(';'); + + var stop = current.Any(x => x == ASSETPOSTPROCESSOR_STOP_SYMBOL); + var newValue = GUILayout.Toggle(stop, KEY_STOP_VRMASSETPOSTPROCESSOR); + EditorGUILayout.HelpBox($"define C# symbol '{ASSETPOSTPROCESSOR_STOP_SYMBOL}'", MessageType.Info, true); + if (EditorGUI.EndChangeCheck()) + { + } + + if (stop != newValue) + { + stop = newValue; + if (stop) + { + // add symbol + PlayerSettings.SetScriptingDefineSymbolsForGroup(target, + string.Join(";", current.Concat(new[] { ASSETPOSTPROCESSOR_STOP_SYMBOL })) + ); + } + else + { + // remove symbol + PlayerSettings.SetScriptingDefineSymbolsForGroup(target, + string.Join(";", current.Where(x => x != ASSETPOSTPROCESSOR_STOP_SYMBOL)) + ); + } + } + } + } +} diff --git a/Assets/VRM10/Editor/VrmPreference.cs.meta b/Assets/VRM10/Editor/VrmPreference.cs.meta new file mode 100644 index 000000000..c58d27cfd --- /dev/null +++ b/Assets/VRM10/Editor/VrmPreference.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 470e88d5a064e7b4f829ab5fa8449d6e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime.meta b/Assets/VRM10/Runtime.meta new file mode 100644 index 000000000..5314201dc --- /dev/null +++ b/Assets/VRM10/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0809da138c9f1564b8e5151372b2161f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components.meta b/Assets/VRM10/Runtime/Components.meta new file mode 100644 index 000000000..30b970a99 --- /dev/null +++ b/Assets/VRM10/Runtime/Components.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7074fe77da26e9d4891ee5c940395c71 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/Constraint.meta b/Assets/VRM10/Runtime/Components/Constraint.meta new file mode 100644 index 000000000..05748a79e --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Constraint.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 61b940c05955d5342aef635236298ce5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/Constraint/ConstraintAxes.cs b/Assets/VRM10/Runtime/Components/Constraint/ConstraintAxes.cs new file mode 100644 index 000000000..8229db85b --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Constraint/ConstraintAxes.cs @@ -0,0 +1,36 @@ +using System; +using UnityEngine; + +namespace UniVRM10 +{ + /// + /// FreezeAxesで使う。bitマスク + /// + [Flags] + public enum AxesMask + { + X = 1, + Y = 2, + Z = 4, + } + + public static class AxesMaskExtensions + { + public static Vector3 Freeze(this AxesMask mask, Vector3 src) + { + if (mask.HasFlag(AxesMask.X)) + { + src.x = 0; + } + if (mask.HasFlag(AxesMask.Y)) + { + src.y = 0; + } + if (mask.HasFlag(AxesMask.Z)) + { + src.z = 0; + } + return src; + } + } +} diff --git a/Assets/VRM10/Runtime/Components/Constraint/ConstraintAxes.cs.meta b/Assets/VRM10/Runtime/Components/Constraint/ConstraintAxes.cs.meta new file mode 100644 index 000000000..c1c49bc0e --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Constraint/ConstraintAxes.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 913d6607f40ece14d827c2f95b76ea55 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/Constraint/ConstraintDestination.cs b/Assets/VRM10/Runtime/Components/Constraint/ConstraintDestination.cs new file mode 100644 index 000000000..4cca644dc --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Constraint/ConstraintDestination.cs @@ -0,0 +1,76 @@ +using System; +using UnityEngine; + +namespace UniVRM10 +{ + public enum DestinationCoordinates + { + World, + Local, + } + + class ConstraintDestination + { + readonly Transform m_transform; + readonly DestinationCoordinates m_coords; + + readonly TRS m_initial; + + public ConstraintDestination(Transform t, DestinationCoordinates coords) + { + m_transform = t; + m_coords = coords; + + switch (m_coords) + { + case DestinationCoordinates.World: + m_initial = TRS.GetWorld(t); + break; + + case DestinationCoordinates.Local: + m_initial = TRS.GetLocal(t); + break; + + default: + throw new NotImplementedException(); + } + } + + public void ApplyTranslation(Vector3 delta, float weight) + { + var value = m_initial.Translation + delta * weight; + switch (m_coords) + { + case DestinationCoordinates.World: + m_transform.position = value; + break; + + case DestinationCoordinates.Local: + m_transform.localPosition = value; + break; + + default: + throw new NotImplementedException(); + } + } + + public void ApplyRotation(Quaternion delta, float weight) + { + // 0~1 で clamp しない slerp + var value = Quaternion.LerpUnclamped(Quaternion.identity, delta, weight) * m_initial.Rotation; + switch (m_coords) + { + case DestinationCoordinates.World: + m_transform.rotation = value; + break; + + case DestinationCoordinates.Local: + m_transform.localRotation = value; + break; + + default: + throw new NotImplementedException(); + } + } + } +} diff --git a/Assets/VRM10/Runtime/Components/Constraint/ConstraintDestination.cs.meta b/Assets/VRM10/Runtime/Components/Constraint/ConstraintDestination.cs.meta new file mode 100644 index 000000000..f88469da2 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Constraint/ConstraintDestination.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9468d76832ea7224b97e0260f53a87dd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/Constraint/ConstraintSource.cs b/Assets/VRM10/Runtime/Components/Constraint/ConstraintSource.cs new file mode 100644 index 000000000..a74ca1db3 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Constraint/ConstraintSource.cs @@ -0,0 +1,95 @@ +using UnityEngine; +using System; + +namespace UniVRM10 +{ + public enum SourceCoordinates + { + /// + /// ワールド座標 + /// + World, + + /// + /// モデルルート(指定のTransform)ローカル座標 + /// + Model, + + /// + /// m_transform ローカル座標 + /// + Local, + } + + class ConstraintSource + { + readonly Transform m_modelRoot; + + readonly Transform m_transform; + + readonly SourceCoordinates m_coords; + + readonly TRS m_initial; + + public Vector3 TranslationDelta + { + get + { + switch (m_coords) + { + case SourceCoordinates.World: return m_transform.position - m_initial.Translation; + case SourceCoordinates.Local: return m_transform.localPosition - m_initial.Translation; + case SourceCoordinates.Model: return m_modelRoot.worldToLocalMatrix.MultiplyPoint(m_transform.position) - m_initial.Translation; + default: throw new NotImplementedException(); + } + } + } + + public Quaternion RotationDelta + { + get + { + switch (m_coords) + { + // 右からかけるか、左からかけるか、それが問題なのだ + case SourceCoordinates.World: return m_transform.rotation * Quaternion.Inverse(m_initial.Rotation); + case SourceCoordinates.Local: return m_transform.localRotation * Quaternion.Inverse(m_initial.Rotation); + case SourceCoordinates.Model: return m_transform.rotation * Quaternion.Inverse(m_modelRoot.rotation) * Quaternion.Inverse(m_initial.Rotation); + default: throw new NotImplementedException(); + } + } + } + + public ConstraintSource(Transform t, SourceCoordinates coords, Transform modelRoot = null) + { + m_transform = t; + m_coords = coords; + + switch (coords) + { + case SourceCoordinates.World: + m_initial = TRS.GetWorld(t); + break; + + case SourceCoordinates.Local: + m_initial = TRS.GetLocal(t); + break; + + case SourceCoordinates.Model: + { + var world = TRS.GetWorld(t); + m_modelRoot = modelRoot; + m_initial = new TRS + { + Translation = modelRoot.worldToLocalMatrix.MultiplyPoint(world.Translation), + Rotation = world.Rotation * Quaternion.Inverse(m_modelRoot.rotation), + }; + } + break; + + default: + throw new NotImplementedException(); + } + } + } +} diff --git a/Assets/VRM10/Runtime/Components/Constraint/ConstraintSource.cs.meta b/Assets/VRM10/Runtime/Components/Constraint/ConstraintSource.cs.meta new file mode 100644 index 000000000..c155762ed --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Constraint/ConstraintSource.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0730ba085e3395f4aa76c477cdc6548b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/Constraint/TRS.cs b/Assets/VRM10/Runtime/Components/Constraint/TRS.cs new file mode 100644 index 000000000..1881b77ac --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Constraint/TRS.cs @@ -0,0 +1,31 @@ +using UnityEngine; + +namespace UniVRM10 +{ + struct TRS + { + public Vector3 Translation; + public Quaternion Rotation; + public Vector3 Scale; + + public static TRS GetWorld(Transform t) + { + return new TRS + { + Translation = t.position, + Rotation = t.rotation, + Scale = t.lossyScale, + }; + } + + public static TRS GetLocal(Transform t) + { + return new TRS + { + Translation = t.localPosition, + Rotation = t.localRotation, + Scale = t.localScale, + }; + } + } +} diff --git a/Assets/VRM10/Runtime/Components/Constraint/TRS.cs.meta b/Assets/VRM10/Runtime/Components/Constraint/TRS.cs.meta new file mode 100644 index 000000000..8d5e46b4b --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Constraint/TRS.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b00df4c7268fefe42a16416935bbceb5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/Constraint/TransformExtensions.cs b/Assets/VRM10/Runtime/Components/Constraint/TransformExtensions.cs new file mode 100644 index 000000000..6b07bd795 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Constraint/TransformExtensions.cs @@ -0,0 +1,12 @@ +using UnityEngine; + +namespace UniVRM10 +{ + public static class TransformExtensions + { + public static Quaternion ParentRotation(this Transform transform) + { + return transform.parent == null ? Quaternion.identity : transform.parent.rotation; + } + } +} diff --git a/Assets/VRM10/Runtime/Components/Constraint/TransformExtensions.cs.meta b/Assets/VRM10/Runtime/Components/Constraint/TransformExtensions.cs.meta new file mode 100644 index 000000000..1d848428a --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Constraint/TransformExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ec9ca9ddc040c304793c322223f11b6a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/Constraint/VRMAimConstraint.cs b/Assets/VRM10/Runtime/Components/Constraint/VRMAimConstraint.cs new file mode 100644 index 000000000..685116b57 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Constraint/VRMAimConstraint.cs @@ -0,0 +1,173 @@ +using System; +using UnityEngine; + + +namespace UniVRM10 +{ + /// + /// WIP + /// + /// Slerp(thisRot * offsetRot(Aim x Up), sourceRot, weight) + /// + /// + [DisallowMultipleComponent] + public class VRMAimConstraint : VRMConstraint + { + [SerializeField] + Transform Source = default; + + // [SerializeField] + // [Range(0, 10.0f)] + // float Weight = 1.0f; + + /// + /// Forward + /// + [SerializeField] + Vector3 AimVector = Vector3.forward; + + [SerializeField] + Vector3 UpVector = Vector3.up; + + Vector3 RightVector; + + Quaternion m_selfInitial; + Matrix4x4 m_coords; + + void Start() + { + if (Source == null) + { + return; + } + + m_selfInitial = transform.rotation; + + // 正規直交座標を作る + // Y x Z => X + AimVector.Normalize(); + UpVector.Normalize(); + RightVector = Vector3.Cross(UpVector, AimVector).normalized; + // 直交するように再計算 + UpVector = Vector3.Cross(AimVector, RightVector).normalized; + m_coords = new Matrix4x4( + new Vector4(RightVector.x, RightVector.y, RightVector.z, 0), + new Vector4(UpVector.x, UpVector.y, UpVector.z, 0), + new Vector4(AimVector.x, AimVector.y, AimVector.z, 0), + new Vector4(0, 0, 0, 1) + ); + } + + static (float, int, float, int) CalcYawPitch(Matrix4x4 m, Vector3 target) + { + var zaxis = Vector3.Project(target, m.GetColumn(2)); + var yaxis = Vector3.Project(target, m.GetColumn(1)); + var xaxis = Vector3.Project(target, m.GetColumn(0)); + + var xDot = Vector3.Dot(xaxis, m.GetColumn(0)) > 0; + var yDot = Vector3.Dot(yaxis, m.GetColumn(1)) > 0; + var zDot = Vector3.Dot(zaxis, m.GetColumn(2)) > 0; + + // xz + var yaw = (float)System.Math.Atan2(xaxis.magnitude, zaxis.magnitude) * Mathf.Rad2Deg; + var yawQuadrant = -1; + if (xDot && zDot) + { + // 1st(0-90) + yawQuadrant = 0; + } + else if (xDot && !zDot) + { + // 2nd(90-180) + yawQuadrant = 1; + } + else if (!xDot && !zDot) + { + // 3rd + yawQuadrant = 2; + } + else if (!xDot && zDot) + { + // 4th + yawQuadrant = 3; + } + else + { + throw new NotImplementedException(); + } + + // xy + var pitch = (float)System.Math.Atan2(yaxis.magnitude, (xaxis + zaxis).magnitude) * Mathf.Rad2Deg; + var pitchQuadrant = -1; + if (yDot && zDot) + { + // 1st + pitchQuadrant = 0; + } + else if (yDot & !zDot) + { + // 2nd + pitchQuadrant = 1; + } + else if (!yDot & !zDot) + { + // 3rd + pitchQuadrant = 2; + } + else if (!yDot & zDot) + { + // 4th + pitchQuadrant = 3; + } + + return (yaw, yawQuadrant, pitch, pitchQuadrant); + } + + /// + /// TargetのUpdateよりも先か後かはその時による。 + /// 厳密に制御するのは無理。 + /// + public override void Process() + { + if (Source == null) + { + return; + } + + var localPosition = transform.worldToLocalMatrix.MultiplyPoint(Source.position); + // var (yaw, pitch) = CalcYawPitch(m_coords, localPosition); + } + + void OnDrawGizmos() + { + if (Source == null) + { + return; + } + + var localPosition = transform.worldToLocalMatrix.MultiplyPoint(Source.position); + var (yaw, yaw_, pitch, pitch_) = CalcYawPitch(m_coords, localPosition); + switch (yaw_) + { + case 0: break; + case 1: yaw = 180 - yaw; break; + case 2: yaw = 180 + yaw; break; + case 3: yaw = 360 - yaw; break; + } + switch (pitch_) + { + case 0: pitch = -pitch; break; + case 1: pitch = -pitch; break; + case 2: break; + case 3: break; + } + // Debug.Log($"{yaw}({yaw_}), {pitch}({pitch_})"); + // var rot = Quaternion.Euler(pitch, yaw, 0); + var rot = Quaternion.AngleAxis(yaw, Vector3.up) * Quaternion.AngleAxis(pitch, Vector3.right); + var p = rot * Vector3.forward; + + Gizmos.matrix = transform.localToWorldMatrix; + Gizmos.DrawLine(Vector3.zero, p * 5); + } + } +} diff --git a/Assets/VRM10/Runtime/Components/Constraint/VRMAimConstraint.cs.meta b/Assets/VRM10/Runtime/Components/Constraint/VRMAimConstraint.cs.meta new file mode 100644 index 000000000..9b67ac4a2 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Constraint/VRMAimConstraint.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a8f13bc2bb7b6734e92bba2b204e2e49 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/Constraint/VRMConstraint.cs b/Assets/VRM10/Runtime/Components/Constraint/VRMConstraint.cs new file mode 100644 index 000000000..45e1e848e --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Constraint/VRMConstraint.cs @@ -0,0 +1,12 @@ +using UnityEngine; + +namespace UniVRM10 +{ + public abstract class VRMConstraint : MonoBehaviour + { + public virtual void Process() + { + + } + } +} diff --git a/Assets/VRM10/Runtime/Components/Constraint/VRMConstraint.cs.meta b/Assets/VRM10/Runtime/Components/Constraint/VRMConstraint.cs.meta new file mode 100644 index 000000000..73d1134e8 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Constraint/VRMConstraint.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e017e9a63d31c4e4cbcc905a0caf0605 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/Constraint/VRMPositionConstraint.cs b/Assets/VRM10/Runtime/Components/Constraint/VRMPositionConstraint.cs new file mode 100644 index 000000000..ef29e7a25 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Constraint/VRMPositionConstraint.cs @@ -0,0 +1,65 @@ +using UnityEngine; + +namespace UniVRM10 +{ + /// + /// 対象の初期位置と現在位置の差分(delta)を、自身の初期位置に対してWeightを乗算して加算する。 + /// + [DisallowMultipleComponent] + public class VRMPositionConstraint : VRMConstraint + { + [SerializeField] + Transform Source = default; + + [SerializeField] + SourceCoordinates SourceCoordinate = default; + + [SerializeField] + DestinationCoordinates DestinationCoordinate = default; + + [SerializeField] + AxesMask FreezeAxes = default; + + [SerializeField] + [Range(0, 10.0f)] + float Weight = 1.0f; + + [SerializeField] + Transform ModelRoot = default; + + ConstraintSource m_src; + + ConstraintDestination m_dst; + + /// + /// Editorで設定値の変更を反映するために、クリアする + /// + void OnValidate() + { + // Debug.Log("Validate"); + m_src = null; + m_dst = null; + } + + public override void Process() + { + if (Source == null) + { + enabled = false; + return; + } + + if (m_src == null) + { + m_src = new ConstraintSource(Source, SourceCoordinate, ModelRoot); + } + if (m_dst == null) + { + m_dst = new ConstraintDestination(transform, DestinationCoordinate); + } + + var delta = FreezeAxes.Freeze(m_src.TranslationDelta); + m_dst.ApplyTranslation(delta, Weight); + } + } +} diff --git a/Assets/VRM10/Runtime/Components/Constraint/VRMPositionConstraint.cs.meta b/Assets/VRM10/Runtime/Components/Constraint/VRMPositionConstraint.cs.meta new file mode 100644 index 000000000..647bb21a1 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Constraint/VRMPositionConstraint.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0f104dcf447611a4ca99f4ac41e806e8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/Constraint/VRMRotationConstraint.cs b/Assets/VRM10/Runtime/Components/Constraint/VRMRotationConstraint.cs new file mode 100644 index 000000000..4ed4c1e07 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Constraint/VRMRotationConstraint.cs @@ -0,0 +1,75 @@ +using UnityEngine; + + +namespace UniVRM10 +{ + /// + /// 対象の初期回転と現在回転の差分(delta)を、自身の初期回転と自身の初期回転にdeltaを乗算したものに対してWeightでSlerpする。 + /// + [DisallowMultipleComponent] + public class VRMRotationConstraint : VRMConstraint + { + [SerializeField] + Transform Source = default; + + [SerializeField] + SourceCoordinates SourceCoordinate = default; + + [SerializeField] + DestinationCoordinates DestinationCoordinate = default; + + [SerializeField] + AxesMask FreezeAxes = default; + + [SerializeField] + [Range(0, 10.0f)] + float Weight = 1.0f; + + [SerializeField] + Transform ModelRoot = default; + + ConstraintSource m_src; + + ConstraintDestination m_dst; + + /// + /// Editorで設定値の変更を反映するために、クリアする + /// + void OnValidate() + { + // Debug.Log("Validate"); + m_src = null; + m_dst = null; + } + + /// + /// SourceのUpdateよりも先か後かはその時による。 + /// 厳密に制御するのは無理。 + /// + public override void Process() + { + if (Source == null) + { + enabled = false; + return; + } + + if (m_src == null) + { + m_src = new ConstraintSource(Source, SourceCoordinate, ModelRoot); + } + if (m_dst == null) + { + m_dst = new ConstraintDestination(transform, DestinationCoordinate); + } + + // 軸制限をしたオイラー角 + var delta = m_src.RotationDelta; + var fleezed = FreezeAxes.Freeze(delta.eulerAngles); + var rotation = Quaternion.Euler(fleezed); + // Debug.Log($"{delta} => {rotation}"); + // オイラー角を再度Quaternionへ。weight を加味してSlerpする + m_dst.ApplyRotation(rotation, Weight); + } + } +} diff --git a/Assets/VRM10/Runtime/Components/Constraint/VRMRotationConstraint.cs.meta b/Assets/VRM10/Runtime/Components/Constraint/VRMRotationConstraint.cs.meta new file mode 100644 index 000000000..0ccca3c7b --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Constraint/VRMRotationConstraint.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e7e95091802973e488fdcc4b3840cdc5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/Editor.meta b/Assets/VRM10/Runtime/Components/Editor.meta new file mode 100644 index 000000000..e164de119 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 457e5f19c7a1c124fa7752b246052bb9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/Expression.meta b/Assets/VRM10/Runtime/Components/Expression.meta new file mode 100644 index 000000000..a9013ae99 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Expression.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 86b623ba37ecc8344a984fad4ceff2d4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/Expression/ExpressionKey.cs b/Assets/VRM10/Runtime/Components/Expression/ExpressionKey.cs new file mode 100644 index 000000000..9237653e3 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Expression/ExpressionKey.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; + +namespace UniVRM10 +{ + [Serializable] + public struct ExpressionKey : IEquatable, IComparable + { + /// + /// Enum.ToString() のGC回避用キャッシュ + /// + private static readonly Dictionary m_presetNameDictionary = + new Dictionary(); + + + /// + /// ExpressionPreset と同名の名前を持つ独自に追加した Expression を区別するための prefix + /// + private static readonly string UnknownPresetPrefix = "Unknown_"; + + private string m_customName; + + public string Name + { + get { return m_customName.ToUpper(); } + } + + public VrmLib.ExpressionPreset Preset; + + string m_id; + + string ID + { + get + { + if (string.IsNullOrEmpty(m_id)) + { + // Unknown was deleted + if (Preset != VrmLib.ExpressionPreset.Custom) + { + if (m_presetNameDictionary.ContainsKey(Preset)) + { + m_id = m_presetNameDictionary[Preset]; + } + else + { + m_presetNameDictionary.Add(Preset, Preset.ToString()); + m_id = m_presetNameDictionary[Preset]; + } + } + else + { + m_id = UnknownPresetPrefix + m_customName; + } + } + + return m_id; + } + } + + public ExpressionKey(VrmLib.ExpressionPreset preset, string customName = null) + { + Preset = preset; + m_customName = customName; + + if (Preset != VrmLib.ExpressionPreset.Custom) + { + if (m_presetNameDictionary.ContainsKey((Preset))) + { + m_id = m_presetNameDictionary[Preset]; + } + else + { + m_presetNameDictionary.Add(Preset, Preset.ToString()); + m_id = m_presetNameDictionary[Preset]; + } + } + else + { + if (string.IsNullOrEmpty(m_customName)) + { + throw new ArgumentException("name is required for VrmLib.ExpressionPreset.Custom"); + } + m_id = UnknownPresetPrefix + m_customName; + } + } + + public static ExpressionKey CreateCustom(String key) + { + return new ExpressionKey(VrmLib.ExpressionPreset.Custom, key); + } + + public static ExpressionKey CreateFromPreset(VrmLib.ExpressionPreset preset) + { + return new ExpressionKey(preset); + } + + public static ExpressionKey CreateFromClip(VRM10Expression clip) + { + if (clip == null) + { + return default(ExpressionKey); + } + + return new ExpressionKey(clip.Preset, clip.ExpressionName); + } + + public override string ToString() + { + return ID.Replace(UnknownPresetPrefix, "").ToUpper(); + } + + public bool Equals(ExpressionKey other) + { + return ID == other.ID; + } + + public override bool Equals(object obj) + { + if (obj is ExpressionKey) + { + return Equals((ExpressionKey)obj); + } + else + { + return false; + } + } + + public override int GetHashCode() + { + return ID.GetHashCode(); + } + + public bool Match(VRM10Expression clip) + { + return this.Equals(CreateFromClip(clip)); + } + + public int CompareTo(ExpressionKey other) + { + if (Preset != other.Preset) + { + return Preset - other.Preset; + } + + return 0; + } + } +} diff --git a/Assets/VRM10/Runtime/Components/Expression/ExpressionKey.cs.meta b/Assets/VRM10/Runtime/Components/Expression/ExpressionKey.cs.meta new file mode 100644 index 000000000..5029b99fd --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Expression/ExpressionKey.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: adb1d8985f9fee54db1b8584da77ee08 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/Expression/ExpressionMerger.cs b/Assets/VRM10/Runtime/Components/Expression/ExpressionMerger.cs new file mode 100644 index 000000000..4aecd0e61 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Expression/ExpressionMerger.cs @@ -0,0 +1,102 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + + +namespace UniVRM10 +{ + + /// + /// ブレンドシェイプを蓄えてまとめて適用するクラス + /// + class ExpressionMerger + { + /// + /// Key から Expression を得る + /// + Dictionary m_clipMap; + + public IEnumerable ExpressionKeys => m_clipMap.Keys; + + /// + /// Expression のWeightを記録する + /// + Dictionary m_valueMap; + + MorphTargetBindingMerger m_morphTargetBindingMerger; + MaterialValueBindingMerger m_materialValueBindingMerger; + + + public ExpressionMerger(IEnumerable clips, Transform root) + { + m_clipMap = clips.ToDictionary(x => ExpressionKey.CreateFromClip(x), x => x); + + m_valueMap = new Dictionary(); + + m_morphTargetBindingMerger = new MorphTargetBindingMerger(m_clipMap, root); + m_materialValueBindingMerger = new MaterialValueBindingMerger(m_clipMap, root); + } + + /// + /// 蓄積した値を適用する + /// + public void Apply() + { + m_morphTargetBindingMerger.Apply(); + m_materialValueBindingMerger.Apply(); + } + + /// + /// まとめて反映する。1フレームに1回呼び出されることを想定 + /// + /// + public void SetValues(IEnumerable> values) + { + foreach (var kv in values) + { + AccumulateValue(kv.Key, kv.Value); + } + } + + /// + /// 即時に反映しない。後にApplyによって反映する + /// + /// + /// + public void AccumulateValue(ExpressionKey key, float value) + { + m_valueMap[key] = value; + + VRM10Expression clip; + if (!m_clipMap.TryGetValue(key, out clip)) + { + return; + } + + if (clip.IsBinary) + { + value = Mathf.Round(value); + } + + m_morphTargetBindingMerger.AccumulateValue(clip, value); + m_materialValueBindingMerger.AccumulateValue(clip, value); + } + + public VRM10Expression GetClip(ExpressionKey key) + { + if (m_clipMap.ContainsKey(key)) + { + return m_clipMap[key]; + } + else + { + return null; + } + } + + public void RestoreMaterialInitialValues() + { + m_materialValueBindingMerger.RestoreMaterialInitialValues(); + } + } +} diff --git a/Assets/VRM10/Runtime/Components/Expression/ExpressionMerger.cs.meta b/Assets/VRM10/Runtime/Components/Expression/ExpressionMerger.cs.meta new file mode 100644 index 000000000..42f0dcbeb --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Expression/ExpressionMerger.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b21afa8373708b04b9f656f894daf392 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/Expression/MaterialColorBinding.cs b/Assets/VRM10/Runtime/Components/Expression/MaterialColorBinding.cs new file mode 100644 index 000000000..896d78c26 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Expression/MaterialColorBinding.cs @@ -0,0 +1,35 @@ +using System; +using UnityEngine; + +namespace UniVRM10 +{ + [Serializable] + public struct MaterialColorBinding : IEquatable + { + public String MaterialName; + public VrmLib.MaterialBindType BindType; + public Vector4 TargetValue; + + public bool Equals(MaterialColorBinding other) + { + return string.Equals(MaterialName, other.MaterialName) && BindType.Equals(other.BindType) && TargetValue.Equals(other.TargetValue); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + return obj is MaterialColorBinding && Equals((MaterialColorBinding)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = (MaterialName != null ? MaterialName.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ BindType.GetHashCode(); + hashCode = (hashCode * 397) ^ TargetValue.GetHashCode(); + return hashCode; + } + } + } +} diff --git a/Assets/VRM10/Runtime/Components/Expression/MaterialColorBinding.cs.meta b/Assets/VRM10/Runtime/Components/Expression/MaterialColorBinding.cs.meta new file mode 100644 index 000000000..907161af3 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Expression/MaterialColorBinding.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 985d3a33d6e0e144e99deed88e048b59 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/Expression/MaterialUVBinding.cs b/Assets/VRM10/Runtime/Components/Expression/MaterialUVBinding.cs new file mode 100644 index 000000000..2fe88d0dd --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Expression/MaterialUVBinding.cs @@ -0,0 +1,46 @@ +using System; +using UnityEngine; + +namespace UniVRM10 +{ + /// + /// 対象のマテリアルのすべての テクスチャの Scale/Offset をまとめて変更する。 + /// 主に _MainTex_ST 値 + /// + [Serializable] + public struct MaterialUVBinding : IEquatable + { + /// + /// モデルのヒエラルキーから得たマテリアルの中から名前で検索する。 + /// 同名マテリアルはやめてください。 + /// + public String MaterialName; + + public Vector2 Scaling; // default: Vector2.one + public Vector2 Offset; // default: Vector2.zero + + public Vector4 ScalingOffset => new Vector4(Scaling.x, Scaling.y, Offset.x, Offset.y); + + public bool Equals(MaterialUVBinding other) + { + return string.Equals(MaterialName, other.MaterialName) && Scaling.Equals(other.Scaling) && Offset.Equals(other.Offset); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + return obj is MaterialUVBinding && Equals((MaterialUVBinding)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = (MaterialName != null ? MaterialName.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ Scaling.GetHashCode(); + hashCode = (hashCode * 397) ^ Offset.GetHashCode(); + return hashCode; + } + } + } +} diff --git a/Assets/VRM10/Runtime/Components/Expression/MaterialUVBinding.cs.meta b/Assets/VRM10/Runtime/Components/Expression/MaterialUVBinding.cs.meta new file mode 100644 index 000000000..012631962 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Expression/MaterialUVBinding.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5a80fb172c64ddc4e865ae0e0130f827 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/Expression/MaterialValueBindingMerger.cs b/Assets/VRM10/Runtime/Components/Expression/MaterialValueBindingMerger.cs new file mode 100644 index 000000000..f8a3e9822 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Expression/MaterialValueBindingMerger.cs @@ -0,0 +1,248 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UniVRM10 +{ + /// + /// Base + (A.Target - Base) * A.Weight + (B.Target - Base) * B.Weight ... + /// + class MaterialValueBindingMerger + { + #region MaterialMap + /// + /// MaterialValueBinding の対象になるマテリアルの情報を記録する + /// + Dictionary m_materialMap = new Dictionary(); + + void InitializeMaterialMap(Dictionary clipMap, Transform root) + { + Dictionary materialNameMap = new Dictionary(); + foreach (var renderer in root.GetComponentsInChildren()) + { + foreach (var material in renderer.sharedMaterials) + { + if (!materialNameMap.ContainsKey(material.name)) + { + materialNameMap.Add(material.name, material); + } + } + } + + foreach (var kv in clipMap) + { + foreach (var binding in kv.Value.MaterialColorBindings) + { + PreviewMaterialItem item; + if (!m_materialMap.TryGetValue(binding.MaterialName, out item)) + { + if (!materialNameMap.TryGetValue(binding.MaterialName, out Material material)) + { + // not found skip + continue; + } + item = new PreviewMaterialItem(material); + m_materialMap.Add(binding.MaterialName, item); + } + var propName = VrmLib.MaterialBindTypeExtensions.GetProperty(binding.BindType); + item.PropMap.Add(binding.BindType, new PropItem + { + Name = propName, + DefaultValues = item.Material.GetVector(propName), + }); + } + + foreach (var binding in kv.Value.MaterialUVBindings) + { + PreviewMaterialItem item; + if (!m_materialMap.TryGetValue(binding.MaterialName, out item)) + { + if (!materialNameMap.TryGetValue(binding.MaterialName, out Material material)) + { + // not found skip + continue; + } + item = new PreviewMaterialItem(material); + m_materialMap.Add(binding.MaterialName, item); + } + } + } + } + + /// + /// m_materialMap に記録した値に Material を復旧する + /// + public void RestoreMaterialInitialValues() + { + foreach (var kv in m_materialMap) + { + kv.Value.RestoreInitialValues(); + } + } + #endregion + + #region Accumulate + struct DictionaryKeyMaterialValueBindingComparer : IEqualityComparer + { + public bool Equals(MaterialColorBinding x, MaterialColorBinding y) + { + return x.TargetValue == y.TargetValue && x.MaterialName == y.MaterialName && x.BindType == y.BindType; + } + + public int GetHashCode(MaterialColorBinding obj) + { + return obj.GetHashCode(); + } + } + + static DictionaryKeyMaterialValueBindingComparer comparer = new DictionaryKeyMaterialValueBindingComparer(); + + /// + /// MaterialValueの適用値を蓄積する + /// + /// + /// + /// + Dictionary m_materialColorMap = new Dictionary(comparer); + + /// + /// UV Scale/Offset + /// + Dictionary m_materialUVMap = new Dictionary(); + + static readonly Vector4 DefaultUVScaleOffset = new Vector4(1, 1, 0, 0); + + public void AccumulateValue(VRM10Expression clip, float value) + { + // material color + foreach (var binding in clip.MaterialColorBindings) + { + float acc; + if (m_materialColorMap.TryGetValue(binding, out acc)) + { + m_materialColorMap[binding] = acc + value; + } + else + { + m_materialColorMap[binding] = value; + } + } + + // maetrial uv + foreach (var binding in clip.MaterialUVBindings) + { + Vector4 acc; + if (!m_materialUVMap.TryGetValue(binding.MaterialName, out acc)) + { + acc = DefaultUVScaleOffset; + } + + var delta = binding.ScalingOffset - DefaultUVScaleOffset; + m_materialUVMap[binding.MaterialName] = acc + delta * value; + } + } + + struct MaterialTarget : IEquatable + { + public string MaterialName; + public string ValueName; + + public bool Equals(MaterialTarget other) + { + return MaterialName == other.MaterialName + && ValueName == other.ValueName; + } + + public override bool Equals(object obj) + { + if (obj is MaterialTarget) + { + return Equals((MaterialTarget)obj); + } + else + { + return false; + } + } + + public override int GetHashCode() + { + if (MaterialName == null || ValueName == null) + { + return 0; + } + return MaterialName.GetHashCode() + ValueName.GetHashCode(); + } + + public static MaterialTarget Create(MaterialColorBinding binding) + { + return new MaterialTarget + { + MaterialName = binding.MaterialName, + ValueName = VrmLib.MaterialBindTypeExtensions.GetProperty(binding.BindType), + }; + } + } + + HashSet m_used = new HashSet(); + public void Apply() + { + { + m_used.Clear(); + foreach (var kv in m_materialColorMap) + { + var key = MaterialTarget.Create(kv.Key); + PreviewMaterialItem item; + if (m_materialMap.TryGetValue(key.MaterialName, out item)) + { + // 初期値(コンストラクタで記録) + var initial = item.PropMap[kv.Key.BindType].DefaultValues; + if (!m_used.Contains(key)) + { + // + // m_used に入っていない場合は、このフレームで初回の呼び出しになる。 + // (Apply はフレームに一回呼ばれる想定) + // 初回は、値を初期値に戻す。 + // + item.Material.SetColor(key.ValueName, initial); + m_used.Add(key); + } + + // 現在値 + var current = item.Material.GetVector(key.ValueName); + // 変化量 + var value = (kv.Key.TargetValue - initial) * kv.Value; + // 適用 + item.Material.SetColor(key.ValueName, current + value); + } + else + { + // エラー? + } + } + m_materialColorMap.Clear(); + } + + { + foreach (var kv in m_materialUVMap) + { + PreviewMaterialItem item; + if (m_materialMap.TryGetValue(kv.Key, out item)) + { + // + // Standard and MToon use _MainTex_ST as uv0 scale/offset + // + item.Material.SetVector("_MainTex_ST", kv.Value); + } + } + m_materialUVMap.Clear(); + } + } + #endregion + + public MaterialValueBindingMerger(Dictionary clipMap, Transform root) + { + InitializeMaterialMap(clipMap, root); + } + } +} diff --git a/Assets/VRM10/Runtime/Components/Expression/MaterialValueBindingMerger.cs.meta b/Assets/VRM10/Runtime/Components/Expression/MaterialValueBindingMerger.cs.meta new file mode 100644 index 000000000..1caec40de --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Expression/MaterialValueBindingMerger.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: b6aebf9f38de41c4a9cfd03e85756e9e +timeCreated: 1541229189 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/Expression/MorphTargetBinding.cs b/Assets/VRM10/Runtime/Components/Expression/MorphTargetBinding.cs new file mode 100644 index 000000000..4eec14a78 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Expression/MorphTargetBinding.cs @@ -0,0 +1,50 @@ +using System; + +namespace UniVRM10 +{ + [Serializable] + public struct MorphTargetBinding : IEquatable + { + /// + /// SkinnedMeshRenderer を 指し示す。 + /// + /// [トレードオフ] + /// + /// * SkinnedMeshRenderer そのものの方がわかりやすい + /// * Prefab 生成時の順番問題 + /// * Prefabの中で、Prefab自体を参照するので String の方が扱いが楽(Prefab生成時にトラブルになりがち。Runtimeロードでは問題ない) + /// * モデル変更時の改変への強さ + /// + /// + public String RelativePath; + public int Index; + public float Weight; + + public override string ToString() + { + return string.Format("{0}[{1}]=>{2}", RelativePath, Index, Weight); + } + + public bool Equals(MorphTargetBinding other) + { + return string.Equals(RelativePath, other.RelativePath) && Index == other.Index && Weight.Equals(other.Weight); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + return obj is MorphTargetBinding && Equals((MorphTargetBinding)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = (RelativePath != null ? RelativePath.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ Index; + hashCode = (hashCode * 397) ^ Weight.GetHashCode(); + return hashCode; + } + } + } +} diff --git a/Assets/VRM10/Runtime/Components/Expression/MorphTargetBinding.cs.meta b/Assets/VRM10/Runtime/Components/Expression/MorphTargetBinding.cs.meta new file mode 100644 index 000000000..52e569872 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Expression/MorphTargetBinding.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d69431f58097b7d4dbafd4cd5695881d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger.cs b/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger.cs new file mode 100644 index 000000000..2126029ae --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UniVRM10 +{ + /// + /// A.Value * A.Weight + B.Value * B.Weight ... + /// + class MorphTargetBindingMerger + { + class DictionaryKeyMorphTargetBindingComparer : IEqualityComparer + { + public bool Equals(MorphTargetBinding x, MorphTargetBinding y) + { + return x.RelativePath == y.RelativePath + && x.Index == y.Index; + } + + public int GetHashCode(MorphTargetBinding obj) + { + return obj.RelativePath.GetHashCode() + obj.Index; + } + } + + private static DictionaryKeyMorphTargetBindingComparer comparer = new DictionaryKeyMorphTargetBindingComparer(); + + /// + /// MorphTargetBinding の適用値を蓄積する + /// + /// + /// + /// + Dictionary m_morphTargetValueMap = new Dictionary(comparer); + + /// + /// + /// + /// + Dictionary> m_morphTargetSetterMap = new Dictionary>(comparer); + + public MorphTargetBindingMerger(Dictionary clipMap, Transform root) + { + foreach (var kv in clipMap) + { + foreach (var binding in kv.Value.MorphTargetBindings) + { + if (!m_morphTargetSetterMap.ContainsKey(binding)) + { + var _target = root.Find(binding.RelativePath); + SkinnedMeshRenderer target = null; + if (_target != null) + { + target = _target.GetComponent(); + } + if (target != null) + { + if (binding.Index >= 0 && binding.Index < target.sharedMesh.blendShapeCount) + { + m_morphTargetSetterMap.Add(binding, x => + { + target.SetBlendShapeWeight(binding.Index, x); + }); + } + else + { + Debug.LogWarningFormat("Invalid morphTarget binding: {0}: {1}", target.name, binding); + } + + } + else + { + Debug.LogWarningFormat("SkinnedMeshRenderer: {0} not found", binding.RelativePath); + } + } + } + } + } + + public void AccumulateValue(VRM10Expression clip, float value) + { + foreach (var binding in clip.MorphTargetBindings) + { + float acc; + if (m_morphTargetValueMap.TryGetValue(binding, out acc)) + { + m_morphTargetValueMap[binding] = acc + binding.Weight * value; + } + else + { + m_morphTargetValueMap[binding] = binding.Weight * value; + } + } + } + + public void Apply() + { + foreach (var kv in m_morphTargetValueMap) + { + Action setter; + if (m_morphTargetSetterMap.TryGetValue(kv.Key, out setter)) + { + setter(kv.Value); + } + } + m_morphTargetValueMap.Clear(); + } + } +} diff --git a/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger.cs.meta b/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger.cs.meta new file mode 100644 index 000000000..6c9b5f681 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b955b3371ff699842a355d0e1348a859 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/Expression/PreviewMaterialItem.cs b/Assets/VRM10/Runtime/Components/Expression/PreviewMaterialItem.cs new file mode 100644 index 000000000..8f9aed7dc --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Expression/PreviewMaterialItem.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace UniVRM10 +{ +#if UNITY_EDITOR + [Serializable] + public struct PropItem + { + public string Name; + public ShaderUtil.ShaderPropertyType PropertyType; + public Vector4 DefaultValues; + } +#endif + + /// + /// Material 一つ分のプロパティを蓄えている + /// + /// * PreviewSceneManager で使う + /// * MaterialValueBindingMerger で使う + /// + /// + [Serializable] + public class PreviewMaterialItem + { + public readonly Material Material; + + public PreviewMaterialItem(Material material) + { + Material = material; + } + + public Dictionary PropMap = new Dictionary(); + + public string[] PropNames + { + get; + private set; + } + + public void RestoreInitialValues() + { + foreach (var prop in PropMap) + { + Material.SetColor(prop.Value.Name, prop.Value.DefaultValues); + } + } + +#if UNITY_EDITOR + public static PreviewMaterialItem CreateForPreview(Material material) + { + var item = new PreviewMaterialItem(material); + + var propNames = new List(); + for (int i = 0; i < ShaderUtil.GetPropertyCount(material.shader); ++i) + { + var propType = ShaderUtil.GetPropertyType(material.shader, i); + var name = ShaderUtil.GetPropertyName(material.shader, i); + + switch (propType) + { + case ShaderUtil.ShaderPropertyType.Color: + // 色 + { + var bindType = VrmLib.MaterialBindTypeExtensions.GetBindType(name); + item.PropMap.Add(bindType, new PropItem + { + Name = name, + PropertyType = propType, + DefaultValues = material.GetColor(name), + }); + propNames.Add(name); + } + break; + + case ShaderUtil.ShaderPropertyType.TexEnv: + // テクスチャ + // { + // name += "_ST"; + // item.PropMap.Add(name, new PropItem + // { + // PropertyType = propType, + // DefaultValues = material.GetVector(name), + // }); + // propNames.Add(name); + // } + // // 縦横分離用 + // { + // var st_name = name + "_S"; + // item.PropMap.Add(st_name, new PropItem + // { + // PropertyType = propType, + // DefaultValues = material.GetVector(name), + // }); + // propNames.Add(st_name); + // } + // { + // var st_name = name + "_T"; + // item.PropMap.Add(st_name, new PropItem + // { + // PropertyType = propType, + // DefaultValues = material.GetVector(name), + // }); + // propNames.Add(st_name); + // } + break; + } + } + item.PropNames = propNames.ToArray(); + return item; + } +#endif + } +} diff --git a/Assets/VRM10/Runtime/Components/Expression/PreviewMaterialItem.cs.meta b/Assets/VRM10/Runtime/Components/Expression/PreviewMaterialItem.cs.meta new file mode 100644 index 000000000..8bd7966ba --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Expression/PreviewMaterialItem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 90ee9936998e23342978b06befb3d9dd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/Expression/PreviewMeshItem.cs b/Assets/VRM10/Runtime/Components/Expression/PreviewMeshItem.cs new file mode 100644 index 000000000..5ff577908 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Expression/PreviewMeshItem.cs @@ -0,0 +1,143 @@ +using System.Linq; +using UnityEngine; +using System; +using System.Collections.Generic; + +namespace UniVRM10 +{ + [Serializable] + public class PreviewMeshItem + { + public string Path + { + get; + private set; + } + + public SkinnedMeshRenderer SkinnedMeshRenderer + { + get; + private set; + } + + public Mesh Mesh + { + get; + private set; + } + + public string[] BlendShapeNames + { + get; + private set; + } + + public int BlendShapeCount + { + get { return BlendShapeNames.Length; } + } + + public Material[] Materials + { + get; + private set; + } + + Transform m_transform; + public Vector3 Position + { + get { return m_transform.position; } + } + public Quaternion Rotation + { + get { return m_transform.rotation; } + } + + PreviewMeshItem(string path, Transform transform, Material[] materials) + { + Path = path; + m_transform = transform; + Materials = materials; + } + + public void Bake(IEnumerable values, float weight) + { + if (SkinnedMeshRenderer == null) return; + + // Update baked mesh + if (values != null) + { + // clear + for (int i = 0; i < BlendShapeCount; ++i) + { + SkinnedMeshRenderer.SetBlendShapeWeight(i, 0); + } + + foreach (var x in values) + { + if (x.RelativePath == Path) + { + if (x.Index >= 0 && x.Index < SkinnedMeshRenderer.sharedMesh.blendShapeCount) + { + SkinnedMeshRenderer.SetBlendShapeWeight(x.Index, x.Weight * weight); + } + else + { + Debug.LogWarningFormat("Out of range {0}: 0 <= {1} < {2}", + SkinnedMeshRenderer.name, + x.Index, + SkinnedMeshRenderer.sharedMesh.blendShapeCount); + } + } + } + } + SkinnedMeshRenderer.BakeMesh(Mesh); + } + + public static PreviewMeshItem Create(Transform t, Transform root, + Func getOrCreateMaterial) + { + //Debug.Log("create"); + + var meshFilter = t.GetComponent(); + var meshRenderer = t.GetComponent(); + var skinnedMeshRenderer = t.GetComponent(); + if (meshFilter != null && meshRenderer != null) + { + // copy + meshRenderer.sharedMaterials = meshRenderer.sharedMaterials.Select(x => getOrCreateMaterial(x)).ToArray(); + return new PreviewMeshItem(t.RelativePathFrom(root), t, meshRenderer.sharedMaterials) + { + Mesh = meshFilter.sharedMesh + }; + } + else if (skinnedMeshRenderer != null) + { + // copy + skinnedMeshRenderer.sharedMaterials = skinnedMeshRenderer.sharedMaterials.Select(x => getOrCreateMaterial(x)).ToArray(); + if (skinnedMeshRenderer.sharedMesh.blendShapeCount > 0) + { + // bake required + var sharedMesh = skinnedMeshRenderer.sharedMesh; + return new PreviewMeshItem(t.RelativePathFrom(root), t, skinnedMeshRenderer.sharedMaterials) + { + SkinnedMeshRenderer = skinnedMeshRenderer, + Mesh = new Mesh(), // for bake + BlendShapeNames = Enumerable.Range(0, sharedMesh.blendShapeCount).Select(x => sharedMesh.GetBlendShapeName(x)).ToArray() + }; + } + else + { + return new PreviewMeshItem(t.RelativePathFrom(root), t, skinnedMeshRenderer.sharedMaterials) + { + Mesh = skinnedMeshRenderer.sharedMesh, + }; + } + } + else + { + return null; + } + } + } +} diff --git a/Assets/VRM10/Runtime/Components/Expression/PreviewMeshItem.cs.meta b/Assets/VRM10/Runtime/Components/Expression/PreviewMeshItem.cs.meta new file mode 100644 index 000000000..fe62f21a5 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Expression/PreviewMeshItem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b634c6524bdf71744a1d448ccbec7eaa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/Expression/PreviewSceneManager.cs b/Assets/VRM10/Runtime/Components/Expression/PreviewSceneManager.cs new file mode 100644 index 000000000..66cd2b2a7 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Expression/PreviewSceneManager.cs @@ -0,0 +1,331 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using System; + + +namespace UniVRM10 +{ + /// + /// プレビュー向けのシーンを管理する + /// + public class PreviewSceneManager : MonoBehaviour + { + public GameObject Prefab; + +#if UNITY_EDITOR + public static PreviewSceneManager GetOrCreate(GameObject prefab) + { + if (prefab == null) + { + return null; + } + + PreviewSceneManager manager = null; + + // if we already instantiated a PreviewInstance previously but just lost the reference, then use that same instance instead of making a new one + var managers = GameObject.FindObjectsOfType(); + foreach (var x in managers) + { + if (x.Prefab == prefab) + { + Debug.LogFormat("find {0}", manager); + return manager; + } + Debug.LogFormat("destroy {0}", x); + GameObject.DestroyImmediate(x.gameObject); + } + + //Debug.Log("new prefab. instanciate"); + // no previous instance detected, so now let's make a fresh one + // very important: this loads the PreviewInstance prefab and temporarily instantiates it into PreviewInstance + var go = GameObject.Instantiate(prefab, + prefab.transform.position, + prefab.transform.rotation + ); + go.name = "__PREVIEW_SCENE_MANGER__"; + manager = go.AddComponent(); + manager.Initialize(prefab); + + // HideFlags are special editor-only settings that let you have *secret* GameObjects in a scene, or to tell Unity not to save that temporary GameObject as part of the scene + foreach (var x in go.transform.Traverse()) + { + x.gameObject.hideFlags = HideFlags.None + | HideFlags.DontSave + //| HideFlags.DontSaveInBuild +#if VRM_DEVELOP +#else + | HideFlags.HideAndDontSave +#endif + ; + } + + return manager; + } +#endif + + public void Clean() + { + foreach (var kv in m_materialMap) + { + UnityEngine.Object.DestroyImmediate(kv.Value.Material); + } + } + + private void Initialize(GameObject prefab) + { + //Debug.LogFormat("[PreviewSceneManager.Initialize] {0}", prefab); + Prefab = prefab; + + var materialNames = new List(); + + // preview シーン用に Material を複製する。 + // Expression のカスタムエディタのマテリアル変更は、 + // 複製したものに適用される。 + var map = new Dictionary(); + Func getOrCreateMaterial = src => + { + if (src == null) return null; + if (string.IsNullOrEmpty(src.name)) return null; // ! + + Material dst; + if (!map.TryGetValue(src, out dst)) + { + dst = new Material(src); + map.Add(src, dst); + + //Debug.LogFormat("add material {0}", src.name); + materialNames.Add(src.name); + m_materialMap.Add(src.name, PreviewMaterialItem.CreateForPreview(dst)); + } + return dst; + }; + + m_meshes = transform.Traverse() + .Select(x => PreviewMeshItem.Create(x, transform, getOrCreateMaterial)) + .Where(x => x != null) + .ToArray() + ; + MaterialNames = materialNames.ToArray(); + + m_blendShapeMeshes = m_meshes + .Where(x => x.SkinnedMeshRenderer != null + && x.SkinnedMeshRenderer.sharedMesh.blendShapeCount > 0) + .ToArray(); + + m_rendererPathList = m_meshes.Select(x => x.Path).ToArray(); + m_skinnedMeshRendererPathList = m_meshes + .Where(x => x.SkinnedMeshRenderer != null) + .Select(x => x.Path) + .ToArray(); + + var animator = GetComponent(); + if (animator != null) + { + var head = animator.GetBoneTransform(HumanBodyBones.Head); + if (head != null) + { + m_target = head; + } + } + } + + PreviewMeshItem[] m_meshes; + PreviewMeshItem[] m_blendShapeMeshes; + public IEnumerable EnumRenderItems + { + get + { + if (m_meshes != null) + { + foreach (var x in m_meshes) + { + yield return x; + } + } + } + } + + public string[] MaterialNames + { + get; + private set; + } + + Dictionary m_materialMap = new Dictionary(); + + string[] m_rendererPathList; + public string[] RendererPathList + { + get { return m_rendererPathList; } + } + + string[] m_skinnedMeshRendererPathList; + public string[] SkinnedMeshRendererPathList + { + get { return m_skinnedMeshRendererPathList; } + } + + public string[] GetBlendShapeNames(int blendShapeMeshIndex) + { + if (blendShapeMeshIndex >= 0 && blendShapeMeshIndex < m_blendShapeMeshes.Length) + { + var item = m_blendShapeMeshes[blendShapeMeshIndex]; + return item.BlendShapeNames; + } + + return null; + } + + public PreviewMaterialItem GetMaterialItem(string materialName) + { + PreviewMaterialItem item; + if (!m_materialMap.TryGetValue(materialName, out item)) + { + return null; + } + + return item; + } + + public Transform m_target; + public Vector3 TargetPosition + { + get + { + if (m_target == null) + { + return new Vector3(0, 1.4f, 0); + } + return m_target.position + new Vector3(0, 0.1f, 0); + } + } + +#if UNITY_EDITOR + Bounds m_bounds; + public void Bake(VRM10Expression bake, float weight) + { + if (bake == null) + { + return; + } + + // + // Bake Expression + // + m_bounds = default(Bounds); + if (m_meshes != null) + { + if (bake != null) + { + foreach (var x in m_meshes) + { + x.Bake(bake.MorphTargetBindings, weight); + m_bounds.Expand(x.Mesh.bounds.size); + } + } + } + + // + // Update Material + // + if (m_materialMap != null) + { + // clear + //Debug.LogFormat("clear material"); + foreach (var kv in m_materialMap) + { + foreach (var _kv in kv.Value.PropMap) + { + // var prop = VrmLib.MaterialBindTypeExtensions.GetProperty(_kv.Key); + kv.Value.Material.SetColor(_kv.Value.Name, _kv.Value.DefaultValues); + } + + // clear UV + kv.Value.Material.SetVector("_MainTex_ST", new Vector4(1, 1, 0, 0)); + } + + if (bake.MaterialColorBindings != null) + { + foreach (var x in bake.MaterialColorBindings) + { + PreviewMaterialItem item; + if (m_materialMap.TryGetValue(x.MaterialName, out item)) + { + //Debug.Log("set material"); + PropItem prop; + if (item.PropMap.TryGetValue(x.BindType, out prop)) + { + // var valueName = x.ValueName; + // if (valueName.EndsWith("_ST_S") + // || valueName.EndsWith("_ST_T")) + // { + // valueName = valueName.Substring(0, valueName.Length - 2); + // } + + var value = item.Material.GetVector(prop.Name); + //Debug.LogFormat("{0} => {1}", valueName, x.TargetValue); + value += ((x.TargetValue - prop.DefaultValues) * weight); + item.Material.SetColor(prop.Name, value); + } + } + } + } + + if (bake.MaterialUVBindings != null) + { + foreach (var x in bake.MaterialUVBindings) + { + PreviewMaterialItem item; + if (m_materialMap.TryGetValue(x.MaterialName, out item)) + { + // var valueName = x.ValueName; + // if (valueName.EndsWith("_ST_S") + // || valueName.EndsWith("_ST_T")) + // { + // valueName = valueName.Substring(0, valueName.Length - 2); + // } + + var value = item.Material.GetVector("_MainTex_ST"); + //Debug.LogFormat("{0} => {1}", valueName, x.TargetValue); + value += ((x.ScalingOffset - new Vector4(1, 1, 0, 0)) * weight); + item.Material.SetColor("_MainTex_ST", value); + } + } + } + } + } +#endif + + /// + /// カメラパラメーターを決める + /// + /// + public void SetupCamera(Camera camera, Vector3 target, float yaw, float pitch, Vector3 position) + { + camera.backgroundColor = Color.gray; + camera.clearFlags = CameraClearFlags.Color; + + // projection + //float magnitude = m_bounds.extents.magnitude * 0.5f; + //float distance = magnitude; + //var distance = target.magnitude; + + camera.fieldOfView = 27f; + camera.nearClipPlane = 0.3f; + camera.farClipPlane = -position.z /*+ magnitude*/ * 2.1f; + + var t = Matrix4x4.Translate(position); + var r = Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(pitch, yaw, 0), Vector3.one); + // 回転してから移動 + var m = r * t; + + camera.transform.position = target + m.ExtractPosition(); + camera.transform.rotation = m.ExtractRotation(); + //camera.transform.LookAt(target); + + //previewLayer のみ表示する + //camera.cullingMask = 1 << PreviewLayer; + } + } +} diff --git a/Assets/VRM10/Runtime/Components/Expression/PreviewSceneManager.cs.meta b/Assets/VRM10/Runtime/Components/Expression/PreviewSceneManager.cs.meta new file mode 100644 index 000000000..c662e7df7 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Expression/PreviewSceneManager.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 8753271443aa4cc4d93334c992ea6a27 +timeCreated: 1523096653 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/Expression/VRM10Expression.cs b/Assets/VRM10/Runtime/Components/Expression/VRM10Expression.cs new file mode 100644 index 000000000..cbdcb333d --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Expression/VRM10Expression.cs @@ -0,0 +1,116 @@ +using MeshUtility; +using UnityEngine; + + +namespace UniVRM10 +{ + [CreateAssetMenu(menuName = "VRM10/Expression")] + public class VRM10Expression : ScriptableObject + { +#if UNITY_EDITOR + /// + /// Preview 用のObject参照 + /// + [SerializeField] + GameObject m_prefab; + public GameObject Prefab + { + set { m_prefab = value; } + get + { + if (m_prefab == null) + { + var assetPath = UnityEditor.AssetDatabase.GetAssetPath(this); + if (!string.IsNullOrEmpty(assetPath)) + { + // if asset is subasset of prefab + m_prefab = UnityEditor.AssetDatabase.LoadAssetAtPath(assetPath); + if (m_prefab != null) return m_prefab; + + var parent = UnityPath.FromUnityPath(assetPath).Parent; + var prefabPath = parent.Parent.Child(parent.FileNameWithoutExtension + ".prefab"); + m_prefab = UnityEditor.AssetDatabase.LoadAssetAtPath(prefabPath.Value); + if (m_prefab != null) return m_prefab; + + var parentParent = UnityPath.FromUnityPath(assetPath).Parent.Parent; + var vrmPath = parent.Parent.Child(parent.FileNameWithoutExtension + ".vrm"); + m_prefab = UnityEditor.AssetDatabase.LoadAssetAtPath(vrmPath.Value); + if (m_prefab != null) return m_prefab; + } + } + return m_prefab; + } + } +#endif + + /// + /// ExpressionPreset が Unknown 場合の識別子 + /// + [SerializeField] + public string ExpressionName; + + /// + /// ExpressionPreset を識別する。 Unknown の場合は、 ExpressionName で識別する + /// + [SerializeField] + public VrmLib.ExpressionPreset Preset; + + /// + /// 対象メッシュの Expression を操作する + /// + [SerializeField] + public MorphTargetBinding[] MorphTargetBindings = new MorphTargetBinding[] { }; + + /// + /// 対象マテリアルの Color を操作する + /// + [SerializeField] + public MaterialColorBinding[] MaterialColorBindings = new MaterialColorBinding[] { }; + + /// + /// 対象マテリアルの UVScale+Offset を操作する + /// + [SerializeField] + public MaterialUVBinding[] MaterialUVBindings = new MaterialUVBinding[] { }; + + /// + /// UniVRM-0.45: trueの場合、この Expression は0と1の間の中間値を取らない。四捨五入する + /// + [SerializeField] + public bool IsBinary; + + /// + /// この Expression と Blink(Blink, BlinkLeft, BlinkRight) が同時に有効な場合、Blink の Weight を 0 にする + /// + [SerializeField] + public bool IgnoreBlink; + + /// + /// この Expression と LookAt(LookUp, LookDown, LookLeft, LookRight) が同時に有効な場合、LookAt の Weight を 0 にする + /// + [SerializeField] + public bool IgnoreLookAt; + + /// + /// この Expression と Mouth(Aa, Ih, Ou, Ee, Oh) が同時に有効な場合、Mouth の Weight を 0 にする + /// + [SerializeField] + public bool IgnoreMouth; + + void Reset() + { + OnValidate(); + } + + void OnValidate() + { + if (Preset == VrmLib.ExpressionPreset.Custom) + { + if (string.IsNullOrEmpty(ExpressionName)) + { + ExpressionName = "custom"; + } + } + } + } +} diff --git a/Assets/VRM10/Runtime/Components/Expression/VRM10Expression.cs.meta b/Assets/VRM10/Runtime/Components/Expression/VRM10Expression.cs.meta new file mode 100644 index 000000000..4c6762ab3 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Expression/VRM10Expression.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8c8b2024ae0d0944eb878d90212bf21b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/Expression/VRM10ExpressionAvatar.cs b/Assets/VRM10/Runtime/Components/Expression/VRM10ExpressionAvatar.cs new file mode 100644 index 000000000..4f1ec660e --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Expression/VRM10ExpressionAvatar.cs @@ -0,0 +1,128 @@ +using UnityEngine; +using System.Linq; +using System; +using System.Collections.Generic; +using System.IO; +using MeshUtility; +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace UniVRM10 +{ + [CreateAssetMenu(menuName = "VRM10/ExpressionAvatar")] + public class VRM10ExpressionAvatar : ScriptableObject + { + [SerializeField] + public List Clips = new List(); + + /// + /// NullのClipを削除して詰める + /// + public void RemoveNullClip() + { + if (Clips == null) + { + return; + } + for (int i = Clips.Count - 1; i >= 0; --i) + { + if (Clips[i] == null) + { + Clips.RemoveAt(i); + } + } + } + +#if UNITY_EDITOR + [ContextMenu("Restore")] + void Restore() + { + var assetPath = UnityPath.FromAsset(this); + if (assetPath.IsNull) + { + return; + } + + + foreach (var x in assetPath.Parent.ChildFiles) + { + var clip = UnityEditor.AssetDatabase.LoadAssetAtPath(x.Value); + if (clip == null) continue; + + if (!Clips.Contains(clip)) + { + Clips.Add(clip); + } + + Debug.LogFormat("{0}", clip.name); + } + Clips = Clips.OrderBy(x => ExpressionKey.CreateFromClip(x)).ToList(); + } + + static public VRM10Expression CreateExpression(string path) + { + //Debug.LogFormat("{0}", path); + var clip = ScriptableObject.CreateInstance(); + clip.ExpressionName = Path.GetFileNameWithoutExtension(path); + AssetDatabase.CreateAsset(clip, path); + AssetDatabase.ImportAsset(path); + return clip; + //Clips.Add(clip); + //EditorUtility.SetDirty(this); + //AssetDatabase.SaveAssets(); + } +#endif + + /// + /// Unknown以外で存在しないものを全て作る + /// + public void CreateDefaultPreset() + { + foreach (var preset in ((VrmLib.ExpressionPreset[])Enum.GetValues(typeof(VrmLib.ExpressionPreset))) + .Where(x => x != VrmLib.ExpressionPreset.Custom) + ) + { + CreateDefaultPreset(preset); + } + } + + void CreateDefaultPreset(VrmLib.ExpressionPreset preset) + { + var clip = GetClip(new ExpressionKey(preset)); + if (clip != null) return; + clip = ScriptableObject.CreateInstance(); + clip.name = preset.ToString(); + clip.ExpressionName = preset.ToString(); + clip.Preset = preset; + Clips.Add(clip); + } + + public void SetClip(ExpressionKey key, VRM10Expression clip) + { + int index = -1; + try + { + index = Clips.FindIndex(x => key.Match(x)); + } + catch (Exception) + { + + } + if (index == -1) + { + Clips.Add(clip); + } + else + { + Clips[index] = clip; + } + } + + public VRM10Expression GetClip(ExpressionKey key) + { + if (Clips == null) return null; + return Clips.FirstOrDefault(x => key.Match(x)); + } + } +} diff --git a/Assets/VRM10/Runtime/Components/Expression/VRM10ExpressionAvatar.cs.meta b/Assets/VRM10/Runtime/Components/Expression/VRM10ExpressionAvatar.cs.meta new file mode 100644 index 000000000..54bf8e2cf --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Expression/VRM10ExpressionAvatar.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d985854d00eb11241b09522432e2d057 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/FirstPerson.meta b/Assets/VRM10/Runtime/Components/FirstPerson.meta new file mode 100644 index 000000000..ce5e8bffe --- /dev/null +++ b/Assets/VRM10/Runtime/Components/FirstPerson.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0a31f8c37cbcc4141b963817a2f02aed +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/FirstPerson/RendererFirstPersonFlags.cs b/Assets/VRM10/Runtime/Components/FirstPerson/RendererFirstPersonFlags.cs new file mode 100644 index 000000000..7674e2a09 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/FirstPerson/RendererFirstPersonFlags.cs @@ -0,0 +1,31 @@ +using System; +using UnityEngine; + +namespace UniVRM10 +{ + [Serializable] + public struct RendererFirstPersonFlags + { + public Renderer Renderer; + public VrmLib.FirstPersonMeshType FirstPersonFlag; + public Mesh SharedMesh + { + get + { + var renderer = Renderer as SkinnedMeshRenderer; + if (renderer != null) + { + return renderer.sharedMesh; + } + + var filter = Renderer.GetComponent(); + if (filter != null) + { + return filter.sharedMesh; + } + + return null; + } + } + } +} diff --git a/Assets/VRM10/Runtime/Components/FirstPerson/RendererFirstPersonFlags.cs.meta b/Assets/VRM10/Runtime/Components/FirstPerson/RendererFirstPersonFlags.cs.meta new file mode 100644 index 000000000..2fe0976bf --- /dev/null +++ b/Assets/VRM10/Runtime/Components/FirstPerson/RendererFirstPersonFlags.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e57d4492156c0a14189a0aa26d3022fe +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/LookAt.meta b/Assets/VRM10/Runtime/Components/LookAt.meta new file mode 100644 index 000000000..92f89855a --- /dev/null +++ b/Assets/VRM10/Runtime/Components/LookAt.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fad5429c9ac21574db43656309c3b5e5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/LookAt/CurveMapper.cs b/Assets/VRM10/Runtime/Components/LookAt/CurveMapper.cs new file mode 100644 index 000000000..60fa404fb --- /dev/null +++ b/Assets/VRM10/Runtime/Components/LookAt/CurveMapper.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + + +namespace UniVRM10 +{ + [Serializable] + public class CurveMapper + { + private AnimationCurve _curve = AnimationCurve.Linear(0, 0, 1.0f, 1.0f); + + [Range(20.0f, 90.0f)] + public float CurveXRangeDegree; + + [Range(0, 90.0f)] + public float CurveYRangeDegree; + + public CurveMapper(float xRange, float yRange) + { + CurveXRangeDegree = xRange; + CurveYRangeDegree = yRange; + } + + public void OnValidate() + { + if (CurveXRangeDegree == 0) + { + CurveXRangeDegree = 90.0f; + } + } + + public void Apply(VrmLib.LookAtRangeMap map) + { + CurveXRangeDegree = map.InputMaxValue; + CurveYRangeDegree = map.OutputScaling; + } + + IEnumerable ToKeys(float[] values) + { + for (int i = 0; i < values.Length; i += 4) + { + yield return new Keyframe(values[i], values[i + 1], values[i + 2], values[i + 3]); + } + } + + public float Map(float src) + { + if (src < 0) + { + src = 0; + } + else if (src > CurveXRangeDegree) + { + src = CurveXRangeDegree; + } + return _curve.Evaluate(src / CurveXRangeDegree) * CurveYRangeDegree; + } + } +} diff --git a/Assets/VRM10/Runtime/Components/LookAt/CurveMapper.cs.meta b/Assets/VRM10/Runtime/Components/LookAt/CurveMapper.cs.meta new file mode 100644 index 000000000..6a8ef19bb --- /dev/null +++ b/Assets/VRM10/Runtime/Components/LookAt/CurveMapper.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 16e5128b491d62b4bbed7fed19658d22 +timeCreated: 1521805484 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/LookAt/Matrix4x4Extensions.cs b/Assets/VRM10/Runtime/Components/LookAt/Matrix4x4Extensions.cs new file mode 100644 index 000000000..0cd2da454 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/LookAt/Matrix4x4Extensions.cs @@ -0,0 +1,37 @@ +using System; +using UnityEngine; + + +namespace UniVRM10 +{ + public static class Matrix4x4Extensions + { + public static void CalcYawPitch(this Matrix4x4 m, Vector3 target, out float yaw, out float pitch) + { + var zaxis = Vector3.Project(target, m.GetColumn(2)); + var yaxis = Vector3.Project(target, m.GetColumn(1)); + var xaxis = Vector3.Project(target, m.GetColumn(0)); + + var yawPlusMinus = Vector3.Dot(xaxis, m.GetColumn(0)) > 0 ? 1.0f : -1.0f; + yaw = (float)Math.Atan2(xaxis.magnitude, zaxis.magnitude) * yawPlusMinus * Mathf.Rad2Deg; + + var pitchPlusMinus = Vector3.Dot(yaxis, m.GetColumn(1)) > 0 ? 1.0f : -1.0f; + pitch = (float)Math.Atan2(yaxis.magnitude, (xaxis + zaxis).magnitude) * pitchPlusMinus * Mathf.Rad2Deg; + } + + public static Quaternion YawPitchRotation(this Matrix4x4 m, float yaw, float pitch) + { + return Quaternion.AngleAxis(yaw, m.GetColumn(1)) * Quaternion.AngleAxis(-pitch, m.GetColumn(0)); + } + + public static Matrix4x4 RotationToWorldAxis(this Matrix4x4 m) + { + return UnityExtensions.Matrix4x4FromColumns( + m.MultiplyVector(Vector3.right), + m.MultiplyVector(Vector3.up), + m.MultiplyVector(Vector3.forward), + new Vector4(0, 0, 0, 1) + ); + } + } +} diff --git a/Assets/VRM10/Runtime/Components/LookAt/Matrix4x4Extensions.cs.meta b/Assets/VRM10/Runtime/Components/LookAt/Matrix4x4Extensions.cs.meta new file mode 100644 index 000000000..4c1ec1686 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/LookAt/Matrix4x4Extensions.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 467dbe8b852af2a479d542138339d1b0 +timeCreated: 1518347175 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/LookAt/OffsetOnTransform.cs b/Assets/VRM10/Runtime/Components/LookAt/OffsetOnTransform.cs new file mode 100644 index 000000000..48950e39c --- /dev/null +++ b/Assets/VRM10/Runtime/Components/LookAt/OffsetOnTransform.cs @@ -0,0 +1,62 @@ +using System; +using UnityEngine; + + +namespace UniVRM10 +{ + [Serializable] + public struct OffsetOnTransform + { + public Transform Transform; + public Matrix4x4 OffsetRotation; + + public Matrix4x4 WorldMatrix + { + get + { + if (Transform == null) return Matrix4x4.identity; + return Transform.localToWorldMatrix * OffsetRotation; + } + } + + public Vector3 WorldForward + { + get + { + var m = WorldMatrix; + return m.GetColumn(2); // zaxis + } + } + + Matrix4x4 m_initialLocalMatrix; + public void Setup() + { + if (Transform == null) return; + m_initialLocalMatrix = Transform.parent.worldToLocalMatrix * Transform.localToWorldMatrix; + } + + public Matrix4x4 InitialWorldMatrix + { + get + { + return Transform.parent.localToWorldMatrix * m_initialLocalMatrix; + } + } + + public static OffsetOnTransform Create(Transform transform) + { + var coordinate = new OffsetOnTransform + { + Transform = transform, + m_initialLocalMatrix = Matrix4x4.identity, + }; + + if (transform != null) + { + coordinate.OffsetRotation = transform.worldToLocalMatrix.RotationToWorldAxis(); + } + + return coordinate; + } + } +} diff --git a/Assets/VRM10/Runtime/Components/LookAt/OffsetOnTransform.cs.meta b/Assets/VRM10/Runtime/Components/LookAt/OffsetOnTransform.cs.meta new file mode 100644 index 000000000..2acf715bf --- /dev/null +++ b/Assets/VRM10/Runtime/Components/LookAt/OffsetOnTransform.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 53baaa47aacbd984a8361a5f9d33ea33 +timeCreated: 1518347841 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/Meta.meta b/Assets/VRM10/Runtime/Components/Meta.meta new file mode 100644 index 000000000..e7767d235 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Meta.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9134e2692fe37db46b99691e26390bd9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/Meta/VRM10MetaObject.cs b/Assets/VRM10/Runtime/Components/Meta/VRM10MetaObject.cs new file mode 100644 index 000000000..383487011 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Meta/VRM10MetaObject.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using MeshUtility; +using UnityEngine; + + +namespace UniVRM10 +{ + [CreateAssetMenu(menuName = "VRM10/MetaObject")] + public class VRM10MetaObject : ScriptableObject + { + [SerializeField] + public string ExporterVersion; + + #region Info + [SerializeField] + public string Name; + + [SerializeField] + public string Version; + + [SerializeField] + public string CopyrightInformation; + + [SerializeField] + public string[] Authors; + + [SerializeField] + public string ContactInformation; + + [SerializeField] + public string Reference; + + [SerializeField] + public Texture2D Thumbnail; + #endregion + + #region AvatarPermission + [SerializeField, Tooltip("A person who can perform with this avatar")] + public VrmLib.AvatarUsageType AllowedUser; + + [SerializeField, Tooltip("Violent acts using this avatar")] + public bool ViolentUsage; + + [SerializeField, Tooltip("Sexuality acts using this avatar")] + public bool SexualUsage; + + [SerializeField, Tooltip("For commercial use")] + public VrmLib.CommercialUsageType CommercialUsage; + + [SerializeField] + public bool GameUsage; + + [SerializeField] + public bool PoliticalOrReligiousUsage; + + [SerializeField, Tooltip("Other License Url")] + public string OtherPermissionUrl; + #endregion + + #region Distribution License + [SerializeField] + public VrmLib.CreditNotationType CreditNotation; + + [SerializeField] + public bool Redistribution; + + [SerializeField] + public VrmLib.ModificationLicenseType ModificationLicense; + + [SerializeField] + public string OtherLicenseUrl; + #endregion + + public IEnumerable Validate(GameObject _) + { + if (string.IsNullOrEmpty(Name)) + { + yield return Validation.Error("Require Name. "); + } + // if (string.IsNullOrEmpty(Version)) + // { + // yield return Validation.Error("Require Version. "); + // } + if (Authors == null || Authors.Length == 0 || Authors.All(x => string.IsNullOrEmpty(x))) + { + yield return Validation.Error("Require at leaset one Author."); + } + } + + public void CopyTo(VRM10MetaObject dst) + { + dst.ExporterVersion = ExporterVersion; + dst.Name = Name; + dst.Version = Version; + dst.CopyrightInformation = CopyrightInformation; + if (Authors != null) + { + dst.Authors = Authors.Select(x => x).ToArray(); + } + else + { + dst.Authors = new string[] { }; + } + dst.ContactInformation = ContactInformation; + dst.Reference = Reference; + dst.Thumbnail = Thumbnail; + dst.AllowedUser = AllowedUser; + dst.ViolentUsage = ViolentUsage; + dst.SexualUsage = SexualUsage; + dst.CommercialUsage = CommercialUsage; + dst.GameUsage = GameUsage; + dst.PoliticalOrReligiousUsage = PoliticalOrReligiousUsage; + dst.OtherPermissionUrl = OtherPermissionUrl; + dst.CreditNotation = CreditNotation; + dst.Redistribution = Redistribution; + dst.ModificationLicense = ModificationLicense; + dst.OtherLicenseUrl = OtherLicenseUrl; + } + } +} diff --git a/Assets/VRM10/Runtime/Components/Meta/VRM10MetaObject.cs.meta b/Assets/VRM10/Runtime/Components/Meta/VRM10MetaObject.cs.meta new file mode 100644 index 000000000..2e015ab33 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Meta/VRM10MetaObject.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 848b62e18138b724dbaec592fbf31a1c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/SpringBone.meta b/Assets/VRM10/Runtime/Components/SpringBone.meta new file mode 100644 index 000000000..30530a6d9 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/SpringBone.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 14e1fad136e82e64c8ac16f4c4502b72 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/SpringBone/SpringBoneCollider.cs b/Assets/VRM10/Runtime/Components/SpringBone/SpringBoneCollider.cs new file mode 100644 index 000000000..54b957d6a --- /dev/null +++ b/Assets/VRM10/Runtime/Components/SpringBone/SpringBoneCollider.cs @@ -0,0 +1,26 @@ +using System; +using UnityEngine; + +namespace UniVRM10 +{ + public enum SpringBoneColliderTypes + { + Sphere, + Capsule, + } + + [Serializable] + public class SpringBoneCollider + { + public SpringBoneColliderTypes ColliderType; + + /// bone local position + public Vector3 Offset; + + [Range(0, 1.0f)] + public float Radius; + + /// bone local position + public Vector3 Tail; + } +} diff --git a/Assets/VRM10/Runtime/Components/SpringBone/SpringBoneCollider.cs.meta b/Assets/VRM10/Runtime/Components/SpringBone/SpringBoneCollider.cs.meta new file mode 100644 index 000000000..5df2bad9d --- /dev/null +++ b/Assets/VRM10/Runtime/Components/SpringBone/SpringBoneCollider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b939a4d43f2f17341b3e28bc9c59f8dd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/SpringBone/SpringBoneLogic.cs b/Assets/VRM10/Runtime/Components/SpringBone/SpringBoneLogic.cs new file mode 100644 index 000000000..6eed0936c --- /dev/null +++ b/Assets/VRM10/Runtime/Components/SpringBone/SpringBoneLogic.cs @@ -0,0 +1,203 @@ + +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UniVRM10 +{ + /// + /// + /// original from + /// + /// http://rocketjump.skr.jp/unity3d/109/ + /// + /// + public class SpringBoneLogic + { + Transform m_transform; + public Transform Head + { + get { return m_transform; } + } + + public Vector3 Tail + { + get { return m_transform.localToWorldMatrix.MultiplyPoint(m_boneAxis * m_length); } + } + + float m_length; + Vector3 m_currentTail; + Vector3 m_prevTail; + Vector3 m_localDir; + Quaternion m_localRotation; + public Quaternion LocalRotation + { + get { return m_localRotation; } + } + public Vector3 m_boneAxis; + + public float Radius { get; set; } + + public SpringBoneLogic(Transform center, Transform transform, Vector3 localChildPosition) + { + m_transform = transform; + var worldChildPosition = m_transform.TransformPoint(localChildPosition); + m_currentTail = center != null + ? center.InverseTransformPoint(worldChildPosition) + : worldChildPosition + ; + m_prevTail = m_currentTail; + m_localRotation = transform.localRotation; + m_boneAxis = localChildPosition.normalized; + m_length = localChildPosition.magnitude; + } + + Quaternion ParentRotation + { + get + { + return m_transform.parent != null + ? m_transform.parent.rotation + : Quaternion.identity + ; + } + } + + public struct InternalCollider + { + public SpringBoneColliderTypes ColliderTypes; + public Vector3 WorldPosition; + public float Radius; + public Vector3 WorldTail; + } + + public void Update(Transform center, + float stiffnessForce, float dragForce, Vector3 external, + List colliders) + { + var currentTail = center != null + ? center.TransformPoint(m_currentTail) + : m_currentTail + ; + var prevTail = center != null + ? center.TransformPoint(m_prevTail) + : m_prevTail + ; + + // verlet積分で次の位置を計算 + var nextTail = currentTail + + (currentTail - prevTail) * (1.0f - dragForce) // 前フレームの移動を継続する(減衰もあるよ) + + ParentRotation * m_localRotation * m_boneAxis * stiffnessForce // 親の回転による子ボーンの移動目標 + + external // 外力による移動量 + ; + + // 長さをboneLengthに強制 + nextTail = m_transform.position + (nextTail - m_transform.position).normalized * m_length; + + // Collisionで移動 + nextTail = Collision(colliders, nextTail); + + m_prevTail = center != null + ? center.InverseTransformPoint(currentTail) + : currentTail + ; + m_currentTail = center != null + ? center.InverseTransformPoint(nextTail) + : nextTail + ; + + //回転を適用 + Head.rotation = ApplyRotation(nextTail); + } + + protected virtual Quaternion ApplyRotation(Vector3 nextTail) + { + var rotation = ParentRotation * m_localRotation; + return Quaternion.FromToRotation(rotation * m_boneAxis, + nextTail - m_transform.position) * rotation; + } + + bool TrySphereCollision(Vector3 worldPosition, float radius, ref Vector3 nextTail) + { + var r = Radius + radius; + if (Vector3.SqrMagnitude(nextTail - worldPosition) <= (r * r)) + { + // ヒット。Colliderの半径方向に押し出す + var normal = (nextTail - worldPosition).normalized; + var posFromCollider = worldPosition + normal * (Radius + radius); + // 長さをboneLengthに強制 + nextTail = m_transform.position + (posFromCollider - m_transform.position).normalized * m_length; + return true; + } + else + { + return false; + } + } + + bool TryCapsuleCollision(in InternalCollider collider, ref Vector3 nextTail) + { + var P = collider.WorldTail - collider.WorldPosition; + var Q = m_transform.position - collider.WorldPosition; + var dot = Vector3.Dot(P, Q); + if (dot <= 0) + { + // head側半球の球判定 + return TrySphereCollision(collider.WorldPosition, collider.Radius, ref nextTail); + } + + var t = dot / P.magnitude; + if (t >= 1.0f) + { + // tail側半球の球判定 + return TrySphereCollision(collider.WorldTail, collider.Radius, ref nextTail); + } + + // head-tail上の m_transform.position との最近点 + var p = collider.WorldPosition + P * t; + return TrySphereCollision(p, collider.Radius, ref nextTail); + } + + protected virtual Vector3 Collision(List colliders, Vector3 nextTail) + { + foreach (var collider in colliders) + { + // すべての衝突判定を順番に実行する + switch (collider.ColliderTypes) + { + case SpringBoneColliderTypes.Sphere: + TrySphereCollision(collider.WorldPosition, collider.Radius, ref nextTail); + break; + + case SpringBoneColliderTypes.Capsule: + TryCapsuleCollision(in collider, ref nextTail); + break; + + default: + throw new NotImplementedException(); + } + } + return nextTail; + } + + public void DrawGizmo(Transform center, float radius, Color color) + { + var currentTail = center != null + ? center.TransformPoint(m_currentTail) + : m_currentTail + ; + var prevTail = center != null + ? center.TransformPoint(m_prevTail) + : m_prevTail + ; + + Gizmos.color = Color.gray; + Gizmos.DrawLine(currentTail, prevTail); + Gizmos.DrawWireSphere(prevTail, radius); + + Gizmos.color = color; + Gizmos.DrawLine(currentTail, m_transform.position); + Gizmos.DrawWireSphere(currentTail, radius); + } + } +} diff --git a/Assets/VRM10/Runtime/Components/SpringBone/SpringBoneLogic.cs.meta b/Assets/VRM10/Runtime/Components/SpringBone/SpringBoneLogic.cs.meta new file mode 100644 index 000000000..794ab95b7 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/SpringBone/SpringBoneLogic.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: de6086bc4a9b4434b8cabc6e9e81eb4d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/SpringBone/SpringBoneProcessor.cs b/Assets/VRM10/Runtime/Components/SpringBone/SpringBoneProcessor.cs new file mode 100644 index 000000000..3d4add914 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/SpringBone/SpringBoneProcessor.cs @@ -0,0 +1,111 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace UniVRM10 +{ + public class SpringBoneProcessor + { + List m_logics = new List(); + Dictionary m_initialLocalRotationMap; + + + public void ResetSpringBone() + { + foreach (var verlet in m_logics) + { + verlet.Head.localRotation = Quaternion.identity; + } + } + + public void SetupRecursive(Transform parent, Transform center) + { + if (parent.childCount == 0) + { + // 末端に追加のスプリングを付加する + var delta = parent.position - parent.parent.position; + var childPosition = parent.position + delta.normalized * 0.07f; + m_logics.Add(new SpringBoneLogic(center, parent, parent.worldToLocalMatrix.MultiplyPoint(childPosition))); + } + else + { + // 最初の子ボーンを尻尾としてスプリングを付加する + var firstChild = parent.GetChild(0); + var localPosition = firstChild.localPosition; + var scale = firstChild.lossyScale; + m_logics.Add(new SpringBoneLogic(center, parent, + new Vector3( + localPosition.x * scale.x, + localPosition.y * scale.y, + localPosition.z * scale.z + ))) + ; + } + + foreach (Transform child in parent) + { + SetupRecursive(child, center); + } + } + + void Setup(List RootBones, Transform m_center, bool force = false) + { + if (force || m_initialLocalRotationMap == null) + { + m_initialLocalRotationMap = new Dictionary(); + } + else + { + // restore initial rotation + foreach (var kv in m_initialLocalRotationMap) + { + kv.Key.localRotation = kv.Value; + } + m_initialLocalRotationMap.Clear(); + } + m_logics.Clear(); + + foreach (var transform in RootBones) + { + if (transform != null) + { + foreach (var x in transform.Traverse()) + { + // backup initial rotation + m_initialLocalRotationMap[x] = x.localRotation; + } + + SetupRecursive(transform, m_center); + } + } + } + + public void Update(List RootBones, List m_colliderList, + float stiffness, float m_dragForce, Vector3 external, + float m_hitRadius, Transform m_center) + { + if (m_logics == null || m_logics.Count == 0) + { + Setup(RootBones, m_center); + } + + foreach (var verlet in m_logics) + { + verlet.Radius = m_hitRadius; + verlet.Update(m_center, + stiffness, + m_dragForce, + external, + m_colliderList + ); + } + } + + public void DrawGizmos(Transform m_center, float m_hitRadius, Color m_gizmoColor) + { + foreach (var verlet in m_logics) + { + verlet.DrawGizmo(m_center, m_hitRadius, m_gizmoColor); + } + } + } +} diff --git a/Assets/VRM10/Runtime/Components/SpringBone/SpringBoneProcessor.cs.meta b/Assets/VRM10/Runtime/Components/SpringBone/SpringBoneProcessor.cs.meta new file mode 100644 index 000000000..51b9ea6c2 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/SpringBone/SpringBoneProcessor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fb0714842bac26b44b774260bbef58c3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/SpringBone/VRMSpringBone.cs b/Assets/VRM10/Runtime/Components/SpringBone/VRMSpringBone.cs new file mode 100644 index 000000000..8cc409ed0 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/SpringBone/VRMSpringBone.cs @@ -0,0 +1,118 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace UniVRM10 +{ + /// + /// The base algorithm is http://rocketjump.skr.jp/unity3d/109/ of @ricopin416 + /// DefaultExecutionOrder(11000) means calculate springbone after FinalIK( VRIK ) + /// + [AddComponentMenu("VRM/VRMSpringBone")] +#if UNITY_5_5_OR_NEWER + [DefaultExecutionOrder(11000)] +#endif + public class VRMSpringBone : MonoBehaviour + { + [SerializeField] + public string m_comment; + + [SerializeField, Header("Gizmo")] + bool m_drawGizmo = default; + + [SerializeField] + Color m_gizmoColor = Color.yellow; + + [SerializeField, Range(0, 4), Header("Settings")] + public float m_stiffnessForce = 1.0f; + + [SerializeField, Range(0, 2)] + public float m_gravityPower = 0; + + [SerializeField] + public Vector3 m_gravityDir = new Vector3(0, -1.0f, 0); + + [SerializeField, Range(0, 1)] + public float m_dragForce = 0.4f; + + [SerializeField] + public Transform m_center; + + [SerializeField] + public List RootBones = new List(); + + [SerializeField, Range(0, 0.5f), Header("Collision")] + public float m_hitRadius = 0.02f; + + [SerializeField] + public VRMSpringBoneColliderGroup[] ColliderGroups; + + SpringBoneProcessor m_processor = new SpringBoneProcessor(); + + [ContextMenu("Reset bones")] + public void ResetSpringBone() + { + m_processor.ResetSpringBone(); + } + + List m_colliderList = new List(); + public void Process() + { + if (RootBones == null) + { + return; + } + + // gather colliders + m_colliderList.Clear(); + if (ColliderGroups != null) + { + foreach (var group in ColliderGroups) + { + if (group != null) + { + foreach (var collider in group.Colliders) + { + switch (collider.ColliderType) + { + case SpringBoneColliderTypes.Sphere: + m_colliderList.Add(new SpringBoneLogic.InternalCollider + { + ColliderTypes = SpringBoneColliderTypes.Sphere, + WorldPosition = group.transform.TransformPoint(collider.Offset), + Radius = collider.Radius, + + }); + break; + + case SpringBoneColliderTypes.Capsule: + m_colliderList.Add(new SpringBoneLogic.InternalCollider + { + ColliderTypes = SpringBoneColliderTypes.Capsule, + WorldPosition = group.transform.TransformPoint(collider.Offset), + Radius = collider.Radius, + WorldTail = group.transform.TransformPoint(collider.Tail) + }); + break; + } + } + } + } + } + + var stiffness = m_stiffnessForce * Time.deltaTime; + var external = m_gravityDir * (m_gravityPower * Time.deltaTime); + + m_processor.Update(RootBones, m_colliderList, + stiffness, m_dragForce, external, + m_hitRadius, m_center); + } + + private void OnDrawGizmos() + { + if (m_drawGizmo) + { + m_processor.DrawGizmos(m_center, m_hitRadius, m_gizmoColor); + } + } + } +} diff --git a/Assets/VRM10/Runtime/Components/SpringBone/VRMSpringBone.cs.meta b/Assets/VRM10/Runtime/Components/SpringBone/VRMSpringBone.cs.meta new file mode 100644 index 000000000..11d32a3a4 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/SpringBone/VRMSpringBone.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 9972432db4ac7af489ee0b864c5baaf9 +timeCreated: 1517224588 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/SpringBone/VRMSpringBoneColliderGroup.cs b/Assets/VRM10/Runtime/Components/SpringBone/VRMSpringBoneColliderGroup.cs new file mode 100644 index 000000000..dc325d55f --- /dev/null +++ b/Assets/VRM10/Runtime/Components/SpringBone/VRMSpringBoneColliderGroup.cs @@ -0,0 +1,100 @@ +using System; +using UnityEngine; + + +namespace UniVRM10 +{ + [AddComponentMenu("VRM/VRMSpringBoneColliderGroup")] +#if UNITY_5_5_OR_NEWER + [DefaultExecutionOrder(11001)] +#endif + public class VRMSpringBoneColliderGroup : MonoBehaviour + { + [SerializeField] + public SpringBoneCollider[] Colliders = new SpringBoneCollider[]{ + new SpringBoneCollider + { + ColliderType = SpringBoneColliderTypes.Capsule, + Radius=0.1f + } + }; + + [SerializeField] + Color m_gizmoColor = Color.magenta; + + public static void DrawWireCapsule(Vector3 headPos, Vector3 tailPos, float radius) + { + var headToTail = tailPos - headPos; + if (headToTail.sqrMagnitude <= float.Epsilon) + { + Gizmos.DrawWireSphere(headPos, radius); + return; + } + + var forward = headToTail.normalized * radius; + + var xLen = Mathf.Abs(forward.x); + var yLen = Mathf.Abs(forward.y); + var zLen = Mathf.Abs(forward.z); + var rightWorldAxis = (yLen > xLen && yLen > zLen) ? Vector3.right : Vector3.up; + + var up = Vector3.Cross(forward, rightWorldAxis).normalized * radius; + var right = Vector3.Cross(up, forward).normalized * radius; + + const int division = 24; + DrawWireCircle(headPos, up, right, division, division); + DrawWireCircle(headPos, up, -forward, division, division / 2); + DrawWireCircle(headPos, right, -forward, division, division / 2); + + DrawWireCircle(tailPos, up, right, division, division); + DrawWireCircle(tailPos, up, forward, division, division / 2); + DrawWireCircle(tailPos, right, forward, division, division / 2); + + Gizmos.DrawLine(headPos + right, tailPos + right); + Gizmos.DrawLine(headPos - right, tailPos - right); + Gizmos.DrawLine(headPos + up, tailPos + up); + Gizmos.DrawLine(headPos - up, tailPos - up); + } + + private static void DrawWireCircle(Vector3 centerPos, Vector3 xAxis, Vector3 yAxis, int division, int count) + { + for (var idx = 0; idx < division && idx < count; ++idx) + { + var s = ((idx + 0) % division) / (float)division * Mathf.PI * 2f; + var t = ((idx + 1) % division) / (float)division * Mathf.PI * 2f; + + Gizmos.DrawLine( + centerPos + xAxis * Mathf.Cos(s) + yAxis * Mathf.Sin(s), + centerPos + xAxis * Mathf.Cos(t) + yAxis * Mathf.Sin(t) + ); + } + } + + private void OnDrawGizmosSelected() + { + Gizmos.color = m_gizmoColor; + Matrix4x4 mat = transform.localToWorldMatrix; + Gizmos.matrix = mat * Matrix4x4.Scale(new Vector3( + 1.0f / transform.lossyScale.x, + 1.0f / transform.lossyScale.y, + 1.0f / transform.lossyScale.z + )); + foreach (var y in Colliders) + { + switch (y.ColliderType) + { + case SpringBoneColliderTypes.Sphere: + Gizmos.DrawWireSphere(y.Offset, y.Radius); + break; + + case SpringBoneColliderTypes.Capsule: + // Gizmos.DrawWireSphere(y.Offset, y.Radius); + // Gizmos.DrawWireSphere(y.Tail, y.Radius); + // Gizmos.DrawLine(y.Offset, y.Tail); + DrawWireCapsule(y.Offset, y.Tail, y.Radius); + break; + } + } + } + } +} diff --git a/Assets/VRM10/Runtime/Components/SpringBone/VRMSpringBoneColliderGroup.cs.meta b/Assets/VRM10/Runtime/Components/SpringBone/VRMSpringBoneColliderGroup.cs.meta new file mode 100644 index 000000000..ff68b26e7 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/SpringBone/VRMSpringBoneColliderGroup.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 079b0ee290c8a8d40b07314c753397a1 +timeCreated: 1517984922 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/SpringBone/VRMSpringUtility.cs b/Assets/VRM10/Runtime/Components/SpringBone/VRMSpringUtility.cs new file mode 100644 index 000000000..429063bff --- /dev/null +++ b/Assets/VRM10/Runtime/Components/SpringBone/VRMSpringUtility.cs @@ -0,0 +1,221 @@ +namespace UniVRM10 +{ + + public static class VRMSpringUtility + { +#if false + +#if UNITY_EDITOR +#region save + [MenuItem(VRMVersion.MENU + "/SaveSpringBoneToJSON", validate = true)] + static bool SaveSpringBoneToJSONIsEnable() + { + var root = Selection.activeObject as GameObject; + if (root == null) + { + return false; + } + + var animator = root.GetComponent(); + if (animator == null) + { + return false; + } + + return true; + } + + [MenuItem(VRMVersion.MENU + "/SaveSpringBoneToJSON")] + static void SaveSpringBoneToJSON() + { + var path = EditorUtility.SaveFilePanel( + "Save spring to json", + null, + "VRMSpring.json", + "json"); + if (string.IsNullOrEmpty(path)) + { + return; + } + + var go = Selection.activeObject as GameObject; + var root = go.transform; + var nodes = root.Traverse().Skip(1).ToList(); + var spring = new glTF_VRM_SecondaryAnimation(); + ExportSecondary(root, nodes, + spring.colliderGroups.Add, + spring.boneGroups.Add + ); + + File.WriteAllText(path, spring.ToJson()); + } + +#endregion + +#region load + [MenuItem(VRMVersion.MENU + "/LoadSpringBoneFromJSON", true)] + static bool LoadSpringBoneFromJSONIsEnable() + { + var root = Selection.activeObject as GameObject; + if (root == null) + { + return false; + } + + var animator = root.GetComponent(); + if (animator == null) + { + return false; + } + + return true; + } + + [MenuItem(VRMVersion.MENU + "/LoadSpringBoneFromJSON")] + static void LoadSpringBoneFromJSON() + { + var path = EditorUtility.OpenFilePanel( + "Load spring from json", + null, + "json"); + if (string.IsNullOrEmpty(path)) + { + return; + } + + var json = File.ReadAllText(path, Encoding.UTF8); + var spring = JsonUtility.FromJson(json); + + var go = Selection.activeObject as GameObject; + var root = go.transform; + var nodes = root.Traverse().Skip(1).ToList(); + + LoadSecondary(root, nodes, spring); + } +#endregion +#endif + + public static void ExportSecondary(Transform root, List nodes, + Action addSecondaryColliderGroup, + Action addSecondaryGroup) + { + var colliders = new List(); + foreach (var vrmColliderGroup in root.Traverse() + .Select(x => x.GetComponent()) + .Where(x => x != null)) + { + colliders.Add(vrmColliderGroup); + + var colliderGroup = new glTF_VRM_SecondaryAnimationColliderGroup + { + node = nodes.IndexOf(vrmColliderGroup.transform) + }; + + colliderGroup.colliders = vrmColliderGroup.Colliders.Select(x => + { + return new glTF_VRM_SecondaryAnimationCollider + { + offset = x.Offset, + radius = x.Radius, + }; + + }).ToList(); + + addSecondaryColliderGroup(colliderGroup); + } + + foreach (var spring in root.Traverse() + .SelectMany(x => x.GetComponents()) + .Where(x => x != null)) + { + addSecondaryGroup(new glTF_VRM_SecondaryAnimationGroup + { + comment = spring.m_comment, + center = nodes.IndexOf(spring.m_center), + dragForce = spring.m_dragForce, + gravityDir = spring.m_gravityDir, + gravityPower = spring.m_gravityPower, + stiffiness = spring.m_stiffnessForce, + hitRadius = spring.m_hitRadius, + colliderGroups = spring.ColliderGroups + .Select(x => colliders.IndexOf(x)) + .Where(x => x != -1) + .ToArray(), + bones = spring.RootBones.Select(x => nodes.IndexOf(x)).ToArray(), + }); + } + } + + public static void LoadSecondary(Transform root, List nodes, + glTF_VRM_SecondaryAnimation secondaryAnimation) + { + var secondary = root.Find("secondary"); + if (secondary == null) + { + secondary = new GameObject("secondary").transform; + secondary.SetParent(root, false); + } + + // clear components + var remove = root.Traverse() + .SelectMany(x => x.GetComponents()) + .Where(x => x is VRMSpringBone || x is VRMSpringBoneColliderGroup) + .ToArray(); + foreach (var x in remove) + { + if (Application.isPlaying) + { + GameObject.Destroy(x); + } + else + { + GameObject.DestroyImmediate(x); + } + } + + //var secondaryAnimation = context.VRM.extensions.VRM.secondaryAnimation; + var colliders = new List(); + foreach (var colliderGroup in secondaryAnimation.colliderGroups) + { + var vrmGroup = nodes[colliderGroup.node].gameObject.AddComponent(); + vrmGroup.Colliders = colliderGroup.colliders.Select(x => + { + return new VRMSpringBoneColliderGroup.SphereCollider + { + Offset = x.offset, + Radius = x.radius + }; + }).ToArray(); + colliders.Add(vrmGroup); + } + + if (secondaryAnimation.boneGroups.Count > 0) + { + foreach (var boneGroup in secondaryAnimation.boneGroups) + { + var vrmBoneGroup = secondary.gameObject.AddComponent(); + if (boneGroup.center != -1) + { + vrmBoneGroup.m_center = nodes[boneGroup.center]; + } + vrmBoneGroup.m_comment = boneGroup.comment; + vrmBoneGroup.m_dragForce = boneGroup.dragForce; + vrmBoneGroup.m_gravityDir = boneGroup.gravityDir; + vrmBoneGroup.m_gravityPower = boneGroup.gravityPower; + vrmBoneGroup.m_hitRadius = boneGroup.hitRadius; + vrmBoneGroup.m_stiffnessForce = boneGroup.stiffiness; + if (boneGroup.colliderGroups != null && boneGroup.colliderGroups.Any()) + { + vrmBoneGroup.ColliderGroups = boneGroup.colliderGroups.Select(x => colliders[x]).ToArray(); + } + vrmBoneGroup.RootBones = boneGroup.bones.Select(x => nodes[x]).ToList(); + } + } + else + { + secondary.gameObject.AddComponent(); + } + } +#endif + } +} diff --git a/Assets/VRM10/Runtime/Components/SpringBone/VRMSpringUtility.cs.meta b/Assets/VRM10/Runtime/Components/SpringBone/VRMSpringUtility.cs.meta new file mode 100644 index 000000000..5a4899621 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/SpringBone/VRMSpringUtility.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 59fca440205c8134a898c5164771468c +timeCreated: 1528358605 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/VRM10Controller.cs b/Assets/VRM10/Runtime/Components/VRM10Controller.cs new file mode 100644 index 000000000..12e8daa1f --- /dev/null +++ b/Assets/VRM10/Runtime/Components/VRM10Controller.cs @@ -0,0 +1,177 @@ +using System; +using UnityEngine; + + +namespace UniVRM10 +{ + /// + /// VRM全体を制御するコンポーネント。 + /// + /// 各フレームのHumanoidへのモーション適用後に任意のタイミングで + /// Applyを呼び出してください。 + /// + /// + [AddComponentMenu("VRM10/VRMController")] + [DisallowMultipleComponent] + public class VRM10Controller : MonoBehaviour + { + public enum UpdateTypes + { + None, + Update, + LateUpdate, + } + + [Serializable] + public class VRM10ControllerImpl + { + [SerializeField, Header("UpdateSetting")] + public UpdateTypes UpdateType = UpdateTypes.LateUpdate; + } + + [SerializeField] + public VRM10ControllerImpl Controller = new VRM10ControllerImpl(); + + [SerializeField] + public VRM10MetaObject Meta; + + [SerializeField] + public VRM10ControllerExpression Expression = new VRM10ControllerExpression(); + + [SerializeField] + public VRM10ControllerLookAt LookAt = new VRM10ControllerLookAt(); + + [SerializeField] + public VRM10ControllerFirstPerson FirstPerson = new VRM10ControllerFirstPerson(); + + [SerializeField] + public ModelAsset ModelAsset; + + void OnDestroy() + { + Expression.Dispose(); + + if (ModelAsset != null) + { +#if UNITY_EDITOR + ModelAsset.DisposeEditor(); +#else + ModelAsset.Dispose(); +#endif + } + } + + VRMConstraint[] m_constraints; + VRMSpringBone[] m_springs; + + Transform m_head; + public Transform Head + { + get + { + if (m_head == null) + { + m_head = GetComponent().GetBoneTransform(HumanBodyBones.Head); + } + return m_head; + } + } + + void Reset() + { + var animator = GetComponent(); + m_head = animator.GetBoneTransform(HumanBodyBones.Head); + } + + private void OnValidate() + { + if (LookAt != null) + { + LookAt.HorizontalInner.OnValidate(); + LookAt.HorizontalOuter.OnValidate(); + LookAt.VerticalUp.OnValidate(); + LookAt.VerticalDown.OnValidate(); + } + } + + private void Start() + { + Expression.OnStart(transform); + + // get lookat origin + var animator = GetComponent(); + if (animator != null) + { + m_head = animator.GetBoneTransform(HumanBodyBones.Head); + LookAt.Setup(animator, m_head); + } + } + + /// + /// 毎フレーム関連コンポーネントを解決する + /// + /// * Contraint + /// * Spring + /// * LookAt + /// * Expression + /// + /// + public void Apply() + { + // + // constraint + // + if (m_constraints == null) + { + m_constraints = GetComponentsInChildren(); + } + foreach (var constraint in m_constraints) + { + constraint.Process(); + } + + // + // spring + // + if (m_springs == null) + { + m_springs = GetComponentsInChildren(); + } + foreach (var spring in m_springs) + { + spring.Process(); + } + + // + // expression + // + var validateState = Expression.Begin(); + + // + // gaze control + // + LookAt.Process(Head, Expression.SetPresetValue, validateState.ignoreLookAt); + + // + // expression + // + Expression.End(validateState); + } + + private void Update() + { + if (Controller.UpdateType == UpdateTypes.Update) + { + Apply(); + } + } + + private void LateUpdate() + { + if (Controller.UpdateType == UpdateTypes.LateUpdate) + { + Apply(); + } + } + } +} diff --git a/Assets/VRM10/Runtime/Components/VRM10Controller.cs.meta b/Assets/VRM10/Runtime/Components/VRM10Controller.cs.meta new file mode 100644 index 000000000..88777e93f --- /dev/null +++ b/Assets/VRM10/Runtime/Components/VRM10Controller.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 09958303305bb37408d08e992481d7e9 +timeCreated: 1517467747 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/VRM10ControllerExpression.cs b/Assets/VRM10/Runtime/Components/VRM10ControllerExpression.cs new file mode 100644 index 000000000..6036ca887 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/VRM10ControllerExpression.cs @@ -0,0 +1,228 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace UniVRM10 +{ + [Serializable] + public class VRM10ControllerExpression : IDisposable + { + [SerializeField] + public VRM10ExpressionAvatar ExpressionAvatar; + + bool m_ignoreBlink; + bool m_ignoreLookAt; + bool m_ignoreMouth; + + #region for CustomEditor + public bool IgnoreBlink => m_ignoreBlink; + public bool IgnoreLookAt => m_ignoreLookAt; + public bool IgnoreMouth => m_ignoreMouth; + #endregion + + public Dictionary expressionKeyWeights = new Dictionary(); + + List m_blinkExpressionKeys = new List(); + List m_lookAtExpressionKeys = new List(); + List m_mouthExpressionKeys = new List(); + + ExpressionMerger m_merger; + + public void Dispose() + { + if (m_merger != null) + { + m_merger.RestoreMaterialInitialValues(); + } + } + + public void OnStart(Transform transform) + { + if (ExpressionAvatar != null) + { + if (m_merger == null) + { + m_merger = new ExpressionMerger(ExpressionAvatar.Clips, transform); + } + } + + expressionKeyWeights = m_merger.ExpressionKeys.ToDictionary(x => x, x => 0.0f); + + m_blinkExpressionKeys.Add(m_merger.ExpressionKeys.FirstOrDefault(x => x.Preset == VrmLib.ExpressionPreset.Blink)); + m_lookAtExpressionKeys.Add(m_merger.ExpressionKeys.FirstOrDefault(x => x.Preset == VrmLib.ExpressionPreset.LookUp)); + m_lookAtExpressionKeys.Add(m_merger.ExpressionKeys.FirstOrDefault(x => x.Preset == VrmLib.ExpressionPreset.LookDown)); + m_lookAtExpressionKeys.Add(m_merger.ExpressionKeys.FirstOrDefault(x => x.Preset == VrmLib.ExpressionPreset.LookLeft)); + m_lookAtExpressionKeys.Add(m_merger.ExpressionKeys.FirstOrDefault(x => x.Preset == VrmLib.ExpressionPreset.LookRight)); + m_mouthExpressionKeys.Add(m_merger.ExpressionKeys.FirstOrDefault(x => x.Preset == VrmLib.ExpressionPreset.Aa)); + m_mouthExpressionKeys.Add(m_merger.ExpressionKeys.FirstOrDefault(x => x.Preset == VrmLib.ExpressionPreset.Ih)); + m_mouthExpressionKeys.Add(m_merger.ExpressionKeys.FirstOrDefault(x => x.Preset == VrmLib.ExpressionPreset.Ou)); + m_mouthExpressionKeys.Add(m_merger.ExpressionKeys.FirstOrDefault(x => x.Preset == VrmLib.ExpressionPreset.Ee)); + m_mouthExpressionKeys.Add(m_merger.ExpressionKeys.FirstOrDefault(x => x.Preset == VrmLib.ExpressionPreset.Oh)); + } + + /// + /// SetValue + /// + /// + /// + public void SetValue(ExpressionKey key, float value) + { + if (expressionKeyWeights.ContainsKey(key)) + { + expressionKeyWeights[key] = value; + } + } + + /// + /// Get a expression value + /// + /// + /// + public float GetValue(ExpressionKey key) + { + if (expressionKeyWeights.ContainsKey(key)) + { + return expressionKeyWeights[key]; + } + else + { + return 0.0f; + } + } + + public IEnumerable> GetValues() + { + return expressionKeyWeights.Select(x => new KeyValuePair(x.Key, x.Value)); + } + + /// + /// Set expression values. + /// + /// + public void SetValues(IEnumerable> values) + { + foreach (var keyValue in values) + { + if (expressionKeyWeights.ContainsKey(keyValue.Key)) + { + expressionKeyWeights[keyValue.Key] = keyValue.Value; + } + } + } + + #region Setter and Getter + public float GetPresetValue(VrmLib.ExpressionPreset key) + { + var expressionKey = new ExpressionKey(key); + if (this.expressionKeyWeights.ContainsKey(expressionKey)) + { + return this.expressionKeyWeights[expressionKey]; + } + else + { + return 0.0f; + } + } + + public float GetCustomValue(String key) + { + var expressionKey = ExpressionKey.CreateCustom(key); + if (this.expressionKeyWeights.ContainsKey(expressionKey)) + { + return this.expressionKeyWeights[expressionKey]; + } + else + { + return 0.0f; + } + } + + public void SetPresetValue(VrmLib.ExpressionPreset key, float value) + { + var expressionKey = new ExpressionKey(key); + if (this.expressionKeyWeights.ContainsKey(expressionKey)) + { + this.expressionKeyWeights[expressionKey] = value; + } + } + + /// key + public void SetCustomValue(String key, float value) + { + var expressionKey = ExpressionKey.CreateCustom(key); + if (this.expressionKeyWeights.ContainsKey(expressionKey)) + { + this.expressionKeyWeights[expressionKey] = value; + } + } + #endregion + + public IEnumerable GetKeys() + { + return expressionKeyWeights.Keys; + } + + private (bool ignoreBlink, bool ignoreLookAt, bool ignoreMouth) GetValidateState() + { + var ignoreBlink = false; + var ignoreLookAt = false; + var ignoreMouth = false; + + foreach (var keyWeight in expressionKeyWeights) + { + if (keyWeight.Value <= 0.0f) + continue; + + // Blink + if (!ignoreBlink && m_merger.GetClip(keyWeight.Key).IgnoreBlink && !m_blinkExpressionKeys.Contains(keyWeight.Key)) + { + ignoreBlink = true; + } + + // LookAt + if (!ignoreLookAt && m_merger.GetClip(keyWeight.Key).IgnoreLookAt && !m_lookAtExpressionKeys.Contains(keyWeight.Key)) + { + ignoreLookAt = true; + } + + // Mouth + if (!ignoreMouth && m_merger.GetClip(keyWeight.Key).IgnoreMouth && !m_mouthExpressionKeys.Contains(keyWeight.Key)) + { + ignoreMouth = true; + } + } + + return (ignoreBlink, ignoreLookAt, ignoreMouth); + } + + public (bool ignoreBlink, bool ignoreLookAt, bool ignoreMouth) Begin() + { + // + // get ingore settings + // + var validateState = GetValidateState(); + m_ignoreBlink = validateState.ignoreBlink; + m_ignoreLookAt = validateState.ignoreLookAt; + m_ignoreMouth = validateState.ignoreMouth; + + if (validateState.ignoreBlink) + { + // cancel blink + m_blinkExpressionKeys.ForEach(x => expressionKeyWeights[x] = 0.0f); + } + + return validateState; + } + + public void End((bool ignoreBlink, bool ignoreLookAt, bool ignoreMouth) validateState) + { + if (validateState.ignoreMouth) + { + m_mouthExpressionKeys.ForEach(x => expressionKeyWeights[x] = 0.0f); + } + m_merger.SetValues(expressionKeyWeights.Select(x => new KeyValuePair(x.Key, x.Value))); + m_merger.Apply(); + } + } +} diff --git a/Assets/VRM10/Runtime/Components/VRM10ControllerExpression.cs.meta b/Assets/VRM10/Runtime/Components/VRM10ControllerExpression.cs.meta new file mode 100644 index 000000000..902dacd1d --- /dev/null +++ b/Assets/VRM10/Runtime/Components/VRM10ControllerExpression.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 49cc95eade43a804fa7a5b8bfd6875d5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/VRM10ControllerFirstPerson.cs b/Assets/VRM10/Runtime/Components/VRM10ControllerFirstPerson.cs new file mode 100644 index 000000000..a4b38b189 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/VRM10ControllerFirstPerson.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using MeshUtility; +using UnityEngine; + +namespace UniVRM10 +{ + [Serializable] + public class VRM10ControllerFirstPerson + { + [SerializeField] + public List Renderers = new List(); + + public void CopyTo(GameObject _dst, Dictionary map) + { + var dst = _dst.GetOrAddComponent(); + dst.FirstPerson.Renderers = Renderers.Select(x => + { + var renderer = map[x.Renderer.transform].GetComponent(); + return new RendererFirstPersonFlags + { + Renderer = renderer, + FirstPersonFlag = x.FirstPersonFlag, + }; + }).ToList(); + } + + // If no layer names are set, use the default layer IDs. + // Otherwise use the two Unity layers called "VRMFirstPersonOnly" and "VRMThirdPersonOnly". + public static bool TriedSetupLayer = false; + public static int FIRSTPERSON_ONLY_LAYER = 9; + public static int THIRDPERSON_ONLY_LAYER = 10; + + public static void SetupLayers() + { + if (!TriedSetupLayer) + { + TriedSetupLayer = true; + int layer = LayerMask.NameToLayer("VRMFirstPersonOnly"); + FIRSTPERSON_ONLY_LAYER = (layer == -1) ? FIRSTPERSON_ONLY_LAYER : layer; + layer = LayerMask.NameToLayer("VRMThirdPersonOnly"); + THIRDPERSON_ONLY_LAYER = (layer == -1) ? THIRDPERSON_ONLY_LAYER : layer; + } + } + + static int[] GetBonesThatHasAncestor(SkinnedMeshRenderer smr, Transform ancestor) + { + var eraseBones = smr.bones + .Where(x => x.Ancestor().Any(y => y == ancestor)) + .Select(x => Array.IndexOf(smr.bones, x)) + .ToArray(); + return eraseBones; + } + + // + // 頭部を取り除いたモデルを複製する + // + // renderer: 元になるSkinnedMeshRenderer + // eraseBones: 削除対象になるボーンのindex + private static SkinnedMeshRenderer CreateHeadlessMesh(SkinnedMeshRenderer renderer, int[] eraseBones) + { + var mesh = BoneMeshEraser.CreateErasedMesh(renderer.sharedMesh, eraseBones); + + var go = new GameObject("_headless_" + renderer.name); + var erased = go.AddComponent(); + erased.sharedMesh = mesh; + erased.sharedMaterials = renderer.sharedMaterials; + erased.bones = renderer.bones; + erased.rootBone = renderer.rootBone; + erased.updateWhenOffscreen = true; + + return erased; + } + + bool m_done; + + /// + /// 配下のモデルのレイヤー設定など + /// + public void Setup(GameObject go) + { + SetupLayers(); + if (m_done) + { + return; + } + m_done = true; + + var FirstPersonBone = go.GetComponent().GetBoneTransform(HumanBodyBones.Head); + foreach (var x in Renderers) + { + switch (x.FirstPersonFlag) + { + case VrmLib.FirstPersonMeshType.Auto: + { + if (x.Renderer is SkinnedMeshRenderer smr) + { + var eraseBones = GetBonesThatHasAncestor(smr, FirstPersonBone); + if (eraseBones.Any()) + { + // オリジナルのモデルを3人称用にする + smr.gameObject.layer = THIRDPERSON_ONLY_LAYER; + + // 頭を取り除いた複製モデルを作成し、1人称用にする + var headless = CreateHeadlessMesh(smr, eraseBones); + headless.gameObject.layer = FIRSTPERSON_ONLY_LAYER; + headless.transform.SetParent(smr.transform, false); + } + else + { + // 削除対象が含まれないので何もしない + } + } + else if (x.Renderer is MeshRenderer mr) + { + if (mr.transform.Ancestors().Any(y => y == FirstPersonBone)) + { + // 頭の子孫なので1人称では非表示に + mr.gameObject.layer = THIRDPERSON_ONLY_LAYER; + } + else + { + // 特に変更しない => 両方表示 + } + } + else + { + throw new NotImplementedException(); + } + } + break; + + case VrmLib.FirstPersonMeshType.FirstPersonOnly: + // 1人称のカメラでだけ描画されるようにする + x.Renderer.gameObject.layer = FIRSTPERSON_ONLY_LAYER; + break; + + case VrmLib.FirstPersonMeshType.ThirdPersonOnly: + // 3人称のカメラでだけ描画されるようにする + x.Renderer.gameObject.layer = THIRDPERSON_ONLY_LAYER; + break; + + case VrmLib.FirstPersonMeshType.Both: + // 特に何もしない。すべてのカメラで描画される + break; + } + } + } + } +} diff --git a/Assets/VRM10/Runtime/Components/VRM10ControllerFirstPerson.cs.meta b/Assets/VRM10/Runtime/Components/VRM10ControllerFirstPerson.cs.meta new file mode 100644 index 000000000..6fee5a6b0 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/VRM10ControllerFirstPerson.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3139d59f7bb86b24c933ece483addae3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/VRM10ControllerLookAt.cs b/Assets/VRM10/Runtime/Components/VRM10ControllerLookAt.cs new file mode 100644 index 000000000..551250cfe --- /dev/null +++ b/Assets/VRM10/Runtime/Components/VRM10ControllerLookAt.cs @@ -0,0 +1,311 @@ +using System; +using UnityEngine; +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace UniVRM10 +{ + [Serializable] + public class VRM10ControllerLookAt + { + public enum LookAtTypes + { + // Gaze control by bone (leftEye, rightEye) + Bone, + // Gaze control by blend shape (lookUp, lookDown, lookLeft, lookRight) + Expression, + } + + public enum LookAtTargetTypes + { + CalcYawPitchToGaze, + SetYawPitch, + } + + [SerializeField] + public bool DrawGizmo = true; + + [SerializeField] + public Vector3 OffsetFromHead = new Vector3(0, 0.06f, 0); + + [SerializeField] + public LookAtTypes LookAtType; + + [SerializeField] + public CurveMapper HorizontalOuter = new CurveMapper(90.0f, 10.0f); + + [SerializeField] + public CurveMapper HorizontalInner = new CurveMapper(90.0f, 10.0f); + + [SerializeField] + public CurveMapper VerticalDown = new CurveMapper(90.0f, 10.0f); + + [SerializeField] + public CurveMapper VerticalUp = new CurveMapper(90.0f, 10.0f); + + [SerializeField] + public LookAtTargetTypes LookAtTargetType; + + OffsetOnTransform m_leftEye; + OffsetOnTransform m_rightEye; + + #region LookAtTargetTypes.CalcYawPitchToGaze + /// + /// LookAtTargetTypes.CalcYawPitchToGaze時の注視点 + /// + [SerializeField] + public Transform Gaze; + + // 座標計算用のempty + Transform m_lookAtOrigin; + Transform GetLookAtOrigin(Transform head) + { + if (!Application.isPlaying) + { + return null; + } + if (m_lookAtOrigin == null) + { + m_lookAtOrigin = new GameObject("_lookat_origin_").transform; + m_lookAtOrigin.SetParent(head); + } + return m_lookAtOrigin; + } + + /// + /// Headローカルの注視点からYaw, Pitch角を計算する + /// + (float, float) CalcLookAtYawPitch(Vector3 targetWorldPosition, Transform head) + { + GetLookAtOrigin(head).localPosition = OffsetFromHead; + + var localPosition = m_lookAtOrigin.worldToLocalMatrix.MultiplyPoint(targetWorldPosition); + float yaw, pitch; + Matrix4x4.identity.CalcYawPitch(localPosition, out yaw, out pitch); + return (yaw, pitch); + } + #endregion + + #region LookAtTargetTypes.SetYawPitch + float m_yaw; + float m_pitch; + + /// + /// LookAtTargetTypes.SetYawPitch時の視線の角度を指定する + /// + /// Headボーンのforwardに対するyaw角(度) + /// Headボーンのforwardに対するpitch角(度) + public void SetLookAtYawPitch(float yaw, float pitch) + { + m_yaw = yaw; + m_pitch = pitch; + } + #endregion + + /// + /// LookAtTargetType に応じた yaw, pitch を得る + /// + /// Headボーンのforwardに対するyaw角(度), pitch角(度) + public (float, float) GetLookAtYawPitch(Transform head) + { + switch (LookAtTargetType) + { + case LookAtTargetTypes.CalcYawPitchToGaze: + // Gaze(Transform)のワールド位置に対して計算する + return CalcLookAtYawPitch(Gaze.position, head); + + case LookAtTargetTypes.SetYawPitch: + // 事前にSetYawPitchした値を使う + return (m_yaw, m_pitch); + } + + throw new NotImplementedException(); + } + + /// + /// LeftEyeボーンとRightEyeボーンに回転を適用する + /// + void LookAtBone(float yaw, float pitch) + { + // horizontal + float leftYaw, rightYaw; + if (yaw < 0) + { + leftYaw = -HorizontalOuter.Map(-yaw); + rightYaw = -HorizontalInner.Map(-yaw); + } + else + { + rightYaw = HorizontalOuter.Map(yaw); + leftYaw = HorizontalInner.Map(yaw); + } + + // vertical + if (pitch < 0) + { + pitch = -VerticalDown.Map(-pitch); + } + else + { + pitch = VerticalUp.Map(pitch); + } + + // Apply + if (m_leftEye.Transform != null && m_rightEye.Transform != null) + { + // 目に値を適用する + m_leftEye.Transform.rotation = m_leftEye.InitialWorldMatrix.ExtractRotation() * Matrix4x4.identity.YawPitchRotation(leftYaw, pitch); + m_rightEye.Transform.rotation = m_rightEye.InitialWorldMatrix.ExtractRotation() * Matrix4x4.identity.YawPitchRotation(rightYaw, pitch); + } + } + + public delegate void SetPresetValue(VrmLib.ExpressionPreset preset, float weight); + + /// + /// Expression による LookAt を処理する(関連する Expression の Weight を変更する) + /// + /// + /// + void LookAtExpression(float yaw, float pitch, SetPresetValue SetPresetValue) + { + if (yaw < 0) + { + // Left + SetPresetValue(VrmLib.ExpressionPreset.LookRight, 0); // clear first + SetPresetValue(VrmLib.ExpressionPreset.LookLeft, Mathf.Clamp(HorizontalOuter.Map(-yaw), 0, 1.0f)); + } + else + { + // Right + SetPresetValue(VrmLib.ExpressionPreset.LookLeft, 0); // clear first + SetPresetValue(VrmLib.ExpressionPreset.LookRight, Mathf.Clamp(HorizontalOuter.Map(yaw), 0, 1.0f)); + } + + if (pitch < 0) + { + // Down + SetPresetValue(VrmLib.ExpressionPreset.LookUp, 0); // clear first + SetPresetValue(VrmLib.ExpressionPreset.LookDown, Mathf.Clamp(VerticalDown.Map(-pitch), 0, 1.0f)); + } + else + { + // Up + SetPresetValue(VrmLib.ExpressionPreset.LookDown, 0); // clear first + SetPresetValue(VrmLib.ExpressionPreset.LookUp, Mathf.Clamp(VerticalUp.Map(pitch), 0, 1.0f)); + } + } + + public void Setup(Animator animator, Transform head) + { + m_leftEye = OffsetOnTransform.Create(animator.GetBoneTransform(HumanBodyBones.LeftEye)); + m_rightEye = OffsetOnTransform.Create(animator.GetBoneTransform(HumanBodyBones.RightEye)); + if (Gaze == null) + { + Gaze = new GameObject().transform; + Gaze.name = "__LOOKAT_GAZE__"; + Gaze.SetParent(head); + Gaze.localPosition = Vector3.forward; + } + } + + public void Process(Transform head, SetPresetValue setPresetValue, bool ignoreLookAt) + { + var (yaw, pitch) = (ignoreLookAt) + ? (0.0f, 0.0f) + : GetLookAtYawPitch(head) + ; + switch (LookAtType) + { + case LookAtTypes.Bone: + LookAtBone(yaw, pitch); + break; + + case LookAtTypes.Expression: + LookAtExpression(yaw, pitch, setPresetValue); + break; + } + } + +#if UNITY_EDITOR + #region Gizmo + static void DrawMatrix(Matrix4x4 m, float size) + { + Gizmos.matrix = m; + Gizmos.color = Color.red; + Gizmos.DrawLine(Vector3.zero, Vector3.right * size); + Gizmos.color = Color.green; + Gizmos.DrawLine(Vector3.zero, Vector3.up * size); + Gizmos.color = Color.blue; + Gizmos.DrawLine(Vector3.zero, Vector3.forward * size); + } + + const float LOOKAT_GIZMO_SIZE = 0.5f; + + private void OnDrawGizmos() + { + if (DrawGizmo) + { + if (m_leftEye.Transform != null & m_rightEye.Transform != null) + { + DrawMatrix(m_leftEye.WorldMatrix, LOOKAT_GIZMO_SIZE); + DrawMatrix(m_rightEye.WorldMatrix, LOOKAT_GIZMO_SIZE); + } + } + } + #endregion + + const float RADIUS = 0.5f; + + public void OnSceneGUILookAt(Transform head) + { + if (head == null) return; + if (!DrawGizmo) return; + + if (Gaze != null) + { + { + EditorGUI.BeginChangeCheck(); + var newTargetPosition = Handles.PositionHandle(Gaze.position, Quaternion.identity); + if (EditorGUI.EndChangeCheck()) + { + Undo.RecordObject(Gaze, "Change Look At Target Position"); + Gaze.position = newTargetPosition; + } + } + + Handles.color = new Color(1, 1, 1, 0.6f); + Handles.DrawDottedLine(GetLookAtOrigin(head).position, Gaze.position, 4.0f); + } + + var (yaw, pitch) = GetLookAtYawPitch(head); + var lookAtOriginMatrix = GetLookAtOrigin(head).localToWorldMatrix; + Handles.matrix = lookAtOriginMatrix; + var p = OffsetFromHead; + Handles.Label(Vector3.zero, + $"FromHead: [{p.x:0.00}, {p.y:0.00}, {p.z:0.00}]\nYaw: {yaw:0.}degree\nPitch: {pitch:0.}degree"); + + Handles.color = new Color(0, 1, 0, 0.2f); + Handles.DrawSolidArc(Vector3.zero, + Matrix4x4.identity.GetColumn(1), + Matrix4x4.identity.GetColumn(2), + yaw, + RADIUS); + + + var yawQ = Quaternion.AngleAxis(yaw, Vector3.up); + var yawMatrix = default(Matrix4x4); + yawMatrix.SetTRS(Vector3.zero, yawQ, Vector3.one); + + Handles.matrix = lookAtOriginMatrix * yawMatrix; + Handles.color = new Color(1, 0, 0, 0.2f); + Handles.DrawSolidArc(Vector3.zero, + Matrix4x4.identity.GetColumn(0), + Matrix4x4.identity.GetColumn(2), + -pitch, + RADIUS); + } + +#endif + } +} diff --git a/Assets/VRM10/Runtime/Components/VRM10ControllerLookAt.cs.meta b/Assets/VRM10/Runtime/Components/VRM10ControllerLookAt.cs.meta new file mode 100644 index 000000000..36e7cd8cc --- /dev/null +++ b/Assets/VRM10/Runtime/Components/VRM10ControllerLookAt.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cec6491afc9329e418272980aed747f0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Format.meta b/Assets/VRM10/Runtime/Format.meta new file mode 100644 index 000000000..33ae33272 --- /dev/null +++ b/Assets/VRM10/Runtime/Format.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 94f2f5e159af11947ac84f4a2fff8aca +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Format/Constraints.meta b/Assets/VRM10/Runtime/Format/Constraints.meta new file mode 100644 index 000000000..4f63dc225 --- /dev/null +++ b/Assets/VRM10/Runtime/Format/Constraints.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d4e43cf583d402a468a91afdcd47a323 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Format/Constraints/Deserializer.g.cs b/Assets/VRM10/Runtime/Format/Constraints/Deserializer.g.cs new file mode 100644 index 000000000..9f9523533 --- /dev/null +++ b/Assets/VRM10/Runtime/Format/Constraints/Deserializer.g.cs @@ -0,0 +1,268 @@ +// This file is generated from JsonSchema. Don't modify this source code. +using UniJSON; +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UniGLTF.Extensions.VRMC_constraints { + +public static class GltfDeserializer +{ + +public static bool TryGet(UniGLTF.glTFExtension src, out VRMC_constraints extension) +{ + if(src is UniGLTF.glTFExtensionImport extensions) + { + foreach(var kv in extensions.ObjectItems()) + { + if(kv.Key.GetUtf8String() == VRMC_constraints.ExtensionNameUtf8) + { + extension = Deserialize(kv.Value); + return true; + } + } + } + + extension = default; + return false; +} + + +public static VRMC_constraints Deserialize(ListTreeNode parsed) +{ + var value = new VRMC_constraints(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="extensions"){ + value.Extensions = new glTFExtensionImport(kv.Value); + continue; + } + + if(key=="extras"){ + value.Extras = new glTFExtensionImport(kv.Value); + continue; + } + + if(key=="position"){ + value.Position = Deserialize_Position(kv.Value); + continue; + } + + if(key=="rotation"){ + value.Rotation = Deserialize_Rotation(kv.Value); + continue; + } + + if(key=="aim"){ + value.Aim = Deserialize_Aim(kv.Value); + continue; + } + + } + return value; +} + +public static PositionConstraint Deserialize_Position(ListTreeNode parsed) +{ + var value = new PositionConstraint(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="extensions"){ + value.Extensions = new glTFExtensionImport(kv.Value); + continue; + } + + if(key=="extras"){ + value.Extras = new glTFExtensionImport(kv.Value); + continue; + } + + if(key=="name"){ + value.Name = kv.Value.GetString(); + continue; + } + + if(key=="source"){ + value.Source = kv.Value.GetInt32(); + continue; + } + + if(key=="sourceSpace"){ + value.SourceSpace = (ObjectSpace)Enum.Parse(typeof(ObjectSpace), kv.Value.GetString(), true); + continue; + } + + if(key=="destinationSpace"){ + value.DestinationSpace = (ObjectSpace)Enum.Parse(typeof(ObjectSpace), kv.Value.GetString(), true); + continue; + } + + if(key=="freezeAxes"){ + value.FreezeAxes = Deserialize_FreezeAxes(kv.Value); + continue; + } + + if(key=="weight"){ + value.Weight = kv.Value.GetSingle(); + continue; + } + + } + return value; +} + +public static bool[] Deserialize_FreezeAxes(ListTreeNode parsed) +{ + var value = new bool[parsed.GetArrayCount()]; + int i=0; + foreach(var x in parsed.ArrayItems()) + { + value[i++] = x.GetBoolean(); + } + return value; +} + +public static RotationConstraint Deserialize_Rotation(ListTreeNode parsed) +{ + var value = new RotationConstraint(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="extensions"){ + value.Extensions = new glTFExtensionImport(kv.Value); + continue; + } + + if(key=="extras"){ + value.Extras = new glTFExtensionImport(kv.Value); + continue; + } + + if(key=="name"){ + value.Name = kv.Value.GetString(); + continue; + } + + if(key=="source"){ + value.Source = kv.Value.GetInt32(); + continue; + } + + if(key=="sourceSpace"){ + value.SourceSpace = (ObjectSpace)Enum.Parse(typeof(ObjectSpace), kv.Value.GetString(), true); + continue; + } + + if(key=="destinationSpace"){ + value.DestinationSpace = (ObjectSpace)Enum.Parse(typeof(ObjectSpace), kv.Value.GetString(), true); + continue; + } + + if(key=="freezeAxes"){ + value.FreezeAxes = Deserialize_FreezeAxes(kv.Value); + continue; + } + + if(key=="weight"){ + value.Weight = kv.Value.GetSingle(); + continue; + } + + } + return value; +} + +public static AimConstraint Deserialize_Aim(ListTreeNode parsed) +{ + var value = new AimConstraint(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="extensions"){ + value.Extensions = new glTFExtensionImport(kv.Value); + continue; + } + + if(key=="extras"){ + value.Extras = new glTFExtensionImport(kv.Value); + continue; + } + + if(key=="name"){ + value.Name = kv.Value.GetString(); + continue; + } + + if(key=="source"){ + value.Source = kv.Value.GetInt32(); + continue; + } + + if(key=="sourceSpace"){ + value.SourceSpace = (ObjectSpace)Enum.Parse(typeof(ObjectSpace), kv.Value.GetString(), true); + continue; + } + + if(key=="destinationSpace"){ + value.DestinationSpace = (ObjectSpace)Enum.Parse(typeof(ObjectSpace), kv.Value.GetString(), true); + continue; + } + + if(key=="aimVector"){ + value.AimVector = Deserialize_AimVector(kv.Value); + continue; + } + + if(key=="upVector"){ + value.UpVector = Deserialize_UpVector(kv.Value); + continue; + } + + if(key=="freezeAxes"){ + value.FreezeAxes = Deserialize_FreezeAxes(kv.Value); + continue; + } + + if(key=="weight"){ + value.Weight = kv.Value.GetSingle(); + continue; + } + + } + return value; +} + +public static float[] Deserialize_AimVector(ListTreeNode parsed) +{ + var value = new float[parsed.GetArrayCount()]; + int i=0; + foreach(var x in parsed.ArrayItems()) + { + value[i++] = x.GetSingle(); + } + return value; +} + +public static float[] Deserialize_UpVector(ListTreeNode parsed) +{ + var value = new float[parsed.GetArrayCount()]; + int i=0; + foreach(var x in parsed.ArrayItems()) + { + value[i++] = x.GetSingle(); + } + return value; +} + +} // GltfDeserializer +} // UniGLTF diff --git a/Assets/VRM10/Runtime/Format/Constraints/Deserializer.g.cs.meta b/Assets/VRM10/Runtime/Format/Constraints/Deserializer.g.cs.meta new file mode 100644 index 000000000..446fe34c4 --- /dev/null +++ b/Assets/VRM10/Runtime/Format/Constraints/Deserializer.g.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 43eecb71e6b907349bb0ecca3647fb36 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Format/Constraints/Format.g.cs b/Assets/VRM10/Runtime/Format/Constraints/Format.g.cs new file mode 100644 index 000000000..d998f262a --- /dev/null +++ b/Assets/VRM10/Runtime/Format/Constraints/Format.g.cs @@ -0,0 +1,130 @@ +// This file is generated from JsonSchema. Don't modify this source code. +using System; +using System.Collections.Generic; +using UniGLTF; +using UniJSON; + +namespace UniGLTF.Extensions.VRMC_constraints +{ + + public enum ObjectSpace + { + model, + local, + + } + + public class PositionConstraint + { + // Dictionary object with extension-specific objects. + public glTFExtension Extensions; + + // Application-specific data. + public glTFExtension Extras; + + // The user-defined name of this object. + public string Name; + + // The index of the node constrains the node. + public int? Source; + + // The source node will be evaluated in this space. + [JsonSchema(EnumSerializationType = EnumSerializationType.AsString)] + public ObjectSpace SourceSpace; + + // The destination node will be evaluated in this space. + [JsonSchema(EnumSerializationType = EnumSerializationType.AsString)] + public ObjectSpace DestinationSpace; + + // Axes be constrained by this constraint, in X-Y-Z order. + public bool[] FreezeAxes; + + // The weight of the constraint. + public float? Weight; + } + + public class RotationConstraint + { + // Dictionary object with extension-specific objects. + public glTFExtension Extensions; + + // Application-specific data. + public glTFExtension Extras; + + // The user-defined name of this object. + public string Name; + + // The index of the node constrains the node. + public int? Source; + + // The source node will be evaluated in this space. + [JsonSchema(EnumSerializationType = EnumSerializationType.AsString)] + public ObjectSpace SourceSpace; + + // The destination node will be evaluated in this space. + [JsonSchema(EnumSerializationType = EnumSerializationType.AsString)] + public ObjectSpace DestinationSpace; + + // Axes be constrained by this constraint, in X-Y-Z order. + public bool[] FreezeAxes; + + // The weight of the constraint. + public float? Weight; + } + + public class AimConstraint + { + // Dictionary object with extension-specific objects. + public glTFExtension Extensions; + + // Application-specific data. + public glTFExtension Extras; + + // The user-defined name of this object. + public string Name; + + // The index of the node constrains the node. + public int? Source; + + // The source node will be evaluated in this space. + [JsonSchema(EnumSerializationType = EnumSerializationType.AsString)] + public ObjectSpace SourceSpace; + + // The destination node will be evaluated in this space. + [JsonSchema(EnumSerializationType = EnumSerializationType.AsString)] + public ObjectSpace DestinationSpace; + + // An axis which faces the direction of its sources. + public float[] AimVector; + + // An up axis of the constraint. + public float[] UpVector; + + // Axes be constrained by this constraint, in Yaw-Pitch order. + public bool[] FreezeAxes; + + // The weight of the constraint. + public float? Weight; + } + + public class VRMC_constraints + { + public const string ExtensionName = "VRMC_constraints"; + public static readonly Utf8String ExtensionNameUtf8 = Utf8String.From(ExtensionName); + + // Dictionary object with extension-specific objects. + public glTFExtension Extensions; + + // Application-specific data. + public glTFExtension Extras; + + // A constraint that links the position with sources. + public PositionConstraint Position; + + // A constraint that links the rotation with sources. + public RotationConstraint Rotation; + + // A constraint that rotates the node to face sources. + public AimConstraint Aim; + } +} diff --git a/Assets/VRM10/Runtime/Format/Constraints/Format.g.cs.meta b/Assets/VRM10/Runtime/Format/Constraints/Format.g.cs.meta new file mode 100644 index 000000000..067282758 --- /dev/null +++ b/Assets/VRM10/Runtime/Format/Constraints/Format.g.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7547f9592a249434d8adc9def62b607e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Format/Constraints/Serializer.g.cs b/Assets/VRM10/Runtime/Format/Constraints/Serializer.g.cs new file mode 100644 index 000000000..23474e085 --- /dev/null +++ b/Assets/VRM10/Runtime/Format/Constraints/Serializer.g.cs @@ -0,0 +1,255 @@ +// This file is generated from JsonSchema. Don't modify this source code. +using System; +using System.Collections.Generic; +using System.Linq; +using UniJSON; + +namespace UniGLTF.Extensions.VRMC_constraints { + + static public class GltfSerializer + { + + public static void SerializeTo(ref UniGLTF.glTFExtension dst, VRMC_constraints extension) + { + if (dst is glTFExtensionImport) + { + throw new NotImplementedException(); + } + + if (!(dst is glTFExtensionExport extensions)) + { + extensions = new glTFExtensionExport(); + dst = extensions; + } + + var f = new JsonFormatter(); + Serialize(f, extension); + extensions.Add(VRMC_constraints.ExtensionName, f.GetStoreBytes()); + } + + +public static void Serialize(JsonFormatter f, VRMC_constraints value) +{ + f.BeginMap(); + + + if(value.Extensions!=null){ + f.Key("extensions"); + value.Extensions.Serialize(f); + } + + if(value.Extras!=null){ + f.Key("extras"); + value.Extras.Serialize(f); + } + + if(value.Position!=null){ + f.Key("position"); + Serialize_Position(f, value.Position); + } + + if(value.Rotation!=null){ + f.Key("rotation"); + Serialize_Rotation(f, value.Rotation); + } + + if(value.Aim!=null){ + f.Key("aim"); + Serialize_Aim(f, value.Aim); + } + + f.EndMap(); +} + +public static void Serialize_Position(JsonFormatter f, PositionConstraint value) +{ + f.BeginMap(); + + + if(value.Extensions!=null){ + f.Key("extensions"); + value.Extensions.Serialize(f); + } + + if(value.Extras!=null){ + f.Key("extras"); + value.Extras.Serialize(f); + } + + if(!string.IsNullOrEmpty(value.Name)){ + f.Key("name"); + f.Value(value.Name); + } + + if(value.Source.HasValue){ + f.Key("source"); + f.Value(value.Source.GetValueOrDefault()); + } + + if(true){ + f.Key("sourceSpace"); + f.Value(value.SourceSpace.ToString()); + } + + if(true){ + f.Key("destinationSpace"); + f.Value(value.DestinationSpace.ToString()); + } + + if(value.FreezeAxes!=null&&value.FreezeAxes.Count()>=3){ + f.Key("freezeAxes"); + Serialize_FreezeAxes(f, value.FreezeAxes); + } + + if(value.Weight.HasValue){ + f.Key("weight"); + f.Value(value.Weight.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_FreezeAxes(JsonFormatter f, bool[] value) +{ + f.BeginList(); + + foreach(var item in value) + { + f.Value(item); + + } + f.EndList(); +} + +public static void Serialize_Rotation(JsonFormatter f, RotationConstraint value) +{ + f.BeginMap(); + + + if(value.Extensions!=null){ + f.Key("extensions"); + value.Extensions.Serialize(f); + } + + if(value.Extras!=null){ + f.Key("extras"); + value.Extras.Serialize(f); + } + + if(!string.IsNullOrEmpty(value.Name)){ + f.Key("name"); + f.Value(value.Name); + } + + if(value.Source.HasValue){ + f.Key("source"); + f.Value(value.Source.GetValueOrDefault()); + } + + if(true){ + f.Key("sourceSpace"); + f.Value(value.SourceSpace.ToString()); + } + + if(true){ + f.Key("destinationSpace"); + f.Value(value.DestinationSpace.ToString()); + } + + if(value.FreezeAxes!=null&&value.FreezeAxes.Count()>=3){ + f.Key("freezeAxes"); + Serialize_FreezeAxes(f, value.FreezeAxes); + } + + if(value.Weight.HasValue){ + f.Key("weight"); + f.Value(value.Weight.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_Aim(JsonFormatter f, AimConstraint value) +{ + f.BeginMap(); + + + if(value.Extensions!=null){ + f.Key("extensions"); + value.Extensions.Serialize(f); + } + + if(value.Extras!=null){ + f.Key("extras"); + value.Extras.Serialize(f); + } + + if(!string.IsNullOrEmpty(value.Name)){ + f.Key("name"); + f.Value(value.Name); + } + + if(value.Source.HasValue){ + f.Key("source"); + f.Value(value.Source.GetValueOrDefault()); + } + + if(true){ + f.Key("sourceSpace"); + f.Value(value.SourceSpace.ToString()); + } + + if(true){ + f.Key("destinationSpace"); + f.Value(value.DestinationSpace.ToString()); + } + + if(value.AimVector!=null&&value.AimVector.Count()>=3){ + f.Key("aimVector"); + Serialize_AimVector(f, value.AimVector); + } + + if(value.UpVector!=null&&value.UpVector.Count()>=3){ + f.Key("upVector"); + Serialize_UpVector(f, value.UpVector); + } + + if(value.FreezeAxes!=null&&value.FreezeAxes.Count()>=2){ + f.Key("freezeAxes"); + Serialize_FreezeAxes(f, value.FreezeAxes); + } + + if(value.Weight.HasValue){ + f.Key("weight"); + f.Value(value.Weight.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_AimVector(JsonFormatter f, float[] value) +{ + f.BeginList(); + + foreach(var item in value) + { + f.Value(item); + + } + f.EndList(); +} + +public static void Serialize_UpVector(JsonFormatter f, float[] value) +{ + f.BeginList(); + + foreach(var item in value) + { + f.Value(item); + + } + f.EndList(); +} + + } // class +} // namespace diff --git a/Assets/VRM10/Runtime/Format/Constraints/Serializer.g.cs.meta b/Assets/VRM10/Runtime/Format/Constraints/Serializer.g.cs.meta new file mode 100644 index 000000000..a549d95f0 --- /dev/null +++ b/Assets/VRM10/Runtime/Format/Constraints/Serializer.g.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7f81ff5ee0b16a14abf76d93cc5bece7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Format/MaterialsMToon.meta b/Assets/VRM10/Runtime/Format/MaterialsMToon.meta new file mode 100644 index 000000000..3179411c4 --- /dev/null +++ b/Assets/VRM10/Runtime/Format/MaterialsMToon.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ec5c4b408d986ed47bf5f2f618f0cbba +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Format/MaterialsMToon/Deserializer.g.cs b/Assets/VRM10/Runtime/Format/MaterialsMToon/Deserializer.g.cs new file mode 100644 index 000000000..570e9a0c7 --- /dev/null +++ b/Assets/VRM10/Runtime/Format/MaterialsMToon/Deserializer.g.cs @@ -0,0 +1,207 @@ +// This file is generated from JsonSchema. Don't modify this source code. +using UniJSON; +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UniGLTF.Extensions.VRMC_materials_mtoon { + +public static class GltfDeserializer +{ + +public static bool TryGet(UniGLTF.glTFExtension src, out VRMC_materials_mtoon extension) +{ + if(src is UniGLTF.glTFExtensionImport extensions) + { + foreach(var kv in extensions.ObjectItems()) + { + if(kv.Key.GetUtf8String() == VRMC_materials_mtoon.ExtensionNameUtf8) + { + extension = Deserialize(kv.Value); + return true; + } + } + } + + extension = default; + return false; +} + + +public static VRMC_materials_mtoon Deserialize(ListTreeNode parsed) +{ + var value = new VRMC_materials_mtoon(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="version"){ + value.Version = kv.Value.GetString(); + continue; + } + + if(key=="transparentWithZWrite"){ + value.TransparentWithZWrite = kv.Value.GetBoolean(); + continue; + } + + if(key=="renderQueueOffsetNumber"){ + value.RenderQueueOffsetNumber = kv.Value.GetInt32(); + continue; + } + + if(key=="shadeFactor"){ + value.ShadeFactor = Deserialize_ShadeFactor(kv.Value); + continue; + } + + if(key=="shadeMultiplyTexture"){ + value.ShadeMultiplyTexture = kv.Value.GetInt32(); + continue; + } + + if(key=="shadingShiftFactor"){ + value.ShadingShiftFactor = kv.Value.GetSingle(); + continue; + } + + if(key=="shadingToonyFactor"){ + value.ShadingToonyFactor = kv.Value.GetSingle(); + continue; + } + + if(key=="lightColorAttenuationFactor"){ + value.LightColorAttenuationFactor = kv.Value.GetSingle(); + continue; + } + + if(key=="giIntensityFactor"){ + value.GiIntensityFactor = kv.Value.GetSingle(); + continue; + } + + if(key=="additiveTexture"){ + value.AdditiveTexture = kv.Value.GetInt32(); + continue; + } + + if(key=="rimFactor"){ + value.RimFactor = Deserialize_RimFactor(kv.Value); + continue; + } + + if(key=="rimMultiplyTexture"){ + value.RimMultiplyTexture = kv.Value.GetInt32(); + continue; + } + + if(key=="rimLightingMixFactor"){ + value.RimLightingMixFactor = kv.Value.GetSingle(); + continue; + } + + if(key=="rimFresnelPowerFactor"){ + value.RimFresnelPowerFactor = kv.Value.GetSingle(); + continue; + } + + if(key=="rimLiftFactor"){ + value.RimLiftFactor = kv.Value.GetSingle(); + continue; + } + + if(key=="outlineWidthMode"){ + value.OutlineWidthMode = (OutlineWidthMode)Enum.Parse(typeof(OutlineWidthMode), kv.Value.GetString(), true); + continue; + } + + if(key=="outlineWidthFactor"){ + value.OutlineWidthFactor = kv.Value.GetSingle(); + continue; + } + + if(key=="outlineWidthMultiplyTexture"){ + value.OutlineWidthMultiplyTexture = kv.Value.GetInt32(); + continue; + } + + if(key=="outlineScaledMaxDistanceFactor"){ + value.OutlineScaledMaxDistanceFactor = kv.Value.GetSingle(); + continue; + } + + if(key=="outlineColorMode"){ + value.OutlineColorMode = (OutlineColorMode)Enum.Parse(typeof(OutlineColorMode), kv.Value.GetString(), true); + continue; + } + + if(key=="outlineFactor"){ + value.OutlineFactor = Deserialize_OutlineFactor(kv.Value); + continue; + } + + if(key=="outlineLightingMixFactor"){ + value.OutlineLightingMixFactor = kv.Value.GetSingle(); + continue; + } + + if(key=="uvAnimationMaskTexture"){ + value.UvAnimationMaskTexture = kv.Value.GetInt32(); + continue; + } + + if(key=="uvAnimationScrollXSpeedFactor"){ + value.UvAnimationScrollXSpeedFactor = kv.Value.GetSingle(); + continue; + } + + if(key=="uvAnimationScrollYSpeedFactor"){ + value.UvAnimationScrollYSpeedFactor = kv.Value.GetSingle(); + continue; + } + + if(key=="uvAnimationRotationSpeedFactor"){ + value.UvAnimationRotationSpeedFactor = kv.Value.GetSingle(); + continue; + } + + } + return value; +} + +public static float[] Deserialize_ShadeFactor(ListTreeNode parsed) +{ + var value = new float[parsed.GetArrayCount()]; + int i=0; + foreach(var x in parsed.ArrayItems()) + { + value[i++] = x.GetSingle(); + } + return value; +} + +public static float[] Deserialize_RimFactor(ListTreeNode parsed) +{ + var value = new float[parsed.GetArrayCount()]; + int i=0; + foreach(var x in parsed.ArrayItems()) + { + value[i++] = x.GetSingle(); + } + return value; +} + +public static float[] Deserialize_OutlineFactor(ListTreeNode parsed) +{ + var value = new float[parsed.GetArrayCount()]; + int i=0; + foreach(var x in parsed.ArrayItems()) + { + value[i++] = x.GetSingle(); + } + return value; +} + +} // GltfDeserializer +} // UniGLTF diff --git a/Assets/VRM10/Runtime/Format/MaterialsMToon/Deserializer.g.cs.meta b/Assets/VRM10/Runtime/Format/MaterialsMToon/Deserializer.g.cs.meta new file mode 100644 index 000000000..50b528f5d --- /dev/null +++ b/Assets/VRM10/Runtime/Format/MaterialsMToon/Deserializer.g.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ef33feaabe6513840802975ece5179d8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Format/MaterialsMToon/Format.g.cs b/Assets/VRM10/Runtime/Format/MaterialsMToon/Format.g.cs new file mode 100644 index 000000000..b835241cc --- /dev/null +++ b/Assets/VRM10/Runtime/Format/MaterialsMToon/Format.g.cs @@ -0,0 +1,90 @@ +// This file is generated from JsonSchema. Don't modify this source code. +using System; +using System.Collections.Generic; +using UniGLTF; +using UniJSON; + +namespace UniGLTF.Extensions.VRMC_materials_mtoon +{ + + public enum OutlineWidthMode + { + none, + worldCoordinates, + screenCoordinates, + + } + + public enum OutlineColorMode + { + fixedColor, + mixedLighting, + + } + + public class VRMC_materials_mtoon + { + public const string ExtensionName = "VRMC_materials_mtoon"; + public static readonly Utf8String ExtensionNameUtf8 = Utf8String.From(ExtensionName); + + // Meta + public string Version; + + // enable depth buffer when renderMode is transparent + public bool? TransparentWithZWrite; + + public int? RenderQueueOffsetNumber; + + public float[] ShadeFactor; + + public int? ShadeMultiplyTexture; + + // Lighting + public float? ShadingShiftFactor; + + public float? ShadingToonyFactor; + + public float? LightColorAttenuationFactor; + + public float? GiIntensityFactor; + + // MatCap + public int? AdditiveTexture; + + // Rim + public float[] RimFactor; + + public int? RimMultiplyTexture; + + public float? RimLightingMixFactor; + + public float? RimFresnelPowerFactor; + + public float? RimLiftFactor; + + // Outline + [JsonSchema(EnumSerializationType = EnumSerializationType.AsString)] + public OutlineWidthMode OutlineWidthMode; + + public float? OutlineWidthFactor; + + public int? OutlineWidthMultiplyTexture; + + public float? OutlineScaledMaxDistanceFactor; + + [JsonSchema(EnumSerializationType = EnumSerializationType.AsString)] + public OutlineColorMode OutlineColorMode; + + public float[] OutlineFactor; + + public float? OutlineLightingMixFactor; + + public int? UvAnimationMaskTexture; + + public float? UvAnimationScrollXSpeedFactor; + + public float? UvAnimationScrollYSpeedFactor; + + public float? UvAnimationRotationSpeedFactor; + } +} diff --git a/Assets/VRM10/Runtime/Format/MaterialsMToon/Format.g.cs.meta b/Assets/VRM10/Runtime/Format/MaterialsMToon/Format.g.cs.meta new file mode 100644 index 000000000..afa8ef7fa --- /dev/null +++ b/Assets/VRM10/Runtime/Format/MaterialsMToon/Format.g.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 95a156592f2b7fc47b1b6cc557d1df4b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Format/MaterialsMToon/Serializer.g.cs b/Assets/VRM10/Runtime/Format/MaterialsMToon/Serializer.g.cs new file mode 100644 index 000000000..33ad2640a --- /dev/null +++ b/Assets/VRM10/Runtime/Format/MaterialsMToon/Serializer.g.cs @@ -0,0 +1,206 @@ +// This file is generated from JsonSchema. Don't modify this source code. +using System; +using System.Collections.Generic; +using System.Linq; +using UniJSON; + +namespace UniGLTF.Extensions.VRMC_materials_mtoon { + + static public class GltfSerializer + { + + public static void SerializeTo(ref UniGLTF.glTFExtension dst, VRMC_materials_mtoon extension) + { + if (dst is glTFExtensionImport) + { + throw new NotImplementedException(); + } + + if (!(dst is glTFExtensionExport extensions)) + { + extensions = new glTFExtensionExport(); + dst = extensions; + } + + var f = new JsonFormatter(); + Serialize(f, extension); + extensions.Add(VRMC_materials_mtoon.ExtensionName, f.GetStoreBytes()); + } + + +public static void Serialize(JsonFormatter f, VRMC_materials_mtoon value) +{ + f.BeginMap(); + + + if(!string.IsNullOrEmpty(value.Version)){ + f.Key("version"); + f.Value(value.Version); + } + + if(value.TransparentWithZWrite.HasValue){ + f.Key("transparentWithZWrite"); + f.Value(value.TransparentWithZWrite.GetValueOrDefault()); + } + + if(value.RenderQueueOffsetNumber.HasValue){ + f.Key("renderQueueOffsetNumber"); + f.Value(value.RenderQueueOffsetNumber.GetValueOrDefault()); + } + + if(value.ShadeFactor!=null&&value.ShadeFactor.Count()>=0){ + f.Key("shadeFactor"); + Serialize_ShadeFactor(f, value.ShadeFactor); + } + + if(value.ShadeMultiplyTexture.HasValue){ + f.Key("shadeMultiplyTexture"); + f.Value(value.ShadeMultiplyTexture.GetValueOrDefault()); + } + + if(value.ShadingShiftFactor.HasValue){ + f.Key("shadingShiftFactor"); + f.Value(value.ShadingShiftFactor.GetValueOrDefault()); + } + + if(value.ShadingToonyFactor.HasValue){ + f.Key("shadingToonyFactor"); + f.Value(value.ShadingToonyFactor.GetValueOrDefault()); + } + + if(value.LightColorAttenuationFactor.HasValue){ + f.Key("lightColorAttenuationFactor"); + f.Value(value.LightColorAttenuationFactor.GetValueOrDefault()); + } + + if(value.GiIntensityFactor.HasValue){ + f.Key("giIntensityFactor"); + f.Value(value.GiIntensityFactor.GetValueOrDefault()); + } + + if(value.AdditiveTexture.HasValue){ + f.Key("additiveTexture"); + f.Value(value.AdditiveTexture.GetValueOrDefault()); + } + + if(value.RimFactor!=null&&value.RimFactor.Count()>=0){ + f.Key("rimFactor"); + Serialize_RimFactor(f, value.RimFactor); + } + + if(value.RimMultiplyTexture.HasValue){ + f.Key("rimMultiplyTexture"); + f.Value(value.RimMultiplyTexture.GetValueOrDefault()); + } + + if(value.RimLightingMixFactor.HasValue){ + f.Key("rimLightingMixFactor"); + f.Value(value.RimLightingMixFactor.GetValueOrDefault()); + } + + if(value.RimFresnelPowerFactor.HasValue){ + f.Key("rimFresnelPowerFactor"); + f.Value(value.RimFresnelPowerFactor.GetValueOrDefault()); + } + + if(value.RimLiftFactor.HasValue){ + f.Key("rimLiftFactor"); + f.Value(value.RimLiftFactor.GetValueOrDefault()); + } + + if(true){ + f.Key("outlineWidthMode"); + f.Value(value.OutlineWidthMode.ToString()); + } + + if(value.OutlineWidthFactor.HasValue){ + f.Key("outlineWidthFactor"); + f.Value(value.OutlineWidthFactor.GetValueOrDefault()); + } + + if(value.OutlineWidthMultiplyTexture.HasValue){ + f.Key("outlineWidthMultiplyTexture"); + f.Value(value.OutlineWidthMultiplyTexture.GetValueOrDefault()); + } + + if(value.OutlineScaledMaxDistanceFactor.HasValue){ + f.Key("outlineScaledMaxDistanceFactor"); + f.Value(value.OutlineScaledMaxDistanceFactor.GetValueOrDefault()); + } + + if(true){ + f.Key("outlineColorMode"); + f.Value(value.OutlineColorMode.ToString()); + } + + if(value.OutlineFactor!=null&&value.OutlineFactor.Count()>=0){ + f.Key("outlineFactor"); + Serialize_OutlineFactor(f, value.OutlineFactor); + } + + if(value.OutlineLightingMixFactor.HasValue){ + f.Key("outlineLightingMixFactor"); + f.Value(value.OutlineLightingMixFactor.GetValueOrDefault()); + } + + if(value.UvAnimationMaskTexture.HasValue){ + f.Key("uvAnimationMaskTexture"); + f.Value(value.UvAnimationMaskTexture.GetValueOrDefault()); + } + + if(value.UvAnimationScrollXSpeedFactor.HasValue){ + f.Key("uvAnimationScrollXSpeedFactor"); + f.Value(value.UvAnimationScrollXSpeedFactor.GetValueOrDefault()); + } + + if(value.UvAnimationScrollYSpeedFactor.HasValue){ + f.Key("uvAnimationScrollYSpeedFactor"); + f.Value(value.UvAnimationScrollYSpeedFactor.GetValueOrDefault()); + } + + if(value.UvAnimationRotationSpeedFactor.HasValue){ + f.Key("uvAnimationRotationSpeedFactor"); + f.Value(value.UvAnimationRotationSpeedFactor.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_ShadeFactor(JsonFormatter f, float[] value) +{ + f.BeginList(); + + foreach(var item in value) + { + f.Value(item); + + } + f.EndList(); +} + +public static void Serialize_RimFactor(JsonFormatter f, float[] value) +{ + f.BeginList(); + + foreach(var item in value) + { + f.Value(item); + + } + f.EndList(); +} + +public static void Serialize_OutlineFactor(JsonFormatter f, float[] value) +{ + f.BeginList(); + + foreach(var item in value) + { + f.Value(item); + + } + f.EndList(); +} + + } // class +} // namespace diff --git a/Assets/VRM10/Runtime/Format/MaterialsMToon/Serializer.g.cs.meta b/Assets/VRM10/Runtime/Format/MaterialsMToon/Serializer.g.cs.meta new file mode 100644 index 000000000..87212bea6 --- /dev/null +++ b/Assets/VRM10/Runtime/Format/MaterialsMToon/Serializer.g.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 88f0964f72d8de44983c4ed691f41558 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Format/NodeCollider.meta b/Assets/VRM10/Runtime/Format/NodeCollider.meta new file mode 100644 index 000000000..c5aa25fd7 --- /dev/null +++ b/Assets/VRM10/Runtime/Format/NodeCollider.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 57de298702679f949a2f2658ad186d4a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Format/NodeCollider/Deserializer.g.cs b/Assets/VRM10/Runtime/Format/NodeCollider/Deserializer.g.cs new file mode 100644 index 000000000..0eba7de28 --- /dev/null +++ b/Assets/VRM10/Runtime/Format/NodeCollider/Deserializer.g.cs @@ -0,0 +1,152 @@ +// This file is generated from JsonSchema. Don't modify this source code. +using UniJSON; +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UniGLTF.Extensions.VRMC_node_collider { + +public static class GltfDeserializer +{ + +public static bool TryGet(UniGLTF.glTFExtension src, out VRMC_node_collider extension) +{ + if(src is UniGLTF.glTFExtensionImport extensions) + { + foreach(var kv in extensions.ObjectItems()) + { + if(kv.Key.GetUtf8String() == VRMC_node_collider.ExtensionNameUtf8) + { + extension = Deserialize(kv.Value); + return true; + } + } + } + + extension = default; + return false; +} + + +public static VRMC_node_collider Deserialize(ListTreeNode parsed) +{ + var value = new VRMC_node_collider(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="shapes"){ + value.Shapes = Deserialize_Shapes(kv.Value); + continue; + } + + } + return value; +} + +public static List Deserialize_Shapes(ListTreeNode parsed) +{ + var value = new List(); + foreach(var x in parsed.ArrayItems()) + { + value.Add(Deserialize_Shapes_ITEM(x)); + } + return value; +} + +public static ColliderShape Deserialize_Shapes_ITEM(ListTreeNode parsed) +{ + var value = new ColliderShape(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="sphere"){ + value.Sphere = Deserialize_Sphere(kv.Value); + continue; + } + + if(key=="capsule"){ + value.Capsule = Deserialize_Capsule(kv.Value); + continue; + } + + } + return value; +} + +public static ColliderShapeSphere Deserialize_Sphere(ListTreeNode parsed) +{ + var value = new ColliderShapeSphere(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="offset"){ + value.Offset = Deserialize_Offset(kv.Value); + continue; + } + + if(key=="radius"){ + value.Radius = kv.Value.GetSingle(); + continue; + } + + } + return value; +} + +public static float[] Deserialize_Offset(ListTreeNode parsed) +{ + var value = new float[parsed.GetArrayCount()]; + int i=0; + foreach(var x in parsed.ArrayItems()) + { + value[i++] = x.GetSingle(); + } + return value; +} + +public static ColliderShapeCapsule Deserialize_Capsule(ListTreeNode parsed) +{ + var value = new ColliderShapeCapsule(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="offset"){ + value.Offset = Deserialize_Offset(kv.Value); + continue; + } + + if(key=="radius"){ + value.Radius = kv.Value.GetSingle(); + continue; + } + + if(key=="tail"){ + value.Tail = Deserialize_Tail(kv.Value); + continue; + } + + } + return value; +} + +public static float[] Deserialize_Tail(ListTreeNode parsed) +{ + var value = new float[parsed.GetArrayCount()]; + int i=0; + foreach(var x in parsed.ArrayItems()) + { + value[i++] = x.GetSingle(); + } + return value; +} + +} // GltfDeserializer +} // UniGLTF diff --git a/Assets/VRM10/Runtime/Format/NodeCollider/Deserializer.g.cs.meta b/Assets/VRM10/Runtime/Format/NodeCollider/Deserializer.g.cs.meta new file mode 100644 index 000000000..8d5235f1b --- /dev/null +++ b/Assets/VRM10/Runtime/Format/NodeCollider/Deserializer.g.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 162032f06e7a71549bb767d4aad3d221 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Format/NodeCollider/Format.g.cs b/Assets/VRM10/Runtime/Format/NodeCollider/Format.g.cs new file mode 100644 index 000000000..5512bc9ab --- /dev/null +++ b/Assets/VRM10/Runtime/Format/NodeCollider/Format.g.cs @@ -0,0 +1,45 @@ +// This file is generated from JsonSchema. Don't modify this source code. +using System; +using System.Collections.Generic; +using UniGLTF; +using UniJSON; + +namespace UniGLTF.Extensions.VRMC_node_collider +{ + + public class ColliderShapeSphere + { + // The sphere center. vector3 + public float[] Offset; + + // The sphere radius + public float? Radius; + } + + public class ColliderShapeCapsule + { + // The capsule head. vector3 + public float[] Offset; + + // The capsule radius + public float? Radius; + + // The capsule tail. vector3 + public float[] Tail; + } + + public class ColliderShape + { + public ColliderShapeSphere Sphere; + + public ColliderShapeCapsule Capsule; + } + + public class VRMC_node_collider + { + public const string ExtensionName = "VRMC_node_collider"; + public static readonly Utf8String ExtensionNameUtf8 = Utf8String.From(ExtensionName); + + public List Shapes; + } +} diff --git a/Assets/VRM10/Runtime/Format/NodeCollider/Format.g.cs.meta b/Assets/VRM10/Runtime/Format/NodeCollider/Format.g.cs.meta new file mode 100644 index 000000000..065d2245e --- /dev/null +++ b/Assets/VRM10/Runtime/Format/NodeCollider/Format.g.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b1f73d7cedc49cb4d9513f7a0a9ae9b6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Format/NodeCollider/Serializer.g.cs b/Assets/VRM10/Runtime/Format/NodeCollider/Serializer.g.cs new file mode 100644 index 000000000..298c122bd --- /dev/null +++ b/Assets/VRM10/Runtime/Format/NodeCollider/Serializer.g.cs @@ -0,0 +1,140 @@ +// This file is generated from JsonSchema. Don't modify this source code. +using System; +using System.Collections.Generic; +using System.Linq; +using UniJSON; + +namespace UniGLTF.Extensions.VRMC_node_collider { + + static public class GltfSerializer + { + + public static void SerializeTo(ref UniGLTF.glTFExtension dst, VRMC_node_collider extension) + { + if (dst is glTFExtensionImport) + { + throw new NotImplementedException(); + } + + if (!(dst is glTFExtensionExport extensions)) + { + extensions = new glTFExtensionExport(); + dst = extensions; + } + + var f = new JsonFormatter(); + Serialize(f, extension); + extensions.Add(VRMC_node_collider.ExtensionName, f.GetStoreBytes()); + } + + +public static void Serialize(JsonFormatter f, VRMC_node_collider value) +{ + f.BeginMap(); + + + if(value.Shapes!=null&&value.Shapes.Count()>=0){ + f.Key("shapes"); + Serialize_Shapes(f, value.Shapes); + } + + f.EndMap(); +} + +public static void Serialize_Shapes(JsonFormatter f, List value) +{ + f.BeginList(); + + foreach(var item in value) + { + Serialize_Shapes_ITEM(f, item); + + } + f.EndList(); +} + +public static void Serialize_Shapes_ITEM(JsonFormatter f, ColliderShape value) +{ + f.BeginMap(); + + + if(value.Sphere!=null){ + f.Key("sphere"); + Serialize_Sphere(f, value.Sphere); + } + + if(value.Capsule!=null){ + f.Key("capsule"); + Serialize_Capsule(f, value.Capsule); + } + + f.EndMap(); +} + +public static void Serialize_Sphere(JsonFormatter f, ColliderShapeSphere value) +{ + f.BeginMap(); + + + if(value.Offset!=null&&value.Offset.Count()>=3){ + f.Key("offset"); + Serialize_Offset(f, value.Offset); + } + + if(value.Radius.HasValue){ + f.Key("radius"); + f.Value(value.Radius.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_Offset(JsonFormatter f, float[] value) +{ + f.BeginList(); + + foreach(var item in value) + { + f.Value(item); + + } + f.EndList(); +} + +public static void Serialize_Capsule(JsonFormatter f, ColliderShapeCapsule value) +{ + f.BeginMap(); + + + if(value.Offset!=null&&value.Offset.Count()>=3){ + f.Key("offset"); + Serialize_Offset(f, value.Offset); + } + + if(value.Radius.HasValue){ + f.Key("radius"); + f.Value(value.Radius.GetValueOrDefault()); + } + + if(value.Tail!=null&&value.Tail.Count()>=3){ + f.Key("tail"); + Serialize_Tail(f, value.Tail); + } + + f.EndMap(); +} + +public static void Serialize_Tail(JsonFormatter f, float[] value) +{ + f.BeginList(); + + foreach(var item in value) + { + f.Value(item); + + } + f.EndList(); +} + + } // class +} // namespace diff --git a/Assets/VRM10/Runtime/Format/NodeCollider/Serializer.g.cs.meta b/Assets/VRM10/Runtime/Format/NodeCollider/Serializer.g.cs.meta new file mode 100644 index 000000000..2d2f3390b --- /dev/null +++ b/Assets/VRM10/Runtime/Format/NodeCollider/Serializer.g.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8eb6c9672b71ea147867c73cb8d58a35 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Format/SpringBone.meta b/Assets/VRM10/Runtime/Format/SpringBone.meta new file mode 100644 index 000000000..557e328df --- /dev/null +++ b/Assets/VRM10/Runtime/Format/SpringBone.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a9d1c8d206a79724aa4a6f6ea4957c2a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Format/SpringBone/Deserializer.g.cs b/Assets/VRM10/Runtime/Format/SpringBone/Deserializer.g.cs new file mode 100644 index 000000000..5a363bb61 --- /dev/null +++ b/Assets/VRM10/Runtime/Format/SpringBone/Deserializer.g.cs @@ -0,0 +1,165 @@ +// This file is generated from JsonSchema. Don't modify this source code. +using UniJSON; +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UniGLTF.Extensions.VRMC_springBone { + +public static class GltfDeserializer +{ + +public static bool TryGet(UniGLTF.glTFExtension src, out VRMC_springBone extension) +{ + if(src is UniGLTF.glTFExtensionImport extensions) + { + foreach(var kv in extensions.ObjectItems()) + { + if(kv.Key.GetUtf8String() == VRMC_springBone.ExtensionNameUtf8) + { + extension = Deserialize(kv.Value); + return true; + } + } + } + + extension = default; + return false; +} + + +public static VRMC_springBone Deserialize(ListTreeNode parsed) +{ + var value = new VRMC_springBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="settings"){ + value.Settings = Deserialize_Settings(kv.Value); + continue; + } + + if(key=="springs"){ + value.Springs = Deserialize_Springs(kv.Value); + continue; + } + + } + return value; +} + +public static List Deserialize_Settings(ListTreeNode parsed) +{ + var value = new List(); + foreach(var x in parsed.ArrayItems()) + { + value.Add(Deserialize_Settings_ITEM(x)); + } + return value; +} + +public static SpringSetting Deserialize_Settings_ITEM(ListTreeNode parsed) +{ + var value = new SpringSetting(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="stiffness"){ + value.Stiffness = kv.Value.GetSingle(); + continue; + } + + if(key=="gravityPower"){ + value.GravityPower = kv.Value.GetSingle(); + continue; + } + + if(key=="gravityDir"){ + value.GravityDir = Deserialize_GravityDir(kv.Value); + continue; + } + + if(key=="dragForce"){ + value.DragForce = kv.Value.GetSingle(); + continue; + } + + } + return value; +} + +public static float[] Deserialize_GravityDir(ListTreeNode parsed) +{ + var value = new float[parsed.GetArrayCount()]; + int i=0; + foreach(var x in parsed.ArrayItems()) + { + value[i++] = x.GetSingle(); + } + return value; +} + +public static List Deserialize_Springs(ListTreeNode parsed) +{ + var value = new List(); + foreach(var x in parsed.ArrayItems()) + { + value.Add(Deserialize_Springs_ITEM(x)); + } + return value; +} + +public static Spring Deserialize_Springs_ITEM(ListTreeNode parsed) +{ + var value = new Spring(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="name"){ + value.Name = kv.Value.GetString(); + continue; + } + + if(key=="setting"){ + value.Setting = kv.Value.GetInt32(); + continue; + } + + if(key=="springRoot"){ + value.SpringRoot = kv.Value.GetInt32(); + continue; + } + + if(key=="hitRadius"){ + value.HitRadius = kv.Value.GetSingle(); + continue; + } + + if(key=="colliders"){ + value.Colliders = Deserialize_Colliders(kv.Value); + continue; + } + + } + return value; +} + +public static int[] Deserialize_Colliders(ListTreeNode parsed) +{ + var value = new int[parsed.GetArrayCount()]; + int i=0; + foreach(var x in parsed.ArrayItems()) + { + value[i++] = x.GetInt32(); + } + return value; +} + +} // GltfDeserializer +} // UniGLTF diff --git a/Assets/VRM10/Runtime/Format/SpringBone/Deserializer.g.cs.meta b/Assets/VRM10/Runtime/Format/SpringBone/Deserializer.g.cs.meta new file mode 100644 index 000000000..f382cb416 --- /dev/null +++ b/Assets/VRM10/Runtime/Format/SpringBone/Deserializer.g.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1e0fa030990ac4146afcf75a0ef81e33 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Format/SpringBone/Format.g.cs b/Assets/VRM10/Runtime/Format/SpringBone/Format.g.cs new file mode 100644 index 000000000..9510b42e8 --- /dev/null +++ b/Assets/VRM10/Runtime/Format/SpringBone/Format.g.cs @@ -0,0 +1,54 @@ +// This file is generated from JsonSchema. Don't modify this source code. +using System; +using System.Collections.Generic; +using UniGLTF; +using UniJSON; + +namespace UniGLTF.Extensions.VRMC_springBone +{ + + public class SpringSetting + { + // The force to return to the initial pose + public float? Stiffness; + + // Gravitational acceleration + public float? GravityPower; + + // The direction of gravity. A gravity other than downward direction also works. + public float[] GravityDir; + + // Air resistance. Deceleration force + public float? DragForce; + } + + public class Spring + { + // Name of the Spring + public string Name; + + // The index of spring settings + public int? Setting; + + // The node index of spring root + public int? SpringRoot; + + // The radius of spring sphere + public float? HitRadius; + + // Colliders that detect collision with nodes start from springRoot + public int[] Colliders; + } + + public class VRMC_springBone + { + public const string ExtensionName = "VRMC_springBone"; + public static readonly Utf8String ExtensionNameUtf8 = Utf8String.From(ExtensionName); + + // An array of settings. + public List Settings; + + // An array of springs. + public List Springs; + } +} diff --git a/Assets/VRM10/Runtime/Format/SpringBone/Format.g.cs.meta b/Assets/VRM10/Runtime/Format/SpringBone/Format.g.cs.meta new file mode 100644 index 000000000..b5fa59d57 --- /dev/null +++ b/Assets/VRM10/Runtime/Format/SpringBone/Format.g.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6bcc6164cd229c942b95c80f002df2be +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Format/SpringBone/Serializer.g.cs b/Assets/VRM10/Runtime/Format/SpringBone/Serializer.g.cs new file mode 100644 index 000000000..b033f9b26 --- /dev/null +++ b/Assets/VRM10/Runtime/Format/SpringBone/Serializer.g.cs @@ -0,0 +1,159 @@ +// This file is generated from JsonSchema. Don't modify this source code. +using System; +using System.Collections.Generic; +using System.Linq; +using UniJSON; + +namespace UniGLTF.Extensions.VRMC_springBone { + + static public class GltfSerializer + { + + public static void SerializeTo(ref UniGLTF.glTFExtension dst, VRMC_springBone extension) + { + if (dst is glTFExtensionImport) + { + throw new NotImplementedException(); + } + + if (!(dst is glTFExtensionExport extensions)) + { + extensions = new glTFExtensionExport(); + dst = extensions; + } + + var f = new JsonFormatter(); + Serialize(f, extension); + extensions.Add(VRMC_springBone.ExtensionName, f.GetStoreBytes()); + } + + +public static void Serialize(JsonFormatter f, VRMC_springBone value) +{ + f.BeginMap(); + + + if(value.Settings!=null&&value.Settings.Count()>=0){ + f.Key("settings"); + Serialize_Settings(f, value.Settings); + } + + if(value.Springs!=null&&value.Springs.Count()>=0){ + f.Key("springs"); + Serialize_Springs(f, value.Springs); + } + + f.EndMap(); +} + +public static void Serialize_Settings(JsonFormatter f, List value) +{ + f.BeginList(); + + foreach(var item in value) + { + Serialize_Settings_ITEM(f, item); + + } + f.EndList(); +} + +public static void Serialize_Settings_ITEM(JsonFormatter f, SpringSetting value) +{ + f.BeginMap(); + + + if(value.Stiffness.HasValue){ + f.Key("stiffness"); + f.Value(value.Stiffness.GetValueOrDefault()); + } + + if(value.GravityPower.HasValue){ + f.Key("gravityPower"); + f.Value(value.GravityPower.GetValueOrDefault()); + } + + if(value.GravityDir!=null&&value.GravityDir.Count()>=3){ + f.Key("gravityDir"); + Serialize_GravityDir(f, value.GravityDir); + } + + if(value.DragForce.HasValue){ + f.Key("dragForce"); + f.Value(value.DragForce.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_GravityDir(JsonFormatter f, float[] value) +{ + f.BeginList(); + + foreach(var item in value) + { + f.Value(item); + + } + f.EndList(); +} + +public static void Serialize_Springs(JsonFormatter f, List value) +{ + f.BeginList(); + + foreach(var item in value) + { + Serialize_Springs_ITEM(f, item); + + } + f.EndList(); +} + +public static void Serialize_Springs_ITEM(JsonFormatter f, Spring value) +{ + f.BeginMap(); + + + if(!string.IsNullOrEmpty(value.Name)){ + f.Key("name"); + f.Value(value.Name); + } + + if(value.Setting.HasValue){ + f.Key("setting"); + f.Value(value.Setting.GetValueOrDefault()); + } + + if(value.SpringRoot.HasValue){ + f.Key("springRoot"); + f.Value(value.SpringRoot.GetValueOrDefault()); + } + + if(value.HitRadius.HasValue){ + f.Key("hitRadius"); + f.Value(value.HitRadius.GetValueOrDefault()); + } + + if(value.Colliders!=null&&value.Colliders.Count()>=0){ + f.Key("colliders"); + Serialize_Colliders(f, value.Colliders); + } + + f.EndMap(); +} + +public static void Serialize_Colliders(JsonFormatter f, int[] value) +{ + f.BeginList(); + + foreach(var item in value) + { + f.Value(item); + + } + f.EndList(); +} + + } // class +} // namespace diff --git a/Assets/VRM10/Runtime/Format/SpringBone/Serializer.g.cs.meta b/Assets/VRM10/Runtime/Format/SpringBone/Serializer.g.cs.meta new file mode 100644 index 000000000..0035cfc07 --- /dev/null +++ b/Assets/VRM10/Runtime/Format/SpringBone/Serializer.g.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a7061394db043f8468755f4d1f3b92f3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Format/Vrm.meta b/Assets/VRM10/Runtime/Format/Vrm.meta new file mode 100644 index 000000000..528ca25d3 --- /dev/null +++ b/Assets/VRM10/Runtime/Format/Vrm.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6e411dd959d9da04fa4fc738a688b709 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Format/Vrm/Deserializer.g.cs b/Assets/VRM10/Runtime/Format/Vrm/Deserializer.g.cs new file mode 100644 index 000000000..db4400a59 --- /dev/null +++ b/Assets/VRM10/Runtime/Format/Vrm/Deserializer.g.cs @@ -0,0 +1,1871 @@ +// This file is generated from JsonSchema. Don't modify this source code. +using UniJSON; +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UniGLTF.Extensions.VRMC_vrm { + +public static class GltfDeserializer +{ + +public static bool TryGet(UniGLTF.glTFExtension src, out VRMC_vrm extension) +{ + if(src is UniGLTF.glTFExtensionImport extensions) + { + foreach(var kv in extensions.ObjectItems()) + { + if(kv.Key.GetUtf8String() == VRMC_vrm.ExtensionNameUtf8) + { + extension = Deserialize(kv.Value); + return true; + } + } + } + + extension = default; + return false; +} + + +public static VRMC_vrm Deserialize(ListTreeNode parsed) +{ + var value = new VRMC_vrm(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="specVersion"){ + value.SpecVersion = kv.Value.GetString(); + continue; + } + + if(key=="meta"){ + value.Meta = Deserialize_Meta(kv.Value); + continue; + } + + if(key=="humanoid"){ + value.Humanoid = Deserialize_Humanoid(kv.Value); + continue; + } + + if(key=="firstPerson"){ + value.FirstPerson = Deserialize_FirstPerson(kv.Value); + continue; + } + + if(key=="lookAt"){ + value.LookAt = Deserialize_LookAt(kv.Value); + continue; + } + + if(key=="expressions"){ + value.Expressions = Deserialize_Expressions(kv.Value); + continue; + } + + } + return value; +} + +public static Meta Deserialize_Meta(ListTreeNode parsed) +{ + var value = new Meta(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="name"){ + value.Name = kv.Value.GetString(); + continue; + } + + if(key=="version"){ + value.Version = kv.Value.GetString(); + continue; + } + + if(key=="authors"){ + value.Authors = Deserialize_Authors(kv.Value); + continue; + } + + if(key=="copyrightInformation"){ + value.CopyrightInformation = kv.Value.GetString(); + continue; + } + + if(key=="contactInformation"){ + value.ContactInformation = kv.Value.GetString(); + continue; + } + + if(key=="references"){ + value.References = Deserialize_References(kv.Value); + continue; + } + + if(key=="thirdPartyLicenses"){ + value.ThirdPartyLicenses = kv.Value.GetString(); + continue; + } + + if(key=="thumbnailImage"){ + value.ThumbnailImage = kv.Value.GetInt32(); + continue; + } + + if(key=="avatarPermission"){ + value.AvatarPermission = (AvatarPermissionType)Enum.Parse(typeof(AvatarPermissionType), kv.Value.GetString(), true); + continue; + } + + if(key=="allowExcessivelyViolentUsage"){ + value.AllowExcessivelyViolentUsage = kv.Value.GetBoolean(); + continue; + } + + if(key=="allowExcessivelySexualUsage"){ + value.AllowExcessivelySexualUsage = kv.Value.GetBoolean(); + continue; + } + + if(key=="commercialUsage"){ + value.CommercialUsage = (CommercialUsageType)Enum.Parse(typeof(CommercialUsageType), kv.Value.GetString(), true); + continue; + } + + if(key=="allowPoliticalOrReligiousUsage"){ + value.AllowPoliticalOrReligiousUsage = kv.Value.GetBoolean(); + continue; + } + + if(key=="creditNotation"){ + value.CreditNotation = (CreditNotationType)Enum.Parse(typeof(CreditNotationType), kv.Value.GetString(), true); + continue; + } + + if(key=="allowRedistribution"){ + value.AllowRedistribution = kv.Value.GetBoolean(); + continue; + } + + if(key=="modification"){ + value.Modification = (ModificationType)Enum.Parse(typeof(ModificationType), kv.Value.GetString(), true); + continue; + } + + if(key=="otherLicenseUrl"){ + value.OtherLicenseUrl = kv.Value.GetString(); + continue; + } + + } + return value; +} + +public static List Deserialize_Authors(ListTreeNode parsed) +{ + var value = new List(); + foreach(var x in parsed.ArrayItems()) + { + value.Add(x.GetString()); + } + return value; +} + +public static List Deserialize_References(ListTreeNode parsed) +{ + var value = new List(); + foreach(var x in parsed.ArrayItems()) + { + value.Add(x.GetString()); + } + return value; +} + +public static Humanoid Deserialize_Humanoid(ListTreeNode parsed) +{ + var value = new Humanoid(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="humanBones"){ + value.HumanBones = Deserialize_HumanBones(kv.Value); + continue; + } + + } + return value; +} + +public static HumanBones Deserialize_HumanBones(ListTreeNode parsed) +{ + var value = new HumanBones(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="hips"){ + value.Hips = Deserialize_Hips(kv.Value); + continue; + } + + if(key=="spine"){ + value.Spine = Deserialize_Spine(kv.Value); + continue; + } + + if(key=="chest"){ + value.Chest = Deserialize_Chest(kv.Value); + continue; + } + + if(key=="upperChest"){ + value.UpperChest = Deserialize_UpperChest(kv.Value); + continue; + } + + if(key=="neck"){ + value.Neck = Deserialize_Neck(kv.Value); + continue; + } + + if(key=="head"){ + value.Head = Deserialize_Head(kv.Value); + continue; + } + + if(key=="leftEye"){ + value.LeftEye = Deserialize_LeftEye(kv.Value); + continue; + } + + if(key=="rightEye"){ + value.RightEye = Deserialize_RightEye(kv.Value); + continue; + } + + if(key=="jaw"){ + value.Jaw = Deserialize_Jaw(kv.Value); + continue; + } + + if(key=="leftUpperLeg"){ + value.LeftUpperLeg = Deserialize_LeftUpperLeg(kv.Value); + continue; + } + + if(key=="leftLowerLeg"){ + value.LeftLowerLeg = Deserialize_LeftLowerLeg(kv.Value); + continue; + } + + if(key=="leftFoot"){ + value.LeftFoot = Deserialize_LeftFoot(kv.Value); + continue; + } + + if(key=="leftToes"){ + value.LeftToes = Deserialize_LeftToes(kv.Value); + continue; + } + + if(key=="rightUpperLeg"){ + value.RightUpperLeg = Deserialize_RightUpperLeg(kv.Value); + continue; + } + + if(key=="rightLowerLeg"){ + value.RightLowerLeg = Deserialize_RightLowerLeg(kv.Value); + continue; + } + + if(key=="rightFoot"){ + value.RightFoot = Deserialize_RightFoot(kv.Value); + continue; + } + + if(key=="rightToes"){ + value.RightToes = Deserialize_RightToes(kv.Value); + continue; + } + + if(key=="leftShoulder"){ + value.LeftShoulder = Deserialize_LeftShoulder(kv.Value); + continue; + } + + if(key=="leftUpperArm"){ + value.LeftUpperArm = Deserialize_LeftUpperArm(kv.Value); + continue; + } + + if(key=="leftLowerArm"){ + value.LeftLowerArm = Deserialize_LeftLowerArm(kv.Value); + continue; + } + + if(key=="leftHand"){ + value.LeftHand = Deserialize_LeftHand(kv.Value); + continue; + } + + if(key=="rightShoulder"){ + value.RightShoulder = Deserialize_RightShoulder(kv.Value); + continue; + } + + if(key=="rightUpperArm"){ + value.RightUpperArm = Deserialize_RightUpperArm(kv.Value); + continue; + } + + if(key=="rightLowerArm"){ + value.RightLowerArm = Deserialize_RightLowerArm(kv.Value); + continue; + } + + if(key=="rightHand"){ + value.RightHand = Deserialize_RightHand(kv.Value); + continue; + } + + if(key=="leftThumbProximal"){ + value.LeftThumbProximal = Deserialize_LeftThumbProximal(kv.Value); + continue; + } + + if(key=="leftThumbIntermediate"){ + value.LeftThumbIntermediate = Deserialize_LeftThumbIntermediate(kv.Value); + continue; + } + + if(key=="leftThumbDistal"){ + value.LeftThumbDistal = Deserialize_LeftThumbDistal(kv.Value); + continue; + } + + if(key=="leftIndexProximal"){ + value.LeftIndexProximal = Deserialize_LeftIndexProximal(kv.Value); + continue; + } + + if(key=="leftIndexIntermediate"){ + value.LeftIndexIntermediate = Deserialize_LeftIndexIntermediate(kv.Value); + continue; + } + + if(key=="leftIndexDistal"){ + value.LeftIndexDistal = Deserialize_LeftIndexDistal(kv.Value); + continue; + } + + if(key=="leftMiddleProximal"){ + value.LeftMiddleProximal = Deserialize_LeftMiddleProximal(kv.Value); + continue; + } + + if(key=="leftMiddleIntermediate"){ + value.LeftMiddleIntermediate = Deserialize_LeftMiddleIntermediate(kv.Value); + continue; + } + + if(key=="leftMiddleDistal"){ + value.LeftMiddleDistal = Deserialize_LeftMiddleDistal(kv.Value); + continue; + } + + if(key=="leftRingProximal"){ + value.LeftRingProximal = Deserialize_LeftRingProximal(kv.Value); + continue; + } + + if(key=="leftRingIntermediate"){ + value.LeftRingIntermediate = Deserialize_LeftRingIntermediate(kv.Value); + continue; + } + + if(key=="leftRingDistal"){ + value.LeftRingDistal = Deserialize_LeftRingDistal(kv.Value); + continue; + } + + if(key=="leftLittleProximal"){ + value.LeftLittleProximal = Deserialize_LeftLittleProximal(kv.Value); + continue; + } + + if(key=="leftLittleIntermediate"){ + value.LeftLittleIntermediate = Deserialize_LeftLittleIntermediate(kv.Value); + continue; + } + + if(key=="leftLittleDistal"){ + value.LeftLittleDistal = Deserialize_LeftLittleDistal(kv.Value); + continue; + } + + if(key=="rightThumbProximal"){ + value.RightThumbProximal = Deserialize_RightThumbProximal(kv.Value); + continue; + } + + if(key=="rightThumbIntermediate"){ + value.RightThumbIntermediate = Deserialize_RightThumbIntermediate(kv.Value); + continue; + } + + if(key=="rightThumbDistal"){ + value.RightThumbDistal = Deserialize_RightThumbDistal(kv.Value); + continue; + } + + if(key=="rightIndexProximal"){ + value.RightIndexProximal = Deserialize_RightIndexProximal(kv.Value); + continue; + } + + if(key=="rightIndexIntermediate"){ + value.RightIndexIntermediate = Deserialize_RightIndexIntermediate(kv.Value); + continue; + } + + if(key=="rightIndexDistal"){ + value.RightIndexDistal = Deserialize_RightIndexDistal(kv.Value); + continue; + } + + if(key=="rightMiddleProximal"){ + value.RightMiddleProximal = Deserialize_RightMiddleProximal(kv.Value); + continue; + } + + if(key=="rightMiddleIntermediate"){ + value.RightMiddleIntermediate = Deserialize_RightMiddleIntermediate(kv.Value); + continue; + } + + if(key=="rightMiddleDistal"){ + value.RightMiddleDistal = Deserialize_RightMiddleDistal(kv.Value); + continue; + } + + if(key=="rightRingProximal"){ + value.RightRingProximal = Deserialize_RightRingProximal(kv.Value); + continue; + } + + if(key=="rightRingIntermediate"){ + value.RightRingIntermediate = Deserialize_RightRingIntermediate(kv.Value); + continue; + } + + if(key=="rightRingDistal"){ + value.RightRingDistal = Deserialize_RightRingDistal(kv.Value); + continue; + } + + if(key=="rightLittleProximal"){ + value.RightLittleProximal = Deserialize_RightLittleProximal(kv.Value); + continue; + } + + if(key=="rightLittleIntermediate"){ + value.RightLittleIntermediate = Deserialize_RightLittleIntermediate(kv.Value); + continue; + } + + if(key=="rightLittleDistal"){ + value.RightLittleDistal = Deserialize_RightLittleDistal(kv.Value); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_Hips(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_Spine(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_Chest(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_UpperChest(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_Neck(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_Head(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_LeftEye(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_RightEye(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_Jaw(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_LeftUpperLeg(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_LeftLowerLeg(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_LeftFoot(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_LeftToes(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_RightUpperLeg(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_RightLowerLeg(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_RightFoot(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_RightToes(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_LeftShoulder(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_LeftUpperArm(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_LeftLowerArm(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_LeftHand(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_RightShoulder(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_RightUpperArm(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_RightLowerArm(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_RightHand(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_LeftThumbProximal(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_LeftThumbIntermediate(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_LeftThumbDistal(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_LeftIndexProximal(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_LeftIndexIntermediate(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_LeftIndexDistal(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_LeftMiddleProximal(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_LeftMiddleIntermediate(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_LeftMiddleDistal(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_LeftRingProximal(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_LeftRingIntermediate(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_LeftRingDistal(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_LeftLittleProximal(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_LeftLittleIntermediate(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_LeftLittleDistal(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_RightThumbProximal(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_RightThumbIntermediate(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_RightThumbDistal(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_RightIndexProximal(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_RightIndexIntermediate(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_RightIndexDistal(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_RightMiddleProximal(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_RightMiddleIntermediate(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_RightMiddleDistal(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_RightRingProximal(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_RightRingIntermediate(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_RightRingDistal(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_RightLittleProximal(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_RightLittleIntermediate(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static HumanBone Deserialize_RightLittleDistal(ListTreeNode parsed) +{ + var value = new HumanBone(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + } + return value; +} + +public static FirstPerson Deserialize_FirstPerson(ListTreeNode parsed) +{ + var value = new FirstPerson(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="meshAnnotations"){ + value.MeshAnnotations = Deserialize_MeshAnnotations(kv.Value); + continue; + } + + } + return value; +} + +public static List Deserialize_MeshAnnotations(ListTreeNode parsed) +{ + var value = new List(); + foreach(var x in parsed.ArrayItems()) + { + value.Add(Deserialize_MeshAnnotations_ITEM(x)); + } + return value; +} + +public static MeshAnnotation Deserialize_MeshAnnotations_ITEM(ListTreeNode parsed) +{ + var value = new MeshAnnotation(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + if(key=="firstPersonType"){ + value.FirstPersonType = (FirstPersonType)Enum.Parse(typeof(FirstPersonType), kv.Value.GetString(), true); + continue; + } + + } + return value; +} + +public static LookAt Deserialize_LookAt(ListTreeNode parsed) +{ + var value = new LookAt(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="offsetFromHeadBone"){ + value.OffsetFromHeadBone = Deserialize_OffsetFromHeadBone(kv.Value); + continue; + } + + if(key=="lookAtType"){ + value.LookAtType = (LookAtType)Enum.Parse(typeof(LookAtType), kv.Value.GetString(), true); + continue; + } + + if(key=="lookAtHorizontalInner"){ + value.LookAtHorizontalInner = Deserialize_LookAtHorizontalInner(kv.Value); + continue; + } + + if(key=="lookAtHorizontalOuter"){ + value.LookAtHorizontalOuter = Deserialize_LookAtHorizontalOuter(kv.Value); + continue; + } + + if(key=="lookAtVerticalDown"){ + value.LookAtVerticalDown = Deserialize_LookAtVerticalDown(kv.Value); + continue; + } + + if(key=="lookAtVerticalUp"){ + value.LookAtVerticalUp = Deserialize_LookAtVerticalUp(kv.Value); + continue; + } + + } + return value; +} + +public static float[] Deserialize_OffsetFromHeadBone(ListTreeNode parsed) +{ + var value = new float[parsed.GetArrayCount()]; + int i=0; + foreach(var x in parsed.ArrayItems()) + { + value[i++] = x.GetSingle(); + } + return value; +} + +public static LookAtRangeMap Deserialize_LookAtHorizontalInner(ListTreeNode parsed) +{ + var value = new LookAtRangeMap(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="inputMaxValue"){ + value.InputMaxValue = kv.Value.GetSingle(); + continue; + } + + if(key=="outputScale"){ + value.OutputScale = kv.Value.GetSingle(); + continue; + } + + } + return value; +} + +public static LookAtRangeMap Deserialize_LookAtHorizontalOuter(ListTreeNode parsed) +{ + var value = new LookAtRangeMap(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="inputMaxValue"){ + value.InputMaxValue = kv.Value.GetSingle(); + continue; + } + + if(key=="outputScale"){ + value.OutputScale = kv.Value.GetSingle(); + continue; + } + + } + return value; +} + +public static LookAtRangeMap Deserialize_LookAtVerticalDown(ListTreeNode parsed) +{ + var value = new LookAtRangeMap(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="inputMaxValue"){ + value.InputMaxValue = kv.Value.GetSingle(); + continue; + } + + if(key=="outputScale"){ + value.OutputScale = kv.Value.GetSingle(); + continue; + } + + } + return value; +} + +public static LookAtRangeMap Deserialize_LookAtVerticalUp(ListTreeNode parsed) +{ + var value = new LookAtRangeMap(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="inputMaxValue"){ + value.InputMaxValue = kv.Value.GetSingle(); + continue; + } + + if(key=="outputScale"){ + value.OutputScale = kv.Value.GetSingle(); + continue; + } + + } + return value; +} + +public static List Deserialize_Expressions(ListTreeNode parsed) +{ + var value = new List(); + foreach(var x in parsed.ArrayItems()) + { + value.Add(Deserialize_Expressions_ITEM(x)); + } + return value; +} + +public static Expression Deserialize_Expressions_ITEM(ListTreeNode parsed) +{ + var value = new Expression(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="extensions"){ + value.Extensions = new glTFExtensionImport(kv.Value); + continue; + } + + if(key=="extras"){ + value.Extras = new glTFExtensionImport(kv.Value); + continue; + } + + if(key=="name"){ + value.Name = kv.Value.GetString(); + continue; + } + + if(key=="preset"){ + value.Preset = (ExpressionPreset)Enum.Parse(typeof(ExpressionPreset), kv.Value.GetString(), true); + continue; + } + + if(key=="morphTargetBinds"){ + value.MorphTargetBinds = Deserialize_MorphTargetBinds(kv.Value); + continue; + } + + if(key=="materialColorBinds"){ + value.MaterialColorBinds = Deserialize_MaterialColorBinds(kv.Value); + continue; + } + + if(key=="textureTransformBinds"){ + value.TextureTransformBinds = Deserialize_TextureTransformBinds(kv.Value); + continue; + } + + if(key=="isBinary"){ + value.IsBinary = kv.Value.GetBoolean(); + continue; + } + + if(key=="ignoreBlink"){ + value.IgnoreBlink = kv.Value.GetBoolean(); + continue; + } + + if(key=="ignoreLookAt"){ + value.IgnoreLookAt = kv.Value.GetBoolean(); + continue; + } + + if(key=="ignoreMouth"){ + value.IgnoreMouth = kv.Value.GetBoolean(); + continue; + } + + } + return value; +} + +public static List Deserialize_MorphTargetBinds(ListTreeNode parsed) +{ + var value = new List(); + foreach(var x in parsed.ArrayItems()) + { + value.Add(Deserialize_MorphTargetBinds_ITEM(x)); + } + return value; +} + +public static MorphTargetBind Deserialize_MorphTargetBinds_ITEM(ListTreeNode parsed) +{ + var value = new MorphTargetBind(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="extensions"){ + value.Extensions = new glTFExtensionImport(kv.Value); + continue; + } + + if(key=="extras"){ + value.Extras = new glTFExtensionImport(kv.Value); + continue; + } + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + if(key=="index"){ + value.Index = kv.Value.GetInt32(); + continue; + } + + if(key=="weight"){ + value.Weight = kv.Value.GetSingle(); + continue; + } + + } + return value; +} + +public static List Deserialize_MaterialColorBinds(ListTreeNode parsed) +{ + var value = new List(); + foreach(var x in parsed.ArrayItems()) + { + value.Add(Deserialize_MaterialColorBinds_ITEM(x)); + } + return value; +} + +public static MaterialColorBind Deserialize_MaterialColorBinds_ITEM(ListTreeNode parsed) +{ + var value = new MaterialColorBind(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="extensions"){ + value.Extensions = new glTFExtensionImport(kv.Value); + continue; + } + + if(key=="extras"){ + value.Extras = new glTFExtensionImport(kv.Value); + continue; + } + + if(key=="material"){ + value.Material = kv.Value.GetInt32(); + continue; + } + + if(key=="type"){ + value.Type = (MaterialColorType)Enum.Parse(typeof(MaterialColorType), kv.Value.GetString(), true); + continue; + } + + if(key=="targetValue"){ + value.TargetValue = Deserialize_TargetValue(kv.Value); + continue; + } + + } + return value; +} + +public static float[] Deserialize_TargetValue(ListTreeNode parsed) +{ + var value = new float[parsed.GetArrayCount()]; + int i=0; + foreach(var x in parsed.ArrayItems()) + { + value[i++] = x.GetSingle(); + } + return value; +} + +public static List Deserialize_TextureTransformBinds(ListTreeNode parsed) +{ + var value = new List(); + foreach(var x in parsed.ArrayItems()) + { + value.Add(Deserialize_TextureTransformBinds_ITEM(x)); + } + return value; +} + +public static TextureTransformBind Deserialize_TextureTransformBinds_ITEM(ListTreeNode parsed) +{ + var value = new TextureTransformBind(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="extensions"){ + value.Extensions = new glTFExtensionImport(kv.Value); + continue; + } + + if(key=="extras"){ + value.Extras = new glTFExtensionImport(kv.Value); + continue; + } + + if(key=="material"){ + value.Material = kv.Value.GetInt32(); + continue; + } + + if(key=="scaling"){ + value.Scaling = Deserialize_Scaling(kv.Value); + continue; + } + + if(key=="offset"){ + value.Offset = Deserialize_Offset(kv.Value); + continue; + } + + } + return value; +} + +public static float[] Deserialize_Scaling(ListTreeNode parsed) +{ + var value = new float[parsed.GetArrayCount()]; + int i=0; + foreach(var x in parsed.ArrayItems()) + { + value[i++] = x.GetSingle(); + } + return value; +} + +public static float[] Deserialize_Offset(ListTreeNode parsed) +{ + var value = new float[parsed.GetArrayCount()]; + int i=0; + foreach(var x in parsed.ArrayItems()) + { + value[i++] = x.GetSingle(); + } + return value; +} + +} // GltfDeserializer +} // UniGLTF diff --git a/Assets/VRM10/Runtime/Format/Vrm/Deserializer.g.cs.meta b/Assets/VRM10/Runtime/Format/Vrm/Deserializer.g.cs.meta new file mode 100644 index 000000000..f4df7bec2 --- /dev/null +++ b/Assets/VRM10/Runtime/Format/Vrm/Deserializer.g.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 73b683fed9dcc824c9c8254fe6296445 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Format/Vrm/Format.g.cs b/Assets/VRM10/Runtime/Format/Vrm/Format.g.cs new file mode 100644 index 000000000..b11da0357 --- /dev/null +++ b/Assets/VRM10/Runtime/Format/Vrm/Format.g.cs @@ -0,0 +1,488 @@ +// This file is generated from JsonSchema. Don't modify this source code. +using System; +using System.Collections.Generic; +using UniGLTF; +using UniJSON; + +namespace UniGLTF.Extensions.VRMC_vrm +{ + + public enum AvatarPermissionType + { + onlyAuthor, + explicitlyLicensedPerson, + everyone, + + } + + public enum CommercialUsageType + { + personalNonProfit, + personalProfit, + corporation, + + } + + public enum CreditNotationType + { + required, + unnecessary, + abandoned, + + } + + public enum ModificationType + { + prohibited, + inherited, + notInherited, + + } + + public class Meta + { + // The name of the model + public string Name; + + // The version of the model + public string Version; + + // Authors of the model + public List Authors; + + // An information that describes the copyright of the model + public string CopyrightInformation; + + // An information that describes the contact information of the author + public string ContactInformation; + + // References / original works of the model + public List References; + + // Third party licenses of the model, if required. You can use line breaks + public string ThirdPartyLicenses; + + // The index to access the thumbnail image of the avatar model in gltf.images. The texture resolution of 1024x1024 is recommended. It must be square. Preferable resolution is 1024 x 1024. This is for the application to use as an icon. + public int? ThumbnailImage; + + // A person who can perform with this avatars + [JsonSchema(EnumSerializationType = EnumSerializationType.AsString)] + public AvatarPermissionType AvatarPermission; + + // A flag that permits to use this avatar in excessively violent contents + public bool? AllowExcessivelyViolentUsage; + + // A flag that permits to use this avatar in excessively sexual contents + public bool? AllowExcessivelySexualUsage; + + // An option that permits to use this avatar in commercial products + [JsonSchema(EnumSerializationType = EnumSerializationType.AsString)] + public CommercialUsageType CommercialUsage; + + // A flag that permits to use this avatar in political or religious contents + public bool? AllowPoliticalOrReligiousUsage; + + // An option that forces or abandons to display the credit of this avatar + [JsonSchema(EnumSerializationType = EnumSerializationType.AsString)] + public CreditNotationType CreditNotation; + + // A flag that permits to redistribute this avatar + public bool? AllowRedistribution; + + // An option that controls the condition to modify this avatar + [JsonSchema(EnumSerializationType = EnumSerializationType.AsString)] + public ModificationType Modification; + + // Describe the URL links of other license + public string OtherLicenseUrl; + } + + public class HumanBone + { + // Represents a single glTF node tied to this humanBone. + public int? Node; + } + + public class HumanBones + { + // Represents a single bone of a Humanoid. + public HumanBone Hips; + + // Represents a single bone of a Humanoid. + public HumanBone Spine; + + // Represents a single bone of a Humanoid. + public HumanBone Chest; + + // Represents a single bone of a Humanoid. + public HumanBone UpperChest; + + // Represents a single bone of a Humanoid. + public HumanBone Neck; + + // Represents a single bone of a Humanoid. + public HumanBone Head; + + // Represents a single bone of a Humanoid. + public HumanBone LeftEye; + + // Represents a single bone of a Humanoid. + public HumanBone RightEye; + + // Represents a single bone of a Humanoid. + public HumanBone Jaw; + + // Represents a single bone of a Humanoid. + public HumanBone LeftUpperLeg; + + // Represents a single bone of a Humanoid. + public HumanBone LeftLowerLeg; + + // Represents a single bone of a Humanoid. + public HumanBone LeftFoot; + + // Represents a single bone of a Humanoid. + public HumanBone LeftToes; + + // Represents a single bone of a Humanoid. + public HumanBone RightUpperLeg; + + // Represents a single bone of a Humanoid. + public HumanBone RightLowerLeg; + + // Represents a single bone of a Humanoid. + public HumanBone RightFoot; + + // Represents a single bone of a Humanoid. + public HumanBone RightToes; + + // Represents a single bone of a Humanoid. + public HumanBone LeftShoulder; + + // Represents a single bone of a Humanoid. + public HumanBone LeftUpperArm; + + // Represents a single bone of a Humanoid. + public HumanBone LeftLowerArm; + + // Represents a single bone of a Humanoid. + public HumanBone LeftHand; + + // Represents a single bone of a Humanoid. + public HumanBone RightShoulder; + + // Represents a single bone of a Humanoid. + public HumanBone RightUpperArm; + + // Represents a single bone of a Humanoid. + public HumanBone RightLowerArm; + + // Represents a single bone of a Humanoid. + public HumanBone RightHand; + + // Represents a single bone of a Humanoid. + public HumanBone LeftThumbProximal; + + // Represents a single bone of a Humanoid. + public HumanBone LeftThumbIntermediate; + + // Represents a single bone of a Humanoid. + public HumanBone LeftThumbDistal; + + // Represents a single bone of a Humanoid. + public HumanBone LeftIndexProximal; + + // Represents a single bone of a Humanoid. + public HumanBone LeftIndexIntermediate; + + // Represents a single bone of a Humanoid. + public HumanBone LeftIndexDistal; + + // Represents a single bone of a Humanoid. + public HumanBone LeftMiddleProximal; + + // Represents a single bone of a Humanoid. + public HumanBone LeftMiddleIntermediate; + + // Represents a single bone of a Humanoid. + public HumanBone LeftMiddleDistal; + + // Represents a single bone of a Humanoid. + public HumanBone LeftRingProximal; + + // Represents a single bone of a Humanoid. + public HumanBone LeftRingIntermediate; + + // Represents a single bone of a Humanoid. + public HumanBone LeftRingDistal; + + // Represents a single bone of a Humanoid. + public HumanBone LeftLittleProximal; + + // Represents a single bone of a Humanoid. + public HumanBone LeftLittleIntermediate; + + // Represents a single bone of a Humanoid. + public HumanBone LeftLittleDistal; + + // Represents a single bone of a Humanoid. + public HumanBone RightThumbProximal; + + // Represents a single bone of a Humanoid. + public HumanBone RightThumbIntermediate; + + // Represents a single bone of a Humanoid. + public HumanBone RightThumbDistal; + + // Represents a single bone of a Humanoid. + public HumanBone RightIndexProximal; + + // Represents a single bone of a Humanoid. + public HumanBone RightIndexIntermediate; + + // Represents a single bone of a Humanoid. + public HumanBone RightIndexDistal; + + // Represents a single bone of a Humanoid. + public HumanBone RightMiddleProximal; + + // Represents a single bone of a Humanoid. + public HumanBone RightMiddleIntermediate; + + // Represents a single bone of a Humanoid. + public HumanBone RightMiddleDistal; + + // Represents a single bone of a Humanoid. + public HumanBone RightRingProximal; + + // Represents a single bone of a Humanoid. + public HumanBone RightRingIntermediate; + + // Represents a single bone of a Humanoid. + public HumanBone RightRingDistal; + + // Represents a single bone of a Humanoid. + public HumanBone RightLittleProximal; + + // Represents a single bone of a Humanoid. + public HumanBone RightLittleIntermediate; + + // Represents a single bone of a Humanoid. + public HumanBone RightLittleDistal; + } + + public class Humanoid + { + // Represents a set of humanBones of a humanoid. + public HumanBones HumanBones; + } + + public enum FirstPersonType + { + auto, + both, + thirdPersonOnly, + firstPersonOnly, + + } + + public class MeshAnnotation + { + // The index of the node that attached to target mesh. + public int? Node; + + // How the camera interprets the mesh. + [JsonSchema(EnumSerializationType = EnumSerializationType.AsString)] + public FirstPersonType FirstPersonType; + } + + public class FirstPerson + { + // Mesh rendering annotation for cameras. 'required' : [ 'mesh' , 'firstPersonType' ] + public List MeshAnnotations; + } + + public enum LookAtType + { + bone, + blendShape, + + } + + public class LookAtRangeMap + { + // Yaw and pitch angles ( degrees ) between the head bone forward vector and the eye gaze LookAt vector + public float? InputMaxValue; + + // Degree for LookAtType.bone , Weight for LookAtType.blendShape + public float? OutputScale; + } + + public class LookAt + { + // The origin of LookAt. Position offset from the head bone + public float[] OffsetFromHeadBone; + + [JsonSchema(EnumSerializationType = EnumSerializationType.AsString)] + public LookAtType LookAtType; + + // Horizontal inward movement. The left eye moves right. The right eye moves left. + public LookAtRangeMap LookAtHorizontalInner; + + // Horizontal outward movement. The left eye moves left. The right eye moves right. + public LookAtRangeMap LookAtHorizontalOuter; + + // Vertical downward movement. Both eyes move upwards + public LookAtRangeMap LookAtVerticalDown; + + // Vertical upward movement. Both eyes move downwards + public LookAtRangeMap LookAtVerticalUp; + } + + public enum ExpressionPreset + { + custom, + aa, + ih, + ou, + ee, + oh, + blink, + happy, + angry, + sad, + relaxed, + lookUp, + surprised, + lookDown, + lookLeft, + lookRight, + blinkLeft, + blinkRight, + neutral, + + } + + public class MorphTargetBind + { + // Dictionary object with extension-specific objects. + public glTFExtension Extensions; + + // Application-specific data. + public glTFExtension Extras; + + // The index of the node that attached to target mesh. + public int? Node; + + // The index of the morph target in the mesh. + public int? Index; + + // The weight value of target morph target. + public float? Weight; + } + + public enum MaterialColorType + { + color, + emissionColor, + shadeColor, + rimColor, + outlineColor, + + } + + public class MaterialColorBind + { + // Dictionary object with extension-specific objects. + public glTFExtension Extensions; + + // Application-specific data. + public glTFExtension Extras; + + // target material + public int? Material; + + [JsonSchema(EnumSerializationType = EnumSerializationType.AsString)] + public MaterialColorType Type; + + // target color + public float[] TargetValue; + } + + public class TextureTransformBind + { + // Dictionary object with extension-specific objects. + public glTFExtension Extensions; + + // Application-specific data. + public glTFExtension Extras; + + // target material + public int? Material; + + // uv scaling for TEXCOORD_0 + public float[] Scaling; + + // uv offset for TEXCOORD_0 + public float[] Offset; + } + + public class Expression + { + // Dictionary object with extension-specific objects. + public glTFExtension Extensions; + + // Application-specific data. + public glTFExtension Extras; + + // Use only if the preset is custom. Unique within the model + public string Name; + + // Functions of Expression + [JsonSchema(EnumSerializationType = EnumSerializationType.AsString)] + public ExpressionPreset Preset; + + // Specify a morph target + public List MorphTargetBinds; + + // Material color animation references + public List MaterialColorBinds; + + // Texture transform animation references + public List TextureTransformBinds; + + // Interpret non-zero values as 1 + public bool? IsBinary; + + // Disable Blink when this Expression is enabled + public bool? IgnoreBlink; + + // Disable LookAt when this Expression is enabled + public bool? IgnoreLookAt; + + // Disable Mouth when this Expression is enabled + public bool? IgnoreMouth; + } + + public class VRMC_vrm + { + public const string ExtensionName = "VRMC_vrm"; + public static readonly Utf8String ExtensionNameUtf8 = Utf8String.From(ExtensionName); + + public string SpecVersion; + + public Meta Meta; + + // Correspondence between nodes and human bones + public Humanoid Humanoid; + + // First-person perspective settings + public FirstPerson FirstPerson; + + // Eye gaze control + public LookAt LookAt; + + // Definitions of expressions + public List Expressions; + } +} diff --git a/Assets/VRM10/Runtime/Format/Vrm/Format.g.cs.meta b/Assets/VRM10/Runtime/Format/Vrm/Format.g.cs.meta new file mode 100644 index 000000000..dac6eadf3 --- /dev/null +++ b/Assets/VRM10/Runtime/Format/Vrm/Format.g.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6f35cabeae5de7e428a213eb2fec396b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Format/Vrm/Serializer.g.cs b/Assets/VRM10/Runtime/Format/Vrm/Serializer.g.cs new file mode 100644 index 000000000..e26f43ab5 --- /dev/null +++ b/Assets/VRM10/Runtime/Format/Vrm/Serializer.g.cs @@ -0,0 +1,1609 @@ +// This file is generated from JsonSchema. Don't modify this source code. +using System; +using System.Collections.Generic; +using System.Linq; +using UniJSON; + +namespace UniGLTF.Extensions.VRMC_vrm { + + static public class GltfSerializer + { + + public static void SerializeTo(ref UniGLTF.glTFExtension dst, VRMC_vrm extension) + { + if (dst is glTFExtensionImport) + { + throw new NotImplementedException(); + } + + if (!(dst is glTFExtensionExport extensions)) + { + extensions = new glTFExtensionExport(); + dst = extensions; + } + + var f = new JsonFormatter(); + Serialize(f, extension); + extensions.Add(VRMC_vrm.ExtensionName, f.GetStoreBytes()); + } + + +public static void Serialize(JsonFormatter f, VRMC_vrm value) +{ + f.BeginMap(); + + + if(!string.IsNullOrEmpty(value.SpecVersion)){ + f.Key("specVersion"); + f.Value(value.SpecVersion); + } + + if(value.Meta!=null){ + f.Key("meta"); + Serialize_Meta(f, value.Meta); + } + + if(value.Humanoid!=null){ + f.Key("humanoid"); + Serialize_Humanoid(f, value.Humanoid); + } + + if(value.FirstPerson!=null){ + f.Key("firstPerson"); + Serialize_FirstPerson(f, value.FirstPerson); + } + + if(value.LookAt!=null){ + f.Key("lookAt"); + Serialize_LookAt(f, value.LookAt); + } + + if(value.Expressions!=null&&value.Expressions.Count()>=0){ + f.Key("expressions"); + Serialize_Expressions(f, value.Expressions); + } + + f.EndMap(); +} + +public static void Serialize_Meta(JsonFormatter f, Meta value) +{ + f.BeginMap(); + + + if(!string.IsNullOrEmpty(value.Name)){ + f.Key("name"); + f.Value(value.Name); + } + + if(!string.IsNullOrEmpty(value.Version)){ + f.Key("version"); + f.Value(value.Version); + } + + if(value.Authors!=null&&value.Authors.Count()>=1){ + f.Key("authors"); + Serialize_Authors(f, value.Authors); + } + + if(!string.IsNullOrEmpty(value.CopyrightInformation)){ + f.Key("copyrightInformation"); + f.Value(value.CopyrightInformation); + } + + if(!string.IsNullOrEmpty(value.ContactInformation)){ + f.Key("contactInformation"); + f.Value(value.ContactInformation); + } + + if(value.References!=null&&value.References.Count()>=0){ + f.Key("references"); + Serialize_References(f, value.References); + } + + if(!string.IsNullOrEmpty(value.ThirdPartyLicenses)){ + f.Key("thirdPartyLicenses"); + f.Value(value.ThirdPartyLicenses); + } + + if(value.ThumbnailImage.HasValue){ + f.Key("thumbnailImage"); + f.Value(value.ThumbnailImage.GetValueOrDefault()); + } + + if(true){ + f.Key("avatarPermission"); + f.Value(value.AvatarPermission.ToString()); + } + + if(value.AllowExcessivelyViolentUsage.HasValue){ + f.Key("allowExcessivelyViolentUsage"); + f.Value(value.AllowExcessivelyViolentUsage.GetValueOrDefault()); + } + + if(value.AllowExcessivelySexualUsage.HasValue){ + f.Key("allowExcessivelySexualUsage"); + f.Value(value.AllowExcessivelySexualUsage.GetValueOrDefault()); + } + + if(true){ + f.Key("commercialUsage"); + f.Value(value.CommercialUsage.ToString()); + } + + if(value.AllowPoliticalOrReligiousUsage.HasValue){ + f.Key("allowPoliticalOrReligiousUsage"); + f.Value(value.AllowPoliticalOrReligiousUsage.GetValueOrDefault()); + } + + if(true){ + f.Key("creditNotation"); + f.Value(value.CreditNotation.ToString()); + } + + if(value.AllowRedistribution.HasValue){ + f.Key("allowRedistribution"); + f.Value(value.AllowRedistribution.GetValueOrDefault()); + } + + if(true){ + f.Key("modification"); + f.Value(value.Modification.ToString()); + } + + if(!string.IsNullOrEmpty(value.OtherLicenseUrl)){ + f.Key("otherLicenseUrl"); + f.Value(value.OtherLicenseUrl); + } + + f.EndMap(); +} + +public static void Serialize_Authors(JsonFormatter f, List value) +{ + f.BeginList(); + + foreach(var item in value) + { + f.Value(item); + + } + f.EndList(); +} + +public static void Serialize_References(JsonFormatter f, List value) +{ + f.BeginList(); + + foreach(var item in value) + { + f.Value(item); + + } + f.EndList(); +} + +public static void Serialize_Humanoid(JsonFormatter f, Humanoid value) +{ + f.BeginMap(); + + + if(value.HumanBones!=null){ + f.Key("humanBones"); + Serialize_HumanBones(f, value.HumanBones); + } + + f.EndMap(); +} + +public static void Serialize_HumanBones(JsonFormatter f, HumanBones value) +{ + f.BeginMap(); + + + if(value.Hips!=null){ + f.Key("hips"); + Serialize_Hips(f, value.Hips); + } + + if(value.Spine!=null){ + f.Key("spine"); + Serialize_Spine(f, value.Spine); + } + + if(value.Chest!=null){ + f.Key("chest"); + Serialize_Chest(f, value.Chest); + } + + if(value.UpperChest!=null){ + f.Key("upperChest"); + Serialize_UpperChest(f, value.UpperChest); + } + + if(value.Neck!=null){ + f.Key("neck"); + Serialize_Neck(f, value.Neck); + } + + if(value.Head!=null){ + f.Key("head"); + Serialize_Head(f, value.Head); + } + + if(value.LeftEye!=null){ + f.Key("leftEye"); + Serialize_LeftEye(f, value.LeftEye); + } + + if(value.RightEye!=null){ + f.Key("rightEye"); + Serialize_RightEye(f, value.RightEye); + } + + if(value.Jaw!=null){ + f.Key("jaw"); + Serialize_Jaw(f, value.Jaw); + } + + if(value.LeftUpperLeg!=null){ + f.Key("leftUpperLeg"); + Serialize_LeftUpperLeg(f, value.LeftUpperLeg); + } + + if(value.LeftLowerLeg!=null){ + f.Key("leftLowerLeg"); + Serialize_LeftLowerLeg(f, value.LeftLowerLeg); + } + + if(value.LeftFoot!=null){ + f.Key("leftFoot"); + Serialize_LeftFoot(f, value.LeftFoot); + } + + if(value.LeftToes!=null){ + f.Key("leftToes"); + Serialize_LeftToes(f, value.LeftToes); + } + + if(value.RightUpperLeg!=null){ + f.Key("rightUpperLeg"); + Serialize_RightUpperLeg(f, value.RightUpperLeg); + } + + if(value.RightLowerLeg!=null){ + f.Key("rightLowerLeg"); + Serialize_RightLowerLeg(f, value.RightLowerLeg); + } + + if(value.RightFoot!=null){ + f.Key("rightFoot"); + Serialize_RightFoot(f, value.RightFoot); + } + + if(value.RightToes!=null){ + f.Key("rightToes"); + Serialize_RightToes(f, value.RightToes); + } + + if(value.LeftShoulder!=null){ + f.Key("leftShoulder"); + Serialize_LeftShoulder(f, value.LeftShoulder); + } + + if(value.LeftUpperArm!=null){ + f.Key("leftUpperArm"); + Serialize_LeftUpperArm(f, value.LeftUpperArm); + } + + if(value.LeftLowerArm!=null){ + f.Key("leftLowerArm"); + Serialize_LeftLowerArm(f, value.LeftLowerArm); + } + + if(value.LeftHand!=null){ + f.Key("leftHand"); + Serialize_LeftHand(f, value.LeftHand); + } + + if(value.RightShoulder!=null){ + f.Key("rightShoulder"); + Serialize_RightShoulder(f, value.RightShoulder); + } + + if(value.RightUpperArm!=null){ + f.Key("rightUpperArm"); + Serialize_RightUpperArm(f, value.RightUpperArm); + } + + if(value.RightLowerArm!=null){ + f.Key("rightLowerArm"); + Serialize_RightLowerArm(f, value.RightLowerArm); + } + + if(value.RightHand!=null){ + f.Key("rightHand"); + Serialize_RightHand(f, value.RightHand); + } + + if(value.LeftThumbProximal!=null){ + f.Key("leftThumbProximal"); + Serialize_LeftThumbProximal(f, value.LeftThumbProximal); + } + + if(value.LeftThumbIntermediate!=null){ + f.Key("leftThumbIntermediate"); + Serialize_LeftThumbIntermediate(f, value.LeftThumbIntermediate); + } + + if(value.LeftThumbDistal!=null){ + f.Key("leftThumbDistal"); + Serialize_LeftThumbDistal(f, value.LeftThumbDistal); + } + + if(value.LeftIndexProximal!=null){ + f.Key("leftIndexProximal"); + Serialize_LeftIndexProximal(f, value.LeftIndexProximal); + } + + if(value.LeftIndexIntermediate!=null){ + f.Key("leftIndexIntermediate"); + Serialize_LeftIndexIntermediate(f, value.LeftIndexIntermediate); + } + + if(value.LeftIndexDistal!=null){ + f.Key("leftIndexDistal"); + Serialize_LeftIndexDistal(f, value.LeftIndexDistal); + } + + if(value.LeftMiddleProximal!=null){ + f.Key("leftMiddleProximal"); + Serialize_LeftMiddleProximal(f, value.LeftMiddleProximal); + } + + if(value.LeftMiddleIntermediate!=null){ + f.Key("leftMiddleIntermediate"); + Serialize_LeftMiddleIntermediate(f, value.LeftMiddleIntermediate); + } + + if(value.LeftMiddleDistal!=null){ + f.Key("leftMiddleDistal"); + Serialize_LeftMiddleDistal(f, value.LeftMiddleDistal); + } + + if(value.LeftRingProximal!=null){ + f.Key("leftRingProximal"); + Serialize_LeftRingProximal(f, value.LeftRingProximal); + } + + if(value.LeftRingIntermediate!=null){ + f.Key("leftRingIntermediate"); + Serialize_LeftRingIntermediate(f, value.LeftRingIntermediate); + } + + if(value.LeftRingDistal!=null){ + f.Key("leftRingDistal"); + Serialize_LeftRingDistal(f, value.LeftRingDistal); + } + + if(value.LeftLittleProximal!=null){ + f.Key("leftLittleProximal"); + Serialize_LeftLittleProximal(f, value.LeftLittleProximal); + } + + if(value.LeftLittleIntermediate!=null){ + f.Key("leftLittleIntermediate"); + Serialize_LeftLittleIntermediate(f, value.LeftLittleIntermediate); + } + + if(value.LeftLittleDistal!=null){ + f.Key("leftLittleDistal"); + Serialize_LeftLittleDistal(f, value.LeftLittleDistal); + } + + if(value.RightThumbProximal!=null){ + f.Key("rightThumbProximal"); + Serialize_RightThumbProximal(f, value.RightThumbProximal); + } + + if(value.RightThumbIntermediate!=null){ + f.Key("rightThumbIntermediate"); + Serialize_RightThumbIntermediate(f, value.RightThumbIntermediate); + } + + if(value.RightThumbDistal!=null){ + f.Key("rightThumbDistal"); + Serialize_RightThumbDistal(f, value.RightThumbDistal); + } + + if(value.RightIndexProximal!=null){ + f.Key("rightIndexProximal"); + Serialize_RightIndexProximal(f, value.RightIndexProximal); + } + + if(value.RightIndexIntermediate!=null){ + f.Key("rightIndexIntermediate"); + Serialize_RightIndexIntermediate(f, value.RightIndexIntermediate); + } + + if(value.RightIndexDistal!=null){ + f.Key("rightIndexDistal"); + Serialize_RightIndexDistal(f, value.RightIndexDistal); + } + + if(value.RightMiddleProximal!=null){ + f.Key("rightMiddleProximal"); + Serialize_RightMiddleProximal(f, value.RightMiddleProximal); + } + + if(value.RightMiddleIntermediate!=null){ + f.Key("rightMiddleIntermediate"); + Serialize_RightMiddleIntermediate(f, value.RightMiddleIntermediate); + } + + if(value.RightMiddleDistal!=null){ + f.Key("rightMiddleDistal"); + Serialize_RightMiddleDistal(f, value.RightMiddleDistal); + } + + if(value.RightRingProximal!=null){ + f.Key("rightRingProximal"); + Serialize_RightRingProximal(f, value.RightRingProximal); + } + + if(value.RightRingIntermediate!=null){ + f.Key("rightRingIntermediate"); + Serialize_RightRingIntermediate(f, value.RightRingIntermediate); + } + + if(value.RightRingDistal!=null){ + f.Key("rightRingDistal"); + Serialize_RightRingDistal(f, value.RightRingDistal); + } + + if(value.RightLittleProximal!=null){ + f.Key("rightLittleProximal"); + Serialize_RightLittleProximal(f, value.RightLittleProximal); + } + + if(value.RightLittleIntermediate!=null){ + f.Key("rightLittleIntermediate"); + Serialize_RightLittleIntermediate(f, value.RightLittleIntermediate); + } + + if(value.RightLittleDistal!=null){ + f.Key("rightLittleDistal"); + Serialize_RightLittleDistal(f, value.RightLittleDistal); + } + + f.EndMap(); +} + +public static void Serialize_Hips(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_Spine(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_Chest(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_UpperChest(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_Neck(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_Head(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_LeftEye(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_RightEye(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_Jaw(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_LeftUpperLeg(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_LeftLowerLeg(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_LeftFoot(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_LeftToes(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_RightUpperLeg(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_RightLowerLeg(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_RightFoot(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_RightToes(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_LeftShoulder(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_LeftUpperArm(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_LeftLowerArm(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_LeftHand(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_RightShoulder(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_RightUpperArm(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_RightLowerArm(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_RightHand(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_LeftThumbProximal(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_LeftThumbIntermediate(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_LeftThumbDistal(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_LeftIndexProximal(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_LeftIndexIntermediate(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_LeftIndexDistal(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_LeftMiddleProximal(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_LeftMiddleIntermediate(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_LeftMiddleDistal(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_LeftRingProximal(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_LeftRingIntermediate(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_LeftRingDistal(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_LeftLittleProximal(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_LeftLittleIntermediate(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_LeftLittleDistal(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_RightThumbProximal(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_RightThumbIntermediate(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_RightThumbDistal(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_RightIndexProximal(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_RightIndexIntermediate(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_RightIndexDistal(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_RightMiddleProximal(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_RightMiddleIntermediate(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_RightMiddleDistal(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_RightRingProximal(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_RightRingIntermediate(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_RightRingDistal(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_RightLittleProximal(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_RightLittleIntermediate(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_RightLittleDistal(JsonFormatter f, HumanBone value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_FirstPerson(JsonFormatter f, FirstPerson value) +{ + f.BeginMap(); + + + if(value.MeshAnnotations!=null&&value.MeshAnnotations.Count()>=0){ + f.Key("meshAnnotations"); + Serialize_MeshAnnotations(f, value.MeshAnnotations); + } + + f.EndMap(); +} + +public static void Serialize_MeshAnnotations(JsonFormatter f, List value) +{ + f.BeginList(); + + foreach(var item in value) + { + Serialize_MeshAnnotations_ITEM(f, item); + + } + f.EndList(); +} + +public static void Serialize_MeshAnnotations_ITEM(JsonFormatter f, MeshAnnotation value) +{ + f.BeginMap(); + + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + if(true){ + f.Key("firstPersonType"); + f.Value(value.FirstPersonType.ToString()); + } + + f.EndMap(); +} + +public static void Serialize_LookAt(JsonFormatter f, LookAt value) +{ + f.BeginMap(); + + + if(value.OffsetFromHeadBone!=null&&value.OffsetFromHeadBone.Count()>=0){ + f.Key("offsetFromHeadBone"); + Serialize_OffsetFromHeadBone(f, value.OffsetFromHeadBone); + } + + if(true){ + f.Key("lookAtType"); + f.Value(value.LookAtType.ToString()); + } + + if(value.LookAtHorizontalInner!=null){ + f.Key("lookAtHorizontalInner"); + Serialize_LookAtHorizontalInner(f, value.LookAtHorizontalInner); + } + + if(value.LookAtHorizontalOuter!=null){ + f.Key("lookAtHorizontalOuter"); + Serialize_LookAtHorizontalOuter(f, value.LookAtHorizontalOuter); + } + + if(value.LookAtVerticalDown!=null){ + f.Key("lookAtVerticalDown"); + Serialize_LookAtVerticalDown(f, value.LookAtVerticalDown); + } + + if(value.LookAtVerticalUp!=null){ + f.Key("lookAtVerticalUp"); + Serialize_LookAtVerticalUp(f, value.LookAtVerticalUp); + } + + f.EndMap(); +} + +public static void Serialize_OffsetFromHeadBone(JsonFormatter f, float[] value) +{ + f.BeginList(); + + foreach(var item in value) + { + f.Value(item); + + } + f.EndList(); +} + +public static void Serialize_LookAtHorizontalInner(JsonFormatter f, LookAtRangeMap value) +{ + f.BeginMap(); + + + if(value.InputMaxValue.HasValue){ + f.Key("inputMaxValue"); + f.Value(value.InputMaxValue.GetValueOrDefault()); + } + + if(value.OutputScale.HasValue){ + f.Key("outputScale"); + f.Value(value.OutputScale.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_LookAtHorizontalOuter(JsonFormatter f, LookAtRangeMap value) +{ + f.BeginMap(); + + + if(value.InputMaxValue.HasValue){ + f.Key("inputMaxValue"); + f.Value(value.InputMaxValue.GetValueOrDefault()); + } + + if(value.OutputScale.HasValue){ + f.Key("outputScale"); + f.Value(value.OutputScale.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_LookAtVerticalDown(JsonFormatter f, LookAtRangeMap value) +{ + f.BeginMap(); + + + if(value.InputMaxValue.HasValue){ + f.Key("inputMaxValue"); + f.Value(value.InputMaxValue.GetValueOrDefault()); + } + + if(value.OutputScale.HasValue){ + f.Key("outputScale"); + f.Value(value.OutputScale.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_LookAtVerticalUp(JsonFormatter f, LookAtRangeMap value) +{ + f.BeginMap(); + + + if(value.InputMaxValue.HasValue){ + f.Key("inputMaxValue"); + f.Value(value.InputMaxValue.GetValueOrDefault()); + } + + if(value.OutputScale.HasValue){ + f.Key("outputScale"); + f.Value(value.OutputScale.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_Expressions(JsonFormatter f, List value) +{ + f.BeginList(); + + foreach(var item in value) + { + Serialize_Expressions_ITEM(f, item); + + } + f.EndList(); +} + +public static void Serialize_Expressions_ITEM(JsonFormatter f, Expression value) +{ + f.BeginMap(); + + + if(value.Extensions!=null){ + f.Key("extensions"); + value.Extensions.Serialize(f); + } + + if(value.Extras!=null){ + f.Key("extras"); + value.Extras.Serialize(f); + } + + if(!string.IsNullOrEmpty(value.Name)){ + f.Key("name"); + f.Value(value.Name); + } + + if(true){ + f.Key("preset"); + f.Value(value.Preset.ToString()); + } + + if(value.MorphTargetBinds!=null&&value.MorphTargetBinds.Count()>=0){ + f.Key("morphTargetBinds"); + Serialize_MorphTargetBinds(f, value.MorphTargetBinds); + } + + if(value.MaterialColorBinds!=null&&value.MaterialColorBinds.Count()>=0){ + f.Key("materialColorBinds"); + Serialize_MaterialColorBinds(f, value.MaterialColorBinds); + } + + if(value.TextureTransformBinds!=null&&value.TextureTransformBinds.Count()>=0){ + f.Key("textureTransformBinds"); + Serialize_TextureTransformBinds(f, value.TextureTransformBinds); + } + + if(value.IsBinary.HasValue){ + f.Key("isBinary"); + f.Value(value.IsBinary.GetValueOrDefault()); + } + + if(value.IgnoreBlink.HasValue){ + f.Key("ignoreBlink"); + f.Value(value.IgnoreBlink.GetValueOrDefault()); + } + + if(value.IgnoreLookAt.HasValue){ + f.Key("ignoreLookAt"); + f.Value(value.IgnoreLookAt.GetValueOrDefault()); + } + + if(value.IgnoreMouth.HasValue){ + f.Key("ignoreMouth"); + f.Value(value.IgnoreMouth.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_MorphTargetBinds(JsonFormatter f, List value) +{ + f.BeginList(); + + foreach(var item in value) + { + Serialize_MorphTargetBinds_ITEM(f, item); + + } + f.EndList(); +} + +public static void Serialize_MorphTargetBinds_ITEM(JsonFormatter f, MorphTargetBind value) +{ + f.BeginMap(); + + + if(value.Extensions!=null){ + f.Key("extensions"); + value.Extensions.Serialize(f); + } + + if(value.Extras!=null){ + f.Key("extras"); + value.Extras.Serialize(f); + } + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + if(value.Index.HasValue){ + f.Key("index"); + f.Value(value.Index.GetValueOrDefault()); + } + + if(value.Weight.HasValue){ + f.Key("weight"); + f.Value(value.Weight.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void Serialize_MaterialColorBinds(JsonFormatter f, List value) +{ + f.BeginList(); + + foreach(var item in value) + { + Serialize_MaterialColorBinds_ITEM(f, item); + + } + f.EndList(); +} + +public static void Serialize_MaterialColorBinds_ITEM(JsonFormatter f, MaterialColorBind value) +{ + f.BeginMap(); + + + if(value.Extensions!=null){ + f.Key("extensions"); + value.Extensions.Serialize(f); + } + + if(value.Extras!=null){ + f.Key("extras"); + value.Extras.Serialize(f); + } + + if(value.Material.HasValue){ + f.Key("material"); + f.Value(value.Material.GetValueOrDefault()); + } + + if(true){ + f.Key("type"); + f.Value(value.Type.ToString()); + } + + if(value.TargetValue!=null&&value.TargetValue.Count()>=4){ + f.Key("targetValue"); + Serialize_TargetValue(f, value.TargetValue); + } + + f.EndMap(); +} + +public static void Serialize_TargetValue(JsonFormatter f, float[] value) +{ + f.BeginList(); + + foreach(var item in value) + { + f.Value(item); + + } + f.EndList(); +} + +public static void Serialize_TextureTransformBinds(JsonFormatter f, List value) +{ + f.BeginList(); + + foreach(var item in value) + { + Serialize_TextureTransformBinds_ITEM(f, item); + + } + f.EndList(); +} + +public static void Serialize_TextureTransformBinds_ITEM(JsonFormatter f, TextureTransformBind value) +{ + f.BeginMap(); + + + if(value.Extensions!=null){ + f.Key("extensions"); + value.Extensions.Serialize(f); + } + + if(value.Extras!=null){ + f.Key("extras"); + value.Extras.Serialize(f); + } + + if(value.Material.HasValue){ + f.Key("material"); + f.Value(value.Material.GetValueOrDefault()); + } + + if(value.Scaling!=null&&value.Scaling.Count()>=2){ + f.Key("scaling"); + Serialize_Scaling(f, value.Scaling); + } + + if(value.Offset!=null&&value.Offset.Count()>=2){ + f.Key("offset"); + Serialize_Offset(f, value.Offset); + } + + f.EndMap(); +} + +public static void Serialize_Scaling(JsonFormatter f, float[] value) +{ + f.BeginList(); + + foreach(var item in value) + { + f.Value(item); + + } + f.EndList(); +} + +public static void Serialize_Offset(JsonFormatter f, float[] value) +{ + f.BeginList(); + + foreach(var item in value) + { + f.Value(item); + + } + f.EndList(); +} + + } // class +} // namespace diff --git a/Assets/VRM10/Runtime/Format/Vrm/Serializer.g.cs.meta b/Assets/VRM10/Runtime/Format/Vrm/Serializer.g.cs.meta new file mode 100644 index 000000000..d341b52ed --- /dev/null +++ b/Assets/VRM10/Runtime/Format/Vrm/Serializer.g.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 403738647bdb9aa498164cd295ff64d4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/IO.meta b/Assets/VRM10/Runtime/IO.meta new file mode 100644 index 000000000..543d49c01 --- /dev/null +++ b/Assets/VRM10/Runtime/IO.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c428532d152f9674c8b71ba2b7e23046 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/IO/ArrayBytesBuffer.cs b/Assets/VRM10/Runtime/IO/ArrayBytesBuffer.cs new file mode 100644 index 000000000..fc1dd7de6 --- /dev/null +++ b/Assets/VRM10/Runtime/IO/ArrayBytesBuffer.cs @@ -0,0 +1,89 @@ +using System; +using VrmLib; + +namespace UniVRM10 +{ + /// + /// for exporter + /// + public class ArrayByteBuffer10 + { + public ArraySegment Bytes + { + get + { + if (m_bytes == null) + { + return new ArraySegment(); + } + + return new ArraySegment(m_bytes, 0, m_used); + } + } + + Byte[] m_bytes; + int m_used = 0; + + public ArrayByteBuffer10(Byte[] bytes = null) + { + m_bytes = bytes ?? (new byte[] { }); + } + + public ArrayByteBuffer10(Byte[] bytes, int used) + { + m_bytes = bytes ?? (new byte[] { }); + m_used = used; + } + + + public void ExtendCapacity(int byteLength) + { + var backup = m_bytes; + m_bytes = new byte[backup.Length + byteLength]; + backup.CopyTo(m_bytes, backup.Length); + } + + public void Extend(ArraySegment array, int stride, out int offset, out int length) + { + var tmp = m_bytes; + // alignment + var padding = m_used % stride == 0 ? 0 : stride - m_used % stride; + + if (m_bytes == null || m_used + padding + array.Count > m_bytes.Length) + { + // recreate buffer + var newSize = Math.Max(m_used + padding + array.Count, m_bytes.Length * 2); + m_bytes = new Byte[newSize]; + if (m_used > 0) + { + Buffer.BlockCopy(tmp, 0, m_bytes, 0, m_used); + } + } + if (m_used + padding + array.Count > m_bytes.Length) + { + throw new ArgumentOutOfRangeException(); + } + + Buffer.BlockCopy(array.Array, array.Offset, m_bytes, m_used + padding, array.Count); + + length = array.Count; + offset = m_used + padding; + + + // var result = new GltfBufferView + // { + // buffer = 0, + // byteLength = array.Length, + // byteOffset = m_used + padding, + // target = target, + // }; + // if (target == GltfBufferTargetType.ARRAY_BUFFER) + // { + // result.byteStride = stride; + // } + + m_used = m_used + padding + array.Count; + // return result; + } + } +} \ No newline at end of file diff --git a/Assets/VRM10/Runtime/IO/ArrayBytesBuffer.cs.meta b/Assets/VRM10/Runtime/IO/ArrayBytesBuffer.cs.meta new file mode 100644 index 000000000..9650705da --- /dev/null +++ b/Assets/VRM10/Runtime/IO/ArrayBytesBuffer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c700d6d6fbb38fa4d9662213c58d5725 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/IO/ArrayExtensions.cs b/Assets/VRM10/Runtime/IO/ArrayExtensions.cs new file mode 100644 index 000000000..73b0d250d --- /dev/null +++ b/Assets/VRM10/Runtime/IO/ArrayExtensions.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using VrmLib; + + +namespace UniVRM10 +{ + public static class ArrayExtensions + { + public static Vector2 ToVector2(this float[] src, Vector2 defaultValue = default) + { + if (src.Length != 2) return defaultValue; + + var v = new Vector2(); + v.X = src[0]; + v.Y = src[1]; + return v; + } + + public static Vector3 ToVector3(this float[] src, Vector3 defaultValue = default) + { + if (src.Length != 3) return defaultValue; + + var v = new Vector3(); + v.X = src[0]; + v.Y = src[1]; + v.Z = src[2]; + return v; + } + + public static Quaternion ToQuaternion(this float[] src) + { + if (src.Length != 4) return Quaternion.Identity; + + var v = new Quaternion(src[0], src[1], src[2], src[3]); + return v; + } + + public static TextureInfo GetTexture(this int? nullable, List textures) + { + if (!nullable.TryGetValidIndex(textures.Count, out int index)) + { + return null; + } + var texture = textures[index]; + return new TextureInfo(texture); + } + + public static TextureInfo GetTexture(this int index, List textures) + { + if (index < 0 || index >= textures.Count) + { + return null; + } + var texture = textures[index]; + return new TextureInfo(texture); + } + + public static int? ToIndex(this TextureInfo texture, List textures) + { + if (texture == null) + { + return default; + } + return textures.IndexOfThrow(texture.Texture); + } + + public static Vector4 ToVector4(this float[] src, Vector4 defaultValue = default) + { + switch (src.Length) + { + case 4: + return new Vector4(src[0], src[1], src[2], src[3]); + case 3: + return new Vector4(src[0], src[1], src[2], 1.0f); + case 0: + return defaultValue; + default: + throw new Exception(); + } + } + + public static LinearColor ToLinearColor(this float[] src, Vector4 defaultValue) + { + switch (src.Length) + { + case 4: + return LinearColor.FromLiner(src[0], src[1], src[2], src[3]); + case 3: + return LinearColor.FromLiner(src[0], src[1], src[2], 1.0f); + case 0: + return LinearColor.FromLiner(defaultValue); + default: + throw new Exception(); + } + } + + public static float[] ToFloat2(this System.Numerics.Vector2 value) + { + return new float[] { value.X, value.Y }; + } + + public static float[] ToFloat4(this System.Numerics.Vector4 value) + { + return new float[] { value.X, value.Y, value.Z, value.W }; + } + } +} diff --git a/Assets/VRM10/Runtime/IO/ArrayExtensions.cs.meta b/Assets/VRM10/Runtime/IO/ArrayExtensions.cs.meta new file mode 100644 index 000000000..bab0eef1f --- /dev/null +++ b/Assets/VRM10/Runtime/IO/ArrayExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8c6a95a2c709aee47aa1bad5943c2aa7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/IO/BufferAccessorAdapter.cs b/Assets/VRM10/Runtime/IO/BufferAccessorAdapter.cs new file mode 100644 index 000000000..4dc9b1fd3 --- /dev/null +++ b/Assets/VRM10/Runtime/IO/BufferAccessorAdapter.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using System.Runtime.InteropServices; +using UniGLTF; +using VrmLib; + +namespace UniVRM10 +{ + public static class BufferAccessorAdapter + { + public static int TypeCount(this string type) + { + switch (type) + { + case "SCALAR": + return 1; + case "VEC2": + return 2; + case "VEC3": + return 3; + case "VEC4": + case "MAT2": + return 4; + case "MAT3": + return 9; + case "MAT4": + return 16; + default: + throw new NotImplementedException(); + } + } + + public static int AddViewTo(this BufferAccessor self, + Vrm10Storage storage, int bufferIndex, + int offset = 0, int count = 0) + { + var stride = self.Stride; + if (count == 0) + { + count = self.Count; + } + var slice = self.Bytes.Slice(offset * stride, count * stride); + return storage.AppendToBuffer(bufferIndex, slice, stride); + } + + static glTFAccessor CreateGltfAccessor(this BufferAccessor self, + int viewIndex, int count = 0, int byteOffset = 0) + { + if (count == 0) + { + count = self.Count; + } + return new glTFAccessor + { + bufferView = viewIndex, + byteOffset = byteOffset, + componentType = (glComponentType)self.ComponentType, + type = self.AccessorType.ToString(), + count = count, + }; + } + + public static int AddAccessorTo(this BufferAccessor self, + Vrm10Storage storage, int viewIndex, + Action, glTFAccessor> minMax = null, + int offset = 0, int count = 0) + { + var gltf = storage.Gltf; + var accessorIndex = gltf.accessors.Count; + var accessor = self.CreateGltfAccessor(viewIndex, count, offset * self.Stride); + if (minMax != null) + { + minMax(self.Bytes, accessor); + } + gltf.accessors.Add(accessor); + return accessorIndex; + } + + public static int AddAccessorTo(this BufferAccessor self, + Vrm10Storage storage, int bufferIndex, + // GltfBufferTargetType targetType, + bool useSparse, + Action, glTFAccessor> minMax = null, + int offset = 0, int count = 0) + { + if (self.ComponentType == AccessorValueType.FLOAT + && self.AccessorType == AccessorVectorType.VEC3 + ) + { + var values = self.GetSpan(); + // 巨大ポリゴンのモデル対策にValueTupleの型をushort -> uint へ + var sparseValuesWithIndex = new List>(); + for (int i = 0; i < values.Length; ++i) + { + var v = values[i]; + if (v != Vector3.Zero) + { + sparseValuesWithIndex.Add((i, v)); + } + } + + //var status = $"{sparseIndices.Count * 14}/{values.Length * 12}"; + if (useSparse + && sparseValuesWithIndex.Count > 0 // avoid empty sparse + && sparseValuesWithIndex.Count * 16 < values.Length * 12) + { + // use sparse + var sparseIndexBin = new ArraySegment(new byte[sparseValuesWithIndex.Count * 4]); + var sparseIndexSpan = SpanLike.Wrap(sparseIndexBin); + var sparseValueBin = new ArraySegment(new byte[sparseValuesWithIndex.Count * 12]); + var sparseValueSpan = SpanLike.Wrap(sparseValueBin); + + for (int i = 0; i < sparseValuesWithIndex.Count; ++i) + { + var (index, value) = sparseValuesWithIndex[i]; + sparseIndexSpan[i] = index; + sparseValueSpan[i] = value; + } + + var sparseIndexView = storage.AppendToBuffer(bufferIndex, sparseIndexBin, 4); + var sparseValueView = storage.AppendToBuffer(bufferIndex, sparseValueBin, 12); + + var accessorIndex = storage.Gltf.accessors.Count; + var accessor = new glTFAccessor + { + componentType = (glComponentType)self.ComponentType, + type = self.AccessorType.ToString(), + count = self.Count, + sparse = new glTFSparse + { + count = sparseValuesWithIndex.Count, + indices = new glTFSparseIndices + { + componentType = (glComponentType)AccessorValueType.UNSIGNED_INT, + bufferView = sparseIndexView, + }, + values = new glTFSparseValues + { + bufferView = sparseValueView, + }, + } + }; + if (minMax != null) + { + minMax(sparseValueBin, accessor); + } + storage.Gltf.accessors.Add(accessor); + return accessorIndex; + } + } + + var viewIndex = self.AddViewTo(storage, bufferIndex, offset, count); + return self.AddAccessorTo(storage, viewIndex, minMax, 0, count); + } + } +} diff --git a/Assets/VRM10/Runtime/IO/BufferAccessorAdapter.cs.meta b/Assets/VRM10/Runtime/IO/BufferAccessorAdapter.cs.meta new file mode 100644 index 000000000..0f617c8c4 --- /dev/null +++ b/Assets/VRM10/Runtime/IO/BufferAccessorAdapter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9bdd7001c32368b4da917a0384e1ef86 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/IO/ImageAdapter.cs b/Assets/VRM10/Runtime/IO/ImageAdapter.cs new file mode 100644 index 000000000..d7415c5f9 --- /dev/null +++ b/Assets/VRM10/Runtime/IO/ImageAdapter.cs @@ -0,0 +1,101 @@ +using VrmLib; +using System; +using UniGLTF; + +namespace UniVRM10 +{ + public static class ImageAdapter + { + public static Image FromGltf(this glTFImage x, Vrm10Storage storage) + { + if (x.bufferView == -1) + { + // 外部参照? + throw new Exception(); + } + + var view = storage.Gltf.bufferViews[x.bufferView]; + + var buffer = storage.Gltf.buffers[view.buffer]; + + // テクスチャの用途を調べる + var usage = default(ImageUsage); + foreach (var material in storage.Gltf.materials) + { + var colorImage = GetColorImage(storage, material); + if (colorImage == x) + { + usage |= ImageUsage.Color; + } + + var normalImage = GetNormalImage(storage, material); + if (normalImage == x) + { + usage |= ImageUsage.Normal; + } + } + + var memory = storage.GetBufferBytes(buffer); + return new Image(x.name, + x.mimeType, + usage, + memory.Slice(view.byteOffset, view.byteLength)); + } + + static glTFImage GetTexture(Vrm10Storage storage, int index) + { + if (index < 0 || index >= storage.Gltf.textures.Count) + { + return null; + } + var texture = storage.Gltf.textures[index]; + if (texture.source < 0 || texture.source >= storage.Gltf.images.Count) + { + return null; + } + return storage.Gltf.images[texture.source]; + } + + static glTFImage GetColorImage(Vrm10Storage storage, glTFMaterial m) + { + if (m.pbrMetallicRoughness == null) + { + return null; + } + if (m.pbrMetallicRoughness.baseColorTexture == null) + { + return null; + } + if (!m.pbrMetallicRoughness.baseColorTexture.index.TryGetValidIndex(storage.TextureCount, out int index)) + { + return null; + } + return GetTexture(storage, index); + } + + static glTFImage GetNormalImage(Vrm10Storage storage, glTFMaterial m) + { + if (m.normalTexture == null) + { + return null; + } + if (!m.normalTexture.index.TryGetValidIndex(storage.TextureCount, out int index)) + { + return null; + } + return GetTexture(storage, index); + } + + public static glTFImage ToGltf(this Image src, Vrm10Storage storage) + { + var viewIndex = storage.AppendToBuffer(0, src.Bytes, 1); + var gltf = storage.Gltf; + return new glTFImage + { + name = src.Name, + mimeType = src.MimeType, + bufferView = viewIndex, + }; + } + } +} \ No newline at end of file diff --git a/Assets/VRM10/Runtime/IO/ImageAdapter.cs.meta b/Assets/VRM10/Runtime/IO/ImageAdapter.cs.meta new file mode 100644 index 000000000..efc311140 --- /dev/null +++ b/Assets/VRM10/Runtime/IO/ImageAdapter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d48a6bb715d0d024fa5af64bb63f695e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/IO/IndexExtensions.cs b/Assets/VRM10/Runtime/IO/IndexExtensions.cs new file mode 100644 index 000000000..56b323369 --- /dev/null +++ b/Assets/VRM10/Runtime/IO/IndexExtensions.cs @@ -0,0 +1,22 @@ +namespace UniVRM10 +{ + public static class IndexExtensions + { + public static bool TryGetValidIndex(this int value, int count, out int index) + { + if (value < 0) + { + index = -1; + return false; + } + if (value >= count) + { + index = -1; + return false; + } + + index = value; + return true; + } + } +} diff --git a/Assets/VRM10/Runtime/IO/IndexExtensions.cs.meta b/Assets/VRM10/Runtime/IO/IndexExtensions.cs.meta new file mode 100644 index 000000000..cf2062b6f --- /dev/null +++ b/Assets/VRM10/Runtime/IO/IndexExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3db2e2e2c91b966408fc5ab9803292c2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/IO/MToonAdapter.cs b/Assets/VRM10/Runtime/IO/MToonAdapter.cs new file mode 100644 index 000000000..4d4f94e00 --- /dev/null +++ b/Assets/VRM10/Runtime/IO/MToonAdapter.cs @@ -0,0 +1,263 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using VrmLib.MToon; +using VrmLib; +using UniGLTF; +using UniGLTF.Extensions.VRMC_materials_mtoon; +using UniJSON; + +namespace UniVRM10 +{ + public static class MToonAdapter + { + // for debug + static readonly Vector4 Nan = new Vector4(float.NaN, float.NaN, float.NaN, float.NaN); + + static RenderMode GetRenderMode(string alphaMode, bool isTransparentWithZWrite) + { + switch (alphaMode) + { + case "OPAQUE": return RenderMode.Opaque; + case "MASK": return RenderMode.Cutout; + case "BLEND": + { + if (isTransparentWithZWrite) + { + return RenderMode.TransparentWithZWrite; + } + else + { + return RenderMode.Transparent; + } + } + } + + throw new NotImplementedException(); + } + + public static MToonMaterial MToonFromGltf(glTFMaterial material, List textures, VRMC_materials_mtoon extension) + { + var mtoon = new MToonMaterial(material.name); + + var Meta = new MetaDefinition + { + Implementation = "Santarh/MToon", + }; + var Color = new ColorDefinition + { + LitColor = material.pbrMetallicRoughness.baseColorFactor.ToLinearColor(Nan), + LitMultiplyTexture = material.pbrMetallicRoughness.baseColorTexture?.index.GetTexture(textures), + ShadeColor = extension.ShadeFactor.ToLinearColor(Nan), + ShadeMultiplyTexture = extension.ShadeMultiplyTexture.GetTexture(textures), + CutoutThresholdValue = material.alphaCutoff, + }; + var Outline = new OutlineDefinition + { + OutlineColorMode = (VrmLib.MToon.OutlineColorMode)extension.OutlineColorMode, + OutlineColor = extension.OutlineFactor.ToLinearColor(Nan), + OutlineLightingMixValue = extension.OutlineLightingMixFactor.Value, + OutlineScaledMaxDistanceValue = extension.OutlineScaledMaxDistanceFactor.Value, + OutlineWidthMode = (VrmLib.MToon.OutlineWidthMode)extension.OutlineWidthMode, + OutlineWidthValue = extension.OutlineWidthFactor.Value, + OutlineWidthMultiplyTexture = extension.OutlineWidthMultiplyTexture.GetTexture(textures), + }; + var Emission = new EmissionDefinition + { + EmissionColor = material.emissiveFactor.ToLinearColor(Nan), + }; + if (material.emissiveTexture != null) + { + Emission.EmissionMultiplyTexture = material.emissiveTexture.index.GetTexture(textures); + } + + var Lighting = new LightingDefinition + { + LightingInfluence = new LightingInfluenceDefinition + { + GiIntensityValue = extension.GiIntensityFactor.Value, + LightColorAttenuationValue = extension.LightColorAttenuationFactor.Value, + }, + LitAndShadeMixing = new LitAndShadeMixingDefinition + { + ShadingShiftValue = extension.ShadingShiftFactor.Value, + ShadingToonyValue = extension.ShadingToonyFactor.Value, + }, + Normal = new NormalDefinition + { + }, + }; + if (material.normalTexture != null) + { + Lighting.Normal.NormalScaleValue = material.normalTexture.scale; + Lighting.Normal.NormalTexture = material.normalTexture.index.GetTexture(textures); + } + + var MatCap = new MatCapDefinition + { + AdditiveTexture = extension.AdditiveTexture.GetTexture(textures) + }; + var Rendering = new RenderingDefinition + { + CullMode = material.doubleSided ? CullMode.Off : CullMode.Back, + RenderMode = GetRenderMode(material.alphaMode, extension.TransparentWithZWrite.Value), + RenderQueueOffsetNumber = extension.RenderQueueOffsetNumber.Value, + }; + var Rim = new RimDefinition + { + RimColor = extension.RimFactor.ToLinearColor(Nan), + RimMultiplyTexture = extension.RimMultiplyTexture.GetTexture(textures), + RimLiftValue = extension.RimLiftFactor.Value, + RimFresnelPowerValue = extension.RimFresnelPowerFactor.Value, + RimLightingMixValue = extension.RimLightingMixFactor.Value, + }; + + var TextureOption = new TextureUvCoordsDefinition + { + UvAnimationMaskTexture = extension.UvAnimationMaskTexture.GetTexture(textures), + UvAnimationRotationSpeedValue = extension.UvAnimationRotationSpeedFactor.Value, + UvAnimationScrollXSpeedValue = extension.UvAnimationScrollXSpeedFactor.Value, + UvAnimationScrollYSpeedValue = extension.UvAnimationScrollYSpeedFactor.Value, + }; + + if (glTF_KHR_texture_transform.TryGet(material.pbrMetallicRoughness.baseColorTexture, out glTF_KHR_texture_transform t)) + { + TextureOption.MainTextureLeftBottomOriginOffset = t.offset.ToVector2(); + TextureOption.MainTextureLeftBottomOriginScale = t.scale.ToVector2(); + } + + mtoon.Definition = new MToonDefinition + { + Meta = Meta, + Color = Color, + Outline = Outline, + Emission = Emission, + Lighting = Lighting, + MatCap = MatCap, + Rendering = Rendering, + Rim = Rim, + TextureOption = TextureOption, + }; + + return mtoon; + } + + static (string, bool) GetRenderMode(RenderMode mode) + { + switch (mode) + { + case RenderMode.Opaque: return ("OPAQUE", false); + case RenderMode.Cutout: return ("MASK", false); + case RenderMode.Transparent: return ("BLEND", false); + case RenderMode.TransparentWithZWrite: return ("BLEND", true); + } + + throw new NotImplementedException(); + } + + public static glTFMaterial MToonToGltf(this MToonMaterial mtoon, List textures) + { + var material = mtoon.UnlitToGltf(textures); + + var dst = new VRMC_materials_mtoon(); + + // Color + material.pbrMetallicRoughness.baseColorFactor = mtoon.Definition.Color.LitColor.ToFloat4(); + if (mtoon.Definition.Color.LitMultiplyTexture != null) + { + material.pbrMetallicRoughness.baseColorTexture = new glTFMaterialBaseColorTextureInfo + { + index = mtoon.Definition.Color.LitMultiplyTexture.ToIndex(textures).Value + }; + } + dst.ShadeFactor = mtoon.Definition.Color.ShadeColor.ToFloat3(); + dst.ShadeMultiplyTexture = mtoon.Definition.Color.ShadeMultiplyTexture.ToIndex(textures); + material.alphaCutoff = mtoon.Definition.Color.CutoutThresholdValue; + + // Outline + dst.OutlineColorMode = (UniGLTF.Extensions.VRMC_materials_mtoon.OutlineColorMode)mtoon.Definition.Outline.OutlineColorMode; + dst.OutlineFactor = mtoon.Definition.Outline.OutlineColor.ToFloat3(); + dst.OutlineLightingMixFactor = mtoon.Definition.Outline.OutlineLightingMixValue; + dst.OutlineScaledMaxDistanceFactor = mtoon.Definition.Outline.OutlineScaledMaxDistanceValue; + dst.OutlineWidthMode = (UniGLTF.Extensions.VRMC_materials_mtoon.OutlineWidthMode)mtoon.Definition.Outline.OutlineWidthMode; + dst.OutlineWidthFactor = mtoon.Definition.Outline.OutlineWidthValue; + dst.OutlineWidthMultiplyTexture = mtoon.Definition.Outline.OutlineWidthMultiplyTexture.ToIndex(textures); + + // Emission + material.emissiveFactor = mtoon.Definition.Emission.EmissionColor.ToFloat3(); + if (mtoon.Definition.Emission.EmissionMultiplyTexture != null) + { + material.emissiveTexture = new glTFMaterialEmissiveTextureInfo + { + index = textures.IndexOfNullable(mtoon.Definition.Emission.EmissionMultiplyTexture.Texture).Value + }; + } + + // Light + dst.GiIntensityFactor = mtoon.Definition.Lighting.LightingInfluence.GiIntensityValue; + dst.LightColorAttenuationFactor = mtoon.Definition.Lighting.LightingInfluence.LightColorAttenuationValue; + dst.ShadingShiftFactor = mtoon.Definition.Lighting.LitAndShadeMixing.ShadingShiftValue; + dst.ShadingToonyFactor = mtoon.Definition.Lighting.LitAndShadeMixing.ShadingToonyValue; + if (mtoon.Definition.Lighting.Normal.NormalTexture != null) + { + material.normalTexture = new glTFMaterialNormalTextureInfo + { + index = textures.IndexOfNullable(mtoon.Definition.Lighting.Normal.NormalTexture.Texture).Value, + scale = mtoon.Definition.Lighting.Normal.NormalScaleValue + }; + } + + // matcap + dst.AdditiveTexture = mtoon.Definition.MatCap.AdditiveTexture.ToIndex(textures); + + // rendering + switch (mtoon.Definition.Rendering.CullMode) + { + case CullMode.Back: + material.doubleSided = false; + break; + + case CullMode.Off: + material.doubleSided = true; + break; + + case CullMode.Front: + // GLTF not support + material.doubleSided = false; + break; + + default: + throw new NotImplementedException(); + } + (material.alphaMode, dst.TransparentWithZWrite) = GetRenderMode(mtoon.Definition.Rendering.RenderMode); + dst.RenderQueueOffsetNumber = mtoon.Definition.Rendering.RenderQueueOffsetNumber; + + // rim + dst.RimFactor = mtoon.Definition.Rim.RimColor.ToFloat3(); + dst.RimMultiplyTexture = mtoon.Definition.Rim.RimMultiplyTexture.ToIndex(textures); + dst.RimLiftFactor = mtoon.Definition.Rim.RimLiftValue; + dst.RimFresnelPowerFactor = mtoon.Definition.Rim.RimFresnelPowerValue; + dst.RimLightingMixFactor = mtoon.Definition.Rim.RimLightingMixValue; + + // texture option + dst.UvAnimationMaskTexture = mtoon.Definition.TextureOption.UvAnimationMaskTexture.ToIndex(textures); + dst.UvAnimationRotationSpeedFactor = mtoon.Definition.TextureOption.UvAnimationRotationSpeedValue; + dst.UvAnimationScrollXSpeedFactor = mtoon.Definition.TextureOption.UvAnimationScrollXSpeedValue; + dst.UvAnimationScrollYSpeedFactor = mtoon.Definition.TextureOption.UvAnimationScrollYSpeedValue; + if (material.pbrMetallicRoughness.baseColorTexture != null) + { + var offset = mtoon.Definition.TextureOption.MainTextureLeftBottomOriginOffset; + var scale = mtoon.Definition.TextureOption.MainTextureLeftBottomOriginScale; + glTF_KHR_texture_transform.Serialize( + material.pbrMetallicRoughness.baseColorTexture, + (offset.X, offset.Y), + (scale.X, scale.Y) + ); + } + + UniGLTF.Extensions.VRMC_materials_mtoon.GltfSerializer.SerializeTo(ref material.extensions, dst); + + return material; + } + } +} diff --git a/Assets/VRM10/Runtime/IO/MToonAdapter.cs.meta b/Assets/VRM10/Runtime/IO/MToonAdapter.cs.meta new file mode 100644 index 000000000..363b4ce47 --- /dev/null +++ b/Assets/VRM10/Runtime/IO/MToonAdapter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dfbed4ddbad0e1941862133ed8219c4c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/IO/MToonUtilsFromDefinition.cs b/Assets/VRM10/Runtime/IO/MToonUtilsFromDefinition.cs new file mode 100644 index 000000000..06cdb74c6 --- /dev/null +++ b/Assets/VRM10/Runtime/IO/MToonUtilsFromDefinition.cs @@ -0,0 +1,312 @@ +using Color = System.Numerics.Vector4; +using Material = UniGLTF.glTFMaterial; +using System.Collections.Generic; +using VrmLib; +using VrmLib.MToon; +using System; + +namespace UniVRM10 +{ + public static partial class Utils + { + public static void ToProtobuf(this LinearColor color, Action add, bool hasAlpha) + { + add(color.RGBA.X); + add(color.RGBA.Y); + add(color.RGBA.Z); + if (hasAlpha) + { + add(color.RGBA.W); + } + } + + // public static void SetMToonParametersToMaterial(Material material, MToonDefinition parameters, List textures) + // { + // var mtoon = material.Extensions.VRMCMaterialsMtoon; + + // { + // var meta = parameters.Meta; + // mtoon.Version = meta.VersionNumber.ToString(); + // } + // // TODO: + // // { + // // var rendering = parameters.Rendering; + // // ValidateBlendMode(material, rendering.RenderMode, isChangedByUser: true); + // // ValidateCullMode(material, rendering.CullMode); + // // ValidateRenderQueue(material, offset: rendering.RenderQueueOffsetNumber); + // // } + // { + // var color = parameters.Color; + + // // SetColor(material, MToonUtils.PropColor, color.LitColor); + // color.LitColor.ToProtobuf(mtoon.LitFactor.Add, true); + + // // SetTexture(material, MToonUtils.PropMainTex, color.LitMultiplyTexture, textures); + // mtoon.LitMultiplyTexture = textures.IndexOfNullable(color.LitMultiplyTexture.Texture); + + // // SetColor(material, MToonUtils.PropShadeColor, color.ShadeColor); + // color.ShadeColor.ToProtobuf(mtoon.ShadeFactor.Add, false); + + // // SetTexture(material, MToonUtils.PropShadeTexture, color.ShadeMultiplyTexture, textures); + // mtoon.ShadeMultiplyTexture = textures.IndexOfNullable(color.ShadeMultiplyTexture.Texture); + + // // SetValue(material, MToonUtils.PropCutoff, color.CutoutThresholdValue); + // mtoon.CutoutThresholdFactor = color.CutoutThresholdValue; + // } + // { + // var lighting = parameters.Lighting; + // { + // var prop = lighting.LitAndShadeMixing; + // // SetValue(material, MToonUtils.PropShadeShift, prop.ShadingShiftValue); + // mtoon.ShadingShiftFactor = prop.ShadingShiftValue; + + // // SetValue(material, MToonUtils.PropShadeToony, prop.ShadingToonyValue); + // mtoon.ShadingToonyFactor = prop.ShadingToonyValue; + // } + // { + // var prop = lighting.LightingInfluence; + // // SetValue(material, MToonUtils.PropLightColorAttenuation, prop.LightColorAttenuationValue); + // mtoon.LightColorAttenuationFactor = prop.LightColorAttenuationValue; + + // // SetValue(material, MToonUtils.PropIndirectLightIntensity, prop.GiIntensityValue); + // mtoon.GiIntensityFactor = prop.GiIntensityValue; + // } + // { + // var prop = lighting.Normal; + // // SetTexture(material, MToonUtils.PropBumpMap, prop.NormalTexture, textures); + // mtoon.NormalTexture = textures.IndexOfNullable(prop.NormalTexture.Texture); + + // // SetValue(material, MToonUtils.PropBumpScale, prop.NormalScaleValue); + // mtoon.NormalScaleFactor = prop.NormalScaleValue; + // } + // } + // { + // var emission = parameters.Emission; + // // SetColor(material, MToonUtils.PropEmissionColor, emission.EmissionColor); + // emission.EmissionColor.ToProtobuf(mtoon.EmissionFactor.Add, false); + + // // SetTexture(material, MToonUtils.PropEmissionMap, emission.EmissionMultiplyTexture, textures); + // mtoon.EmissionMultiplyTexture = textures.IndexOfNullable(emission.EmissionMultiplyTexture.Texture); + // } + // { + // var matcap = parameters.MatCap; + // // SetTexture(material, MToonUtils.PropSphereAdd, matcap.AdditiveTexture, textures); + // mtoon.AdditiveTexture = textures.IndexOfNullable(matcap.AdditiveTexture.Texture); + // } + // { + // var rim = parameters.Rim; + // // SetColor(material, MToonUtils.PropRimColor, rim.RimColor); + // rim.RimColor.ToProtobuf(mtoon.RimFactor.Add, false); + + // // SetTexture(material, MToonUtils.PropRimTexture, rim.RimMultiplyTexture, textures); + // mtoon.RimMultiplyTexture = textures.IndexOfNullable(rim.RimMultiplyTexture.Texture); + + // // SetValue(material, MToonUtils.PropRimLightingMix, rim.RimLightingMixValue); + // mtoon.RimLightingMixFactor = rim.RimLightingMixValue; + + // // SetValue(material, MToonUtils.PropRimFresnelPower, rim.RimFresnelPowerValue); + // mtoon.RimFresnelPowerFactor = rim.RimFresnelPowerValue; + + // // SetValue(material, MToonUtils.PropRimLift, rim.RimLiftValue); + // mtoon.RimLiftFactor = rim.RimLiftValue; + // } + // { + // var outline = parameters.Outline; + // // SetValue(material, MToonUtils.PropOutlineWidth, outline.OutlineWidthValue); + // mtoon.OutlineWidthFactor = outline.OutlineWidthValue; + + // // SetTexture(material, MToonUtils.PropOutlineWidthTexture, outline.OutlineWidthMultiplyTexture, textures); + // mtoon.OutlineWidthMultiplyTexture = textures.IndexOfNullable(outline.OutlineWidthMultiplyTexture.Texture); + + // // SetValue(material, MToonUtils.PropOutlineScaledMaxDistance, outline.OutlineScaledMaxDistanceValue); + // mtoon.OutlineScaledMaxDistanceFactor = outline.OutlineScaledMaxDistanceValue; + + // // SetColor(material, MToonUtils.PropOutlineColor, outline.OutlineColor); + // outline.OutlineColor.ToProtobuf(mtoon.OutlineFactor.Add, false); + + // // SetValue(material, MToonUtils.PropOutlineLightingMix, outline.OutlineLightingMixValue); + // mtoon.OutlineLightingMixFactor = outline.OutlineLightingMixValue; + + // // ValidateOutlineMode(material, outline.OutlineWidthMode, outline.OutlineColorMode); + // } + // { + // var textureOptions = parameters.TextureOption; + // // TODO: + // // material.SetTextureScale(MToonUtils.PropMainTex, textureOptions.MainTextureLeftBottomOriginScale); + // // material.SetTextureOffset(MToonUtils.PropMainTex, textureOptions.MainTextureLeftBottomOriginOffset); + + // // material.SetTexture(MToonUtils.PropUvAnimMaskTexture, textureOptions.UvAnimationMaskTexture, textures); + // mtoon.UvAnimationMaskTexture = textures.IndexOfNullable(textureOptions.UvAnimationMaskTexture.Texture); + + // // material.SetFloat(MToonUtils.PropUvAnimScrollX, textureOptions.UvAnimationScrollXSpeedValue); + // mtoon.UvAnimationScrollXSpeedFactor = textureOptions.UvAnimationScrollXSpeedValue; + + // // material.SetFloat(MToonUtils.PropUvAnimScrollY, textureOptions.UvAnimationScrollYSpeedValue); + // mtoon.UvAnimationScrollYSpeedFactor = textureOptions.UvAnimationScrollYSpeedValue; + + // // material.SetFloat(MToonUtils.PropUvAnimRotation, textureOptions.UvAnimationRotationSpeedValue); + // mtoon.UvAnimationRotationSpeedFactor = textureOptions.UvAnimationRotationSpeedValue; + // } + // } + + // /// + // /// Validate properties and Set hidden properties, keywords. + // /// if isBlendModeChangedByUser is true, renderQueue will set specified render mode's default value. + // /// + // /// + // /// + // public static void ValidateProperties(Material material, List textures, bool isBlendModeChangedByUser = false) + // { + // ValidateBlendMode(material, (RenderMode)material.GetFloat(MToonUtils.PropBlendMode), isBlendModeChangedByUser); + // ValidateNormalMode(material, material.GetTexture(MToonUtils.PropBumpMap, textures) != null); + // ValidateOutlineMode(material, + // (OutlineWidthMode)material.GetFloat(MToonUtils.PropOutlineWidthMode), + // (OutlineColorMode)material.GetFloat(MToonUtils.PropOutlineColorMode)); + // ValidateDebugMode(material, (DebugMode)material.GetFloat(MToonUtils.PropDebugMode)); + // ValidateCullMode(material, (CullMode)material.GetFloat(MToonUtils.PropCullMode)); + + // var mainTex = material.GetTexture(MToonUtils.PropMainTex, textures); + // var shadeTex = material.GetTexture(MToonUtils.PropShadeTexture, textures); + // if (mainTex != null && shadeTex == null) + // { + // material.SetTexture(MToonUtils.PropShadeTexture, mainTex, textures); + // } + // } + + // private static void ValidateDebugMode(Material material, DebugMode debugMode) + // { + // switch (debugMode) + // { + // case DebugMode.None: + // SetKeyword(material, MToonUtils.KeyDebugNormal, false); + // SetKeyword(material, MToonUtils.KeyDebugLitShadeRate, false); + // break; + // case DebugMode.Normal: + // SetKeyword(material, MToonUtils.KeyDebugNormal, true); + // SetKeyword(material, MToonUtils.KeyDebugLitShadeRate, false); + // break; + // case DebugMode.LitShadeRate: + // SetKeyword(material, MToonUtils.KeyDebugNormal, false); + // SetKeyword(material, MToonUtils.KeyDebugLitShadeRate, true); + // break; + // } + // } + + // public static void ValidateBlendMode(Material material, RenderMode renderMode, bool isChangedByUser) + // { + // switch (renderMode) + // { + // case RenderMode.Opaque: + // material.SetOverrideTag(MToonUtils.TagRenderTypeKey, MToonUtils.TagRenderTypeValueOpaque); + // material.SetInt(MToonUtils.PropSrcBlend, BlendMode.One); + // material.SetInt(MToonUtils.PropDstBlend, BlendMode.Zero); + // material.SetInt(MToonUtils.PropZWrite, MToonUtils.EnabledIntValue); + // material.SetInt(MToonUtils.PropAlphaToMask, MToonUtils.DisabledIntValue); + // SetKeyword(material, MToonUtils.KeyAlphaTestOn, false); + // SetKeyword(material, MToonUtils.KeyAlphaBlendOn, false); + // SetKeyword(material, MToonUtils.KeyAlphaPremultiplyOn, false); + // break; + // case RenderMode.Cutout: + // material.SetOverrideTag(MToonUtils.TagRenderTypeKey, MToonUtils.TagRenderTypeValueTransparentCutout); + // material.SetInt(MToonUtils.PropSrcBlend, BlendMode.One); + // material.SetInt(MToonUtils.PropDstBlend, BlendMode.Zero); + // material.SetInt(MToonUtils.PropZWrite, MToonUtils.EnabledIntValue); + // material.SetInt(MToonUtils.PropAlphaToMask, MToonUtils.EnabledIntValue); + // SetKeyword(material, MToonUtils.KeyAlphaTestOn, true); + // SetKeyword(material, MToonUtils.KeyAlphaBlendOn, false); + // SetKeyword(material, MToonUtils.KeyAlphaPremultiplyOn, false); + // break; + // case RenderMode.Transparent: + // material.SetOverrideTag(MToonUtils.TagRenderTypeKey, MToonUtils.TagRenderTypeValueTransparent); + // material.SetInt(MToonUtils.PropSrcBlend, BlendMode.SrcAlpha); + // material.SetInt(MToonUtils.PropDstBlend, BlendMode.OneMinusSrcAlpha); + // material.SetInt(MToonUtils.PropZWrite, MToonUtils.DisabledIntValue); + // material.SetInt(MToonUtils.PropAlphaToMask, MToonUtils.DisabledIntValue); + // SetKeyword(material, MToonUtils.KeyAlphaTestOn, false); + // SetKeyword(material, MToonUtils.KeyAlphaBlendOn, true); + // SetKeyword(material, MToonUtils.KeyAlphaPremultiplyOn, false); + // break; + // case RenderMode.TransparentWithZWrite: + // material.SetOverrideTag(MToonUtils.TagRenderTypeKey, MToonUtils.TagRenderTypeValueTransparent); + // material.SetInt(MToonUtils.PropSrcBlend, BlendMode.SrcAlpha); + // material.SetInt(MToonUtils.PropDstBlend, BlendMode.OneMinusSrcAlpha); + // material.SetInt(MToonUtils.PropZWrite, MToonUtils.EnabledIntValue); + // material.SetInt(MToonUtils.PropAlphaToMask, MToonUtils.DisabledIntValue); + // SetKeyword(material, MToonUtils.KeyAlphaTestOn, false); + // SetKeyword(material, MToonUtils.KeyAlphaBlendOn, true); + // SetKeyword(material, MToonUtils.KeyAlphaPremultiplyOn, false); + // break; + // } + + // if (isChangedByUser) + // { + // // ValidateRenderQueue(material, offset: 0); + // } + // else + // { + // var requirement = MToonUtils.GetRenderQueueRequirement(renderMode); + // // ValidateRenderQueue(material, offset: material.renderQueue - requirement.DefaultValue); + // } + // } + + // private static void ValidateRenderQueue(Material material, int offset) + // { + // var requirement = MToonUtils.GetRenderQueueRequirement(GetBlendMode(material)); + // var value = Mathf.Clamp(requirement.DefaultValue + offset, requirement.MinValue, requirement.MaxValue); + // material.renderQueue = value; + // } + + // private static void ValidateOutlineMode(Material material, OutlineWidthMode outlineWidthMode, + // OutlineColorMode outlineColorMode) + // { + // var isFixed = outlineColorMode == OutlineColorMode.FixedColor; + // var isMixed = outlineColorMode == OutlineColorMode.MixedLighting; + + // switch (outlineWidthMode) + // { + // case OutlineWidthMode.None: + // SetKeyword(material, MToonUtils.KeyOutlineWidthWorld, false); + // SetKeyword(material, MToonUtils.KeyOutlineWidthScreen, false); + // SetKeyword(material, MToonUtils.KeyOutlineColorFixed, false); + // SetKeyword(material, MToonUtils.KeyOutlineColorMixed, false); + // break; + // case OutlineWidthMode.WorldCoordinates: + // SetKeyword(material, MToonUtils.KeyOutlineWidthWorld, true); + // SetKeyword(material, MToonUtils.KeyOutlineWidthScreen, false); + // SetKeyword(material, MToonUtils.KeyOutlineColorFixed, isFixed); + // SetKeyword(material, MToonUtils.KeyOutlineColorMixed, isMixed); + // break; + // case OutlineWidthMode.ScreenCoordinates: + // SetKeyword(material, MToonUtils.KeyOutlineWidthWorld, false); + // SetKeyword(material, MToonUtils.KeyOutlineWidthScreen, true); + // SetKeyword(material, MToonUtils.KeyOutlineColorFixed, isFixed); + // SetKeyword(material, MToonUtils.KeyOutlineColorMixed, isMixed); + // break; + // } + // } + + // private static void ValidateNormalMode(Material material, bool requireNormalMapping) + // { + // SetKeyword(material, MToonUtils.KeyNormalMap, requireNormalMapping); + // } + + // private static void ValidateCullMode(Material material, CullMode cullMode) + // { + // switch (cullMode) + // { + // case CullMode.Back: + // material.SetInt(MToonUtils.PropCullMode, CullMode.Back); + // material.SetInt(MToonUtils.PropOutlineCullMode, CullMode.Front); + // break; + // case CullMode.Front: + // material.SetInt(MToonUtils.PropCullMode, CullMode.Front); + // material.SetInt(MToonUtils.PropOutlineCullMode, CullMode.Back); + // break; + // case CullMode.Off: + // material.SetInt(MToonUtils.PropCullMode, CullMode.Off); + // material.SetInt(MToonUtils.PropOutlineCullMode, CullMode.Front); + // break; + // } + // } + } +} \ No newline at end of file diff --git a/Assets/VRM10/Runtime/IO/MToonUtilsFromDefinition.cs.meta b/Assets/VRM10/Runtime/IO/MToonUtilsFromDefinition.cs.meta new file mode 100644 index 000000000..22f9e9fba --- /dev/null +++ b/Assets/VRM10/Runtime/IO/MToonUtilsFromDefinition.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 269eb140bd3b26043a6afac332268766 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/IO/MaterialAdapter.cs b/Assets/VRM10/Runtime/IO/MaterialAdapter.cs new file mode 100644 index 000000000..d931fa903 --- /dev/null +++ b/Assets/VRM10/Runtime/IO/MaterialAdapter.cs @@ -0,0 +1,230 @@ +using VrmLib; +using System.Collections.Generic; +using System.Numerics; +using UniJSON; +using UniGLTF; +using System; + +namespace UniVRM10 +{ + public static class MaterialAdapter + { + public static Material FromGltf(this glTFMaterial x, List textures) + { + if (UniGLTF.Extensions.VRMC_materials_mtoon.GltfDeserializer.TryGet(x.extensions, + out UniGLTF.Extensions.VRMC_materials_mtoon.VRMC_materials_mtoon mtoon)) + { + // mtoon + return MToonAdapter.MToonFromGltf(x, textures, mtoon); + } + + if (glTF_KHR_materials_unlit.IsEnable(x)) + { + // unlit + return UnlitFromGltf(x, textures); + } + + // PBR + return PBRFromGltf(x, textures); + } + + public static void LoadCommonParams(this Material self, glTFMaterial material, List textures) + { + var pbr = material.pbrMetallicRoughness; + if (pbr.baseColorFactor != null) + { + self.BaseColorFactor = LinearColor.FromLiner(pbr.baseColorFactor); + } + var baseColorTexture = pbr.baseColorTexture; + if (baseColorTexture != null && baseColorTexture.index.TryGetValidIndex(textures.Count, out int index)) + { + self.BaseColorTexture = new TextureInfo(textures[index]); + } + + self.AlphaMode = EnumUtil.Parse(material.alphaMode); + self.AlphaCutoff = material.alphaCutoff; + self.DoubleSided = material.doubleSided; + } + + public static PBRMaterial PBRFromGltf(glTFMaterial material, List textures) + { + var self = new PBRMaterial(material.name); + + self.LoadCommonParams(material, textures); + + // + // pbr + // + var pbr = material.pbrMetallicRoughness; + + // metallic roughness + self.MetallicFactor = pbr.metallicFactor; + self.RoughnessFactor = pbr.roughnessFactor; + var metallicRoughnessTexture = pbr.metallicRoughnessTexture; + if (metallicRoughnessTexture != null + && metallicRoughnessTexture.index.TryGetValidIndex(textures.Count, out int metallicRoughnessTextureIndex)) + { + self.MetallicRoughnessTexture = textures[metallicRoughnessTextureIndex]; + } + // + // emissive + // + if (material.emissiveFactor != null) + { + self.EmissiveFactor = new Vector3( + material.emissiveFactor[0], + material.emissiveFactor[1], + material.emissiveFactor[2]); + } + var emissiveTexture = material.emissiveTexture; + if (emissiveTexture != null + && emissiveTexture.index.TryGetValidIndex(textures.Count, out int emissiveTextureIndex)) + { + self.EmissiveTexture = textures[emissiveTextureIndex]; + } + // + // normal + // + var normalTexture = material.normalTexture; + if (normalTexture != null + && normalTexture.index.TryGetValidIndex(textures.Count, out int normalTextureIndex)) + { + self.NormalTexture = textures[normalTextureIndex]; + } + // + // occlusion + // + var occlusionTexture = material.occlusionTexture; + if (occlusionTexture != null + && occlusionTexture.index.TryGetValidIndex(textures.Count, out int occlusionTextureIndex)) + { + self.OcclusionTexture = textures[occlusionTextureIndex]; + } + + return self; + } + + public static UnlitMaterial UnlitFromGltf(glTFMaterial material, List textures) + { + var unlit = new UnlitMaterial(material.name); + unlit.LoadCommonParams(material, textures); + return unlit; + } + + static string CastAlphaMode(VrmLib.AlphaModeType alphaMode) + { + if (alphaMode == AlphaModeType.BLEND_ZWRITE) + { + return "BLEND"; + } + return alphaMode.ToString(); + } + + static glTFMaterial ToGltf(this VrmLib.Material src, List textures) + { + var material = new glTFMaterial + { + name = src.Name, + pbrMetallicRoughness = new glTFPbrMetallicRoughness + { + baseColorFactor = src.BaseColorFactor.ToFloat4(), + }, + alphaMode = CastAlphaMode(src.AlphaMode), + alphaCutoff = src.AlphaCutoff, + doubleSided = src.DoubleSided, + }; + if (src.BaseColorTexture != null) + { + material.pbrMetallicRoughness.baseColorTexture = new glTFMaterialBaseColorTextureInfo + { + index = textures.IndexOfNullable(src.BaseColorTexture.Texture).Value, + }; + } + return material; + } + + public static glTFMaterial PBRToGltf(this PBRMaterial pbr, List textures) + { + var material = pbr.ToGltf(textures); + + // MetallicRoughness + material.pbrMetallicRoughness.baseColorFactor = pbr.BaseColorFactor.ToFloat4(); + if (pbr.BaseColorTexture != null) + { + material.pbrMetallicRoughness.baseColorTexture = new glTFMaterialBaseColorTextureInfo + { + index = textures.IndexOfNullable(pbr.BaseColorTexture.Texture).Value, + }; + } + material.pbrMetallicRoughness.metallicFactor = pbr.MetallicFactor; + material.pbrMetallicRoughness.roughnessFactor = pbr.RoughnessFactor; + if (pbr.MetallicRoughnessTexture != null) + { + material.pbrMetallicRoughness.metallicRoughnessTexture = new glTFMaterialMetallicRoughnessTextureInfo + { + index = textures.IndexOfNullable(pbr.MetallicRoughnessTexture).Value, + }; + } + + // Normal + if (pbr.NormalTexture != null) + { + material.normalTexture = new glTFMaterialNormalTextureInfo + { + index = textures.IndexOfNullable(pbr.NormalTexture).Value, + scale = pbr.NormalTextureScale + }; + } + + // Occlusion + if (pbr.OcclusionTexture != null) + { + material.occlusionTexture = new glTFMaterialOcclusionTextureInfo + { + index = textures.IndexOfNullable(pbr.OcclusionTexture).Value, + strength = pbr.OcclusionTextureStrength, + }; + } + + // Emissive + if (pbr.EmissiveTexture != null) + { + material.emissiveTexture = new glTFMaterialEmissiveTextureInfo + { + index = textures.IndexOfNullable(pbr.EmissiveTexture).Value, + }; + } + material.emissiveFactor = pbr.EmissiveFactor.ToFloat3(); + + // AlphaMode + material.alphaMode = CastAlphaMode(pbr.AlphaMode); + + // AlphaCutoff + material.alphaCutoff = pbr.AlphaCutoff; + + // DoubleSided + material.doubleSided = pbr.DoubleSided; + + return material; + } + + public static glTFMaterial UnlitToGltf(this UnlitMaterial unlit, List textures) + { + var material = unlit.ToGltf(textures); + + if (!(material.extensions is glTFExtensionExport extensions)) + { + extensions = new glTFExtensionExport(); + material.extensions = extensions; + } + extensions.Add( + glTF_KHR_materials_unlit.ExtensionName, + new ArraySegment(glTF_KHR_materials_unlit.Raw)); + + material.pbrMetallicRoughness.roughnessFactor = 0.9f; + material.pbrMetallicRoughness.metallicFactor = 0.0f; + + return material; + } + } +} diff --git a/Assets/VRM10/Runtime/IO/MaterialAdapter.cs.meta b/Assets/VRM10/Runtime/IO/MaterialAdapter.cs.meta new file mode 100644 index 000000000..9d7888645 --- /dev/null +++ b/Assets/VRM10/Runtime/IO/MaterialAdapter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 82c26e1eaa170df4a876714ae8e73046 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/IO/MeshAdapter.cs b/Assets/VRM10/Runtime/IO/MeshAdapter.cs new file mode 100644 index 000000000..23f3eab66 --- /dev/null +++ b/Assets/VRM10/Runtime/IO/MeshAdapter.cs @@ -0,0 +1,402 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Runtime.InteropServices; +using UniGLTF; +using VrmLib; + +namespace UniVRM10 +{ + public static class MeshAdapter + { + /// + /// VertexBufferはひとつでIndexBufferの参照が異なる + /// + /// VertexBuffer + /// +----------------------------------+ + /// | | + /// +----------------------------------+ + /// A A A + /// | | | + /// +---------+--------+--------+ + /// | submesh0|submesh1|submesh2| + /// +---------+--------+--------+ + /// IndexBuffer + /// + public static Mesh SharedBufferFromGltf(this glTFMesh x, Vrm10Storage storage) + { + // 先頭を使う + return FromGltf(storage, x, x.primitives[0], true); + } + + /// + /// IndexBuffer毎に異なるVertexBufferを参照する + /// + /// VertexBuffer + /// +--------+ +--------+ +--------+ + /// |0 | |1 | |2 | + /// +--------+ +--------+ +--------+ + /// A A A + /// | | | + /// +---------+--------+--------+ + /// | submesh0|submesh1|submesh2| + /// +---------+--------+--------+ + /// IndexBuffer + /// + public static Mesh FromGltf(this glTFPrimitives primitive, Vrm10Storage storage, glTFMesh x) + { + return FromGltf(storage, x, primitive, false); + } + + static Mesh FromGltf(Vrm10Storage storage, glTFMesh x, glTFPrimitives primitive, bool isShared) + { + var mesh = new Mesh((TopologyType)primitive.mode) + { + VertexBuffer = primitive.attributes.FromGltf(storage) + }; + + if (isShared) + { + // create joined index buffer + mesh.IndexBuffer = storage.CreateAccessor(x.primitives.Select(y => y.indices).ToArray()); + } + else + { + mesh.IndexBuffer = storage.CreateAccessor(primitive.indices); + } + + { + gltf_mesh_extras_targetNames.TryGet(x, out List targetNames); + + for (int i = 0; i < primitive.targets.Count; ++i) + { + var gltfTarget = primitive.targets[i]; + + string targetName = null; + { + targetName = targetNames[i]; + } + var target = new MorphTarget(targetName) + { + VertexBuffer = gltfTarget.FromGltf(storage) + }; + + // validate count + foreach (var kv in target.VertexBuffer) + { + if (kv.Value.Count != mesh.VertexBuffer.Count) + { + throw new Exception(); + } + } + + mesh.MorphTargets.Add(target); + } + } + + return mesh; + } + + public static VertexBuffer FromGltf(this glTFAttributes attributes, + Vrm10Storage storage) + { + var b = new VertexBuffer(); + + if (storage.TryCreateAccessor(attributes.POSITION, out BufferAccessor position)) + { + b.Add(VertexBuffer.PositionKey, position); + } + else + { + // position required + throw new Exception(); + } + + if (storage.TryCreateAccessor(attributes.NORMAL, out BufferAccessor normal)) b.Add(VertexBuffer.NormalKey, normal); + if (storage.TryCreateAccessor(attributes.COLOR_0, out BufferAccessor color)) b.Add(VertexBuffer.ColorKey, color); + if (storage.TryCreateAccessor(attributes.TEXCOORD_0, out BufferAccessor tex0)) b.Add(VertexBuffer.TexCoordKey, tex0); + if (storage.TryCreateAccessor(attributes.TEXCOORD_1, out BufferAccessor tex1)) b.Add(VertexBuffer.TexCoordKey2, tex1); + // if(storage.TryCreateAccessor(attributes.TANGENT, out BufferAccessor tangent))b.Add(VertexBuffer.TangentKey, tangent); + if (storage.TryCreateAccessor(attributes.WEIGHTS_0, out BufferAccessor weights)) b.Add(VertexBuffer.WeightKey, weights); + if (storage.TryCreateAccessor(attributes.JOINTS_0, out BufferAccessor joints)) b.Add(VertexBuffer.JointKey, joints); + + return b; + } + + public static VertexBuffer FromGltf(this gltfMorphTarget target, Vrm10Storage storage) + { + var b = new VertexBuffer(); + storage.CreateBufferAccessorAndAdd(target.POSITION, b, VertexBuffer.PositionKey); + storage.CreateBufferAccessorAndAdd(target.NORMAL, b, VertexBuffer.NormalKey); + storage.CreateBufferAccessorAndAdd(target.TANGENT, b, VertexBuffer.TangentKey); + return b; + } + + public static bool HasSameVertexBuffer(this glTFPrimitives lhs, glTFPrimitives rhs) + { + if (lhs.attributes.POSITION != rhs.attributes.POSITION) return false; + if (lhs.attributes.NORMAL != rhs.attributes.NORMAL) return false; + if (lhs.attributes.TEXCOORD_0 != rhs.attributes.TEXCOORD_0) return false; + if (lhs.attributes.TEXCOORD_1 != rhs.attributes.TEXCOORD_1) return false; + if (lhs.attributes.COLOR_0 != rhs.attributes.COLOR_0) return false; + if (lhs.attributes.WEIGHTS_0 != rhs.attributes.WEIGHTS_0) return false; + if (lhs.attributes.JOINTS_0 != rhs.attributes.JOINTS_0) return false; + return true; + } + + public static bool AllPrimitivesHasSameVertexBuffer(this glTFMesh m) + { + if (m.primitives.Count <= 1) + { + return true; + } + + var first = m.primitives[0]; + for (int i = 1; i < m.primitives.Count; ++i) + { + if (!first.HasSameVertexBuffer(m.primitives[i])) + { + return false; + } + } + + return true; + } + + public static MeshGroup FromGltf(this glTFMesh x, + Vrm10Storage storage, List materials) + { + var group = new MeshGroup(x.name); + + if (x.primitives.Count == 1) + { + var primitive = x.primitives[0]; + var mesh = primitive.FromGltf(storage, x); + var materialIndex = primitive.material; + + mesh.Submeshes.Add( + new Submesh(0, mesh.IndexBuffer.Count, materials[materialIndex])); + + group.Meshes.Add(mesh); + } + else if (!x.AllPrimitivesHasSameVertexBuffer()) + { + int offset = 0; + foreach (var primitive in x.primitives) + { + var mesh = primitive.FromGltf(storage, x); + var materialIndex = primitive.material; + + mesh.Submeshes.Add( + new Submesh(offset, mesh.IndexBuffer.Count, materials[materialIndex])); + offset += mesh.IndexBuffer.Count; + + group.Meshes.Add(mesh); + } + } + else + { + // for VRM + + var mesh = x.SharedBufferFromGltf(storage); + int offset = 0; + foreach (var primitive in x.primitives) + { + var materialIndex = primitive.material; + var count = storage.Gltf.accessors[primitive.indices].count; + mesh.Submeshes.Add( + new Submesh(offset, count, materials[materialIndex])); + offset += count; + } + + group.Meshes.Add(mesh); + } + + return group; + } + + static void Vec3MinMax(ArraySegment bytes, glTFAccessor accessor) + { + var positions = SpanLike.Wrap(bytes); + var min = new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); + var max = new Vector3(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity); + foreach (var p in positions) + { + min = Vector3.Min(min, p); + max = Vector3.Max(max, p); + } + accessor.min = min.ToFloat3(); + accessor.max = max.ToFloat3(); + } + + static int ExportIndices(Vrm10Storage storage, BufferAccessor x, int offset, int count, ExportArgs option) + { + if (x.Count <= ushort.MaxValue) + { + if (x.ComponentType == AccessorValueType.UNSIGNED_INT) + { + // ensure ushort + var src = x.GetSpan().Slice(offset, count); + var bytes = new byte[src.Length * 2]; + var dst = SpanLike.Wrap(new ArraySegment(bytes)); + for (int i = 0; i < src.Length; ++i) + { + dst[i] = (ushort)src[i]; + } + var accessor = new BufferAccessor(new ArraySegment(bytes), AccessorValueType.UNSIGNED_SHORT, AccessorVectorType.SCALAR, count); + return accessor.AddAccessorTo(storage, 0, option.sparse, null, 0, count); + } + else + { + return x.AddAccessorTo(storage, 0, option.sparse, null, offset, count); + } + } + else + { + return x.AddAccessorTo(storage, 0, option.sparse, null, offset, count); + } + } + + static void ExportMesh(this Mesh mesh, List materials, Vrm10Storage storage, glTFMesh gltfMesh, ExportArgs option) + { + // + // primitive share vertex buffer + // + var attributeAccessorIndexMap = mesh.VertexBuffer + .ToDictionary( + kv => kv.Key, + kv => kv.Value.AddAccessorTo( + storage, 0, option.sparse, + kv.Key == VertexBuffer.PositionKey ? (Action, glTFAccessor>)Vec3MinMax : null + ) + ); + + List> morphTargetAccessorIndexMapList = null; + if (mesh.MorphTargets.Any()) + { + morphTargetAccessorIndexMapList = new List>(); + foreach (var morphTarget in mesh.MorphTargets) + { + var dict = new Dictionary(); + + foreach (var kv in morphTarget.VertexBuffer) + { + if (option.removeTangent && kv.Key == VertexBuffer.TangentKey) + { + // remove tangent + continue; + } + if (option.removeMorphNormal && kv.Key == VertexBuffer.NormalKey) + { + // normal normal + continue; + } + if (kv.Value.Count != mesh.VertexBuffer.Count) + { + throw new Exception("inavlid data"); + } + var accessorIndex = kv.Value.AddAccessorTo(storage, 0, + option.sparse, + kv.Key == VertexBuffer.PositionKey ? (Action, glTFAccessor>)Vec3MinMax : null); + dict.Add(kv.Key, accessorIndex); + } + + morphTargetAccessorIndexMapList.Add(dict); + } + } + + var drawCountOffset = 0; + foreach (var y in mesh.Submeshes) + { + // index + // slide index buffer accessor + var indicesAccessorIndex = ExportIndices(storage, mesh.IndexBuffer, drawCountOffset, y.DrawCount, option); + drawCountOffset += y.DrawCount; + + var prim = new glTFPrimitives + { + mode = (int)mesh.Topology, + material = materials.IndexOf(y.Material), + indices = indicesAccessorIndex, + attributes = new glTFAttributes(), + }; + gltfMesh.primitives.Add(prim); + + // attribute + foreach (var kv in mesh.VertexBuffer) + { + var attributeAccessorIndex = attributeAccessorIndexMap[kv.Key]; + + switch (kv.Key) + { + case VertexBuffer.PositionKey: prim.attributes.POSITION = attributeAccessorIndex; break; + case VertexBuffer.NormalKey: prim.attributes.NORMAL = attributeAccessorIndex; break; + case VertexBuffer.ColorKey: prim.attributes.COLOR_0 = attributeAccessorIndex; break; + case VertexBuffer.TexCoordKey: prim.attributes.TEXCOORD_0 = attributeAccessorIndex; break; + case VertexBuffer.TexCoordKey2: prim.attributes.TEXCOORD_1 = attributeAccessorIndex; break; + case VertexBuffer.JointKey: prim.attributes.JOINTS_0 = attributeAccessorIndex; break; + case VertexBuffer.WeightKey: prim.attributes.WEIGHTS_0 = attributeAccessorIndex; break; + } + } + + // morph target + if (mesh.MorphTargets.Any()) + { + foreach (var (t, accessorIndexMap) in + Enumerable.Zip(mesh.MorphTargets, morphTargetAccessorIndexMapList, (t, v) => (t, v))) + { + var target = new gltfMorphTarget(); + prim.targets.Add(target); + + foreach (var kv in t.VertexBuffer) + { + if (!accessorIndexMap.TryGetValue(kv.Key, out int targetAccessorIndex)) + { + continue; + } + switch (kv.Key) + { + case VertexBuffer.PositionKey: + target.POSITION = targetAccessorIndex; + break; + case VertexBuffer.NormalKey: + target.NORMAL = targetAccessorIndex; + break; + case VertexBuffer.TangentKey: + target.TANGENT = targetAccessorIndex; + break; + + default: + throw new NotImplementedException(); + } + } + } + + } + } + + // target name + if (mesh.MorphTargets.Any()) + { + gltf_mesh_extras_targetNames.Serialize(gltfMesh, mesh.MorphTargets.Select(z => z.Name)); + } + } + + public static glTFMesh ExportMeshGroup(this MeshGroup src, List materials, Vrm10Storage storage, ExportArgs option) + { + var mesh = new glTFMesh + { + name = src.Name + }; + + foreach (var x in src.Meshes) + { + // MeshとSubmeshがGltfのPrimitiveに相当する? + x.ExportMesh(materials, storage, mesh, option); + } + + return mesh; + } + } +} \ No newline at end of file diff --git a/Assets/VRM10/Runtime/IO/MeshAdapter.cs.meta b/Assets/VRM10/Runtime/IO/MeshAdapter.cs.meta new file mode 100644 index 000000000..7a639894c --- /dev/null +++ b/Assets/VRM10/Runtime/IO/MeshAdapter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 573f68f30d3f14a4cb8181b07981c6e0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/IO/ModelExtensions.cs b/Assets/VRM10/Runtime/IO/ModelExtensions.cs new file mode 100644 index 000000000..97d1cdabc --- /dev/null +++ b/Assets/VRM10/Runtime/IO/ModelExtensions.cs @@ -0,0 +1,20 @@ +using VrmLib; + +namespace UniVRM10 +{ + public static class ModelExtensions + { + public static byte[] ToGlb(this VrmLib.Model model) + { + // export vrm-1.0 + var exporter10 = new Vrm10Exporter(); + var option = new VrmLib.ExportArgs + { + // vrm = false + }; + var glbBytes10 = exporter10.Export(model, option); + var glb10 = VrmLib.Glb.Parse(glbBytes10); + return glb10.ToBytes(); + } + } +} diff --git a/Assets/VRM10/Runtime/IO/ModelExtensions.cs.meta b/Assets/VRM10/Runtime/IO/ModelExtensions.cs.meta new file mode 100644 index 000000000..44b50fa5b --- /dev/null +++ b/Assets/VRM10/Runtime/IO/ModelExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ba46a2b02671e7c4598e7fd12f9de81a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/IO/TextureAdapter.cs b/Assets/VRM10/Runtime/IO/TextureAdapter.cs new file mode 100644 index 000000000..289825d8f --- /dev/null +++ b/Assets/VRM10/Runtime/IO/TextureAdapter.cs @@ -0,0 +1,39 @@ +using VrmLib; +using System; +using System.Collections.Generic; +using UniGLTF; + +namespace UniVRM10 +{ + public static class TextureAdapter + { + public static ImageTexture FromGltf(this glTFTexture x, glTFTextureSampler sampler, List images, Texture.ColorSpaceTypes colorSpace, Texture.TextureTypes textureType) + { + var image = images[x.source]; + var name = !string.IsNullOrEmpty(x.name) ? x.name : image.Name; + return new ImageTexture(x.name, sampler.FromGltf(), image, colorSpace, textureType); + } + + public static TextureSampler FromGltf(this glTFTextureSampler sampler) + { + return new TextureSampler + { + WrapS = (TextureWrapType)sampler.wrapS, + WrapT = (TextureWrapType)sampler.wrapT, + MinFilter = (TextureMinFilterType)sampler.minFilter, + MagFilter = (TextureMagFilterType)sampler.magFilter, + }; + } + + public static glTFTextureSampler ToGltf(this TextureSampler src) + { + return new glTFTextureSampler + { + wrapS = (glWrap)src.WrapS, + wrapT = (glWrap)src.WrapT, + minFilter = (glFilter)src.MinFilter, + magFilter = (glFilter)src.MagFilter, + }; + } + } +} \ No newline at end of file diff --git a/Assets/VRM10/Runtime/IO/TextureAdapter.cs.meta b/Assets/VRM10/Runtime/IO/TextureAdapter.cs.meta new file mode 100644 index 000000000..f850f482d --- /dev/null +++ b/Assets/VRM10/Runtime/IO/TextureAdapter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7d307745b90c1da4297bba9f3ff4b325 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/IO/Vrm10Exporter.cs b/Assets/VRM10/Runtime/IO/Vrm10Exporter.cs new file mode 100644 index 000000000..00f4b22af --- /dev/null +++ b/Assets/VRM10/Runtime/IO/Vrm10Exporter.cs @@ -0,0 +1,321 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UniGLTF; +using UniJSON; +using VrmLib; + +namespace UniVRM10 +{ + public class Vrm10Exporter : IVrmExporter + { + public readonly Vrm10Storage Storage = new Vrm10Storage(); + + public readonly string VrmExtensionName = "VRMC_vrm"; + + public Vrm10Exporter() + { + Storage.Gltf.extensionsUsed.Add(glTF_KHR_materials_unlit.ExtensionName); + Storage.Gltf.extensionsUsed.Add(glTF_KHR_texture_transform.ExtensionName); + Storage.Gltf.extensionsUsed.Add(UniGLTF.Extensions.VRMC_vrm.VRMC_vrm.ExtensionName); + Storage.Gltf.extensionsUsed.Add(UniGLTF.Extensions.VRMC_materials_mtoon.VRMC_materials_mtoon.ExtensionName); + Storage.Gltf.extensionsUsed.Add(UniGLTF.Extensions.VRMC_springBone.VRMC_springBone.ExtensionName); + Storage.Gltf.extensionsUsed.Add(UniGLTF.Extensions.VRMC_node_collider.VRMC_node_collider.ExtensionName); + Storage.Gltf.extensionsUsed.Add(UniGLTF.Extensions.VRMC_constraints.VRMC_constraints.ExtensionName); + Storage.Gltf.buffers.Add(new glTFBuffer + { + + }); + } + + public byte[] ToBytes() + { + Storage.Gltf.buffers[0].byteLength = Storage.Buffers[0].Bytes.Count; + + var f = new JsonFormatter(); + UniGLTF.GltfSerializer.Serialize(f, Storage.Gltf); + var json = f.GetStoreBytes(); + + var glb = new VrmLib.Glb( + new VrmLib.GlbChunk(VrmLib.GlbChunkType.JSON, json), + new VrmLib.GlbChunk(VrmLib.GlbChunkType.BIN, Storage.Buffers[0].Bytes)); + return glb.ToBytes(); + } + + public void ExportAsset(Model model) + { + Storage.Gltf.asset = new glTFAssets + { + }; + if (!string.IsNullOrEmpty(model.AssetVersion)) Storage.Gltf.asset.version = model.AssetVersion; + if (!string.IsNullOrEmpty(model.AssetMinVersion)) Storage.Gltf.asset.minVersion = model.AssetMinVersion; + + if (!string.IsNullOrEmpty(model.AssetGenerator)) Storage.Gltf.asset.generator = model.AssetGenerator; + if (model.Vrm != null && !string.IsNullOrEmpty(model.Vrm.ExporterVersion)) + { + Storage.Gltf.asset.generator = model.Vrm.ExporterVersion; + } + + if (!string.IsNullOrEmpty(model.AssetCopyright)) Storage.Gltf.asset.copyright = model.AssetCopyright; + } + + public void Reserve(int bytesLength) + { + Storage.Reserve(bytesLength); + } + + public void ExportImageAndTextures(List images, List textures) + { + foreach (var x in images) + { + Storage.Gltf.images.Add(x.ToGltf(Storage)); + } + foreach (var x in textures) + { + if (x is ImageTexture imageTexture) + { + var samplerIndex = Storage.Gltf.samplers.Count; + Storage.Gltf.samplers.Add(x.Sampler.ToGltf()); + Storage.Gltf.textures.Add(new glTFTexture + { + name = x.Name, + source = images.IndexOfThrow(imageTexture.Image), + sampler = samplerIndex, + // extensions + // = imageTexture.Image.MimeType.Equals("image/webp") ? new GltfTextureExtensions() { EXT_texture_webp = new EXT_texture_webp() { source = images.IndexOf(imageTexture.Image) } } + // : imageTexture.Image.MimeType.Equals("image/vnd-ms.dds") ? new GltfTextureExtensions() { MSFT_texture_dds = new MSFT_texture_dds() { source = images.IndexOf(imageTexture.Image) } } + // : null + }); + } + else + { + throw new NotImplementedException(); + } + } + } + + public void ExportMaterialPBR(Material src, PBRMaterial pbr, List textures) + { + var material = pbr.PBRToGltf(textures); + Storage.Gltf.materials.Add(material); + } + + public void ExportMaterialUnlit(Material src, UnlitMaterial unlit, List textures) + { + var material = unlit.UnlitToGltf(textures); + Storage.Gltf.materials.Add(material); + if (!Storage.Gltf.extensionsUsed.Contains(UnlitMaterial.ExtensionName)) + { + Storage.Gltf.extensionsUsed.Add(UnlitMaterial.ExtensionName); + } + } + + public void ExportMaterialMToon(Material src, MToonMaterial mtoon, List textures) + { + if (!Storage.Gltf.extensionsUsed.Contains(UnlitMaterial.ExtensionName)) + { + Storage.Gltf.extensionsUsed.Add(UnlitMaterial.ExtensionName); + } + + var material = mtoon.MToonToGltf(textures); + Storage.Gltf.materials.Add(material); + if (!Storage.Gltf.extensionsUsed.Contains(MToonMaterial.ExtensionName)) + { + Storage.Gltf.extensionsUsed.Add(MToonMaterial.ExtensionName); + } + } + + public void ExportMeshes(List groups, List materials, ExportArgs option) + { + foreach (var group in groups) + { + var mesh = group.ExportMeshGroup(materials, Storage, option); + Storage.Gltf.meshes.Add(mesh); + } + } + + public void ExportNodes(Node root, List nodes, List groups, ExportArgs option) + { + foreach (var x in nodes) + { + var node = new glTFNode + { + name = x.Name, + }; + + node.translation = x.LocalTranslation.ToFloat3(); + node.rotation = x.LocalRotation.ToFloat4(); + node.scale = x.LocalScaling.ToFloat3(); + + if (x.MeshGroup != null) + { + node.mesh = groups.IndexOfThrow(x.MeshGroup); + var skin = x.MeshGroup.Skin; + if (skin != null) + { + var skinIndex = Storage.Gltf.skins.Count; + var gltfSkin = new glTFSkin() + { + joints = skin.Joints.Select(joint => nodes.IndexOfThrow(joint)).ToArray() + }; + if (skin.InverseMatrices == null) + { + skin.CalcInverseMatrices(); + } + if (skin.InverseMatrices != null) + { + gltfSkin.inverseBindMatrices = skin.InverseMatrices.AddAccessorTo(Storage, 0, option.sparse); + } + if (skin.Root != null) + { + gltfSkin.skeleton = nodes.IndexOf(skin.Root); + } + Storage.Gltf.skins.Add(gltfSkin); + node.skin = skinIndex; + } + } + + node.children = x.Children.Select(child => nodes.IndexOfThrow(child)).ToArray(); + + Storage.Gltf.nodes.Add(node); + } + + Storage.Gltf.scenes.Add(new gltfScene() + { + nodes = root.Children.Select(child => nodes.IndexOfThrow(child)).ToArray() + }); + } + + public void ExportAnimations(List animations, List nodes, ExportArgs option) + { + // throw new System.NotImplementedException(); + } + + public void ExportVrmMeta(Vrm src, List textures) + { + if (!Storage.Gltf.extensionsUsed.Contains(VrmExtensionName)) + { + Storage.Gltf.extensionsUsed.Add(VrmExtensionName); + } + + if (Storage.gltfVrm == null) + { + Storage.gltfVrm = new UniGLTF.Extensions.VRMC_vrm.VRMC_vrm(); + } + + Storage.gltfVrm.SpecVersion = src.SpecVersion; + Storage.gltfVrm.Meta = src.Meta.ToGltf(textures); + } + + public void ExportVrmHumanoid(Dictionary map, List nodes) + { + Storage.gltfVrm.Humanoid = new UniGLTF.Extensions.VRMC_vrm.Humanoid() + { + HumanBones = new UniGLTF.Extensions.VRMC_vrm.HumanBones(), + }; + foreach (var kv in map.OrderBy(kv => kv.Key)) + { + var humanoidBone = new UniGLTF.Extensions.VRMC_vrm.HumanBone + { + Node = nodes.IndexOfThrow(kv.Value), + }; + + switch (kv.Key) + { + case HumanoidBones.hips: Storage.gltfVrm.Humanoid.HumanBones.Hips = humanoidBone; break; + case HumanoidBones.leftUpperLeg: Storage.gltfVrm.Humanoid.HumanBones.LeftUpperLeg = humanoidBone; break; + case HumanoidBones.rightUpperLeg: Storage.gltfVrm.Humanoid.HumanBones.RightUpperLeg = humanoidBone; break; + case HumanoidBones.leftLowerLeg: Storage.gltfVrm.Humanoid.HumanBones.LeftLowerLeg = humanoidBone; break; + case HumanoidBones.rightLowerLeg: Storage.gltfVrm.Humanoid.HumanBones.RightLowerLeg = humanoidBone; break; + case HumanoidBones.leftFoot: Storage.gltfVrm.Humanoid.HumanBones.LeftFoot = humanoidBone; break; + case HumanoidBones.rightFoot: Storage.gltfVrm.Humanoid.HumanBones.RightFoot = humanoidBone; break; + case HumanoidBones.spine: Storage.gltfVrm.Humanoid.HumanBones.Spine = humanoidBone; break; + case HumanoidBones.chest: Storage.gltfVrm.Humanoid.HumanBones.Chest = humanoidBone; break; + case HumanoidBones.neck: Storage.gltfVrm.Humanoid.HumanBones.Neck = humanoidBone; break; + case HumanoidBones.head: Storage.gltfVrm.Humanoid.HumanBones.Head = humanoidBone; break; + case HumanoidBones.leftShoulder: Storage.gltfVrm.Humanoid.HumanBones.LeftShoulder = humanoidBone; break; + case HumanoidBones.rightShoulder: Storage.gltfVrm.Humanoid.HumanBones.RightShoulder = humanoidBone; break; + case HumanoidBones.leftUpperArm: Storage.gltfVrm.Humanoid.HumanBones.LeftUpperArm = humanoidBone; break; + case HumanoidBones.rightUpperArm: Storage.gltfVrm.Humanoid.HumanBones.RightUpperArm = humanoidBone; break; + case HumanoidBones.leftLowerArm: Storage.gltfVrm.Humanoid.HumanBones.LeftLowerArm = humanoidBone; break; + case HumanoidBones.rightLowerArm: Storage.gltfVrm.Humanoid.HumanBones.RightLowerArm = humanoidBone; break; + case HumanoidBones.leftHand: Storage.gltfVrm.Humanoid.HumanBones.LeftHand = humanoidBone; break; + case HumanoidBones.rightHand: Storage.gltfVrm.Humanoid.HumanBones.RightHand = humanoidBone; break; + case HumanoidBones.leftToes: Storage.gltfVrm.Humanoid.HumanBones.LeftToes = humanoidBone; break; + case HumanoidBones.rightToes: Storage.gltfVrm.Humanoid.HumanBones.RightToes = humanoidBone; break; + case HumanoidBones.leftEye: Storage.gltfVrm.Humanoid.HumanBones.LeftEye = humanoidBone; break; + case HumanoidBones.rightEye: Storage.gltfVrm.Humanoid.HumanBones.RightEye = humanoidBone; break; + case HumanoidBones.jaw: Storage.gltfVrm.Humanoid.HumanBones.Jaw = humanoidBone; break; + case HumanoidBones.leftThumbProximal: Storage.gltfVrm.Humanoid.HumanBones.LeftThumbProximal = humanoidBone; break; + case HumanoidBones.leftThumbIntermediate: Storage.gltfVrm.Humanoid.HumanBones.LeftThumbIntermediate = humanoidBone; break; + case HumanoidBones.leftThumbDistal: Storage.gltfVrm.Humanoid.HumanBones.LeftThumbDistal = humanoidBone; break; + case HumanoidBones.leftIndexProximal: Storage.gltfVrm.Humanoid.HumanBones.LeftIndexProximal = humanoidBone; break; + case HumanoidBones.leftIndexIntermediate: Storage.gltfVrm.Humanoid.HumanBones.LeftIndexIntermediate = humanoidBone; break; + case HumanoidBones.leftIndexDistal: Storage.gltfVrm.Humanoid.HumanBones.LeftIndexDistal = humanoidBone; break; + case HumanoidBones.leftMiddleProximal: Storage.gltfVrm.Humanoid.HumanBones.LeftMiddleProximal = humanoidBone; break; + case HumanoidBones.leftMiddleIntermediate: Storage.gltfVrm.Humanoid.HumanBones.LeftMiddleIntermediate = humanoidBone; break; + case HumanoidBones.leftMiddleDistal: Storage.gltfVrm.Humanoid.HumanBones.LeftMiddleDistal = humanoidBone; break; + case HumanoidBones.leftRingProximal: Storage.gltfVrm.Humanoid.HumanBones.LeftRingProximal = humanoidBone; break; + case HumanoidBones.leftRingIntermediate: Storage.gltfVrm.Humanoid.HumanBones.LeftRingIntermediate = humanoidBone; break; + case HumanoidBones.leftRingDistal: Storage.gltfVrm.Humanoid.HumanBones.LeftRingDistal = humanoidBone; break; + case HumanoidBones.leftLittleProximal: Storage.gltfVrm.Humanoid.HumanBones.LeftLittleProximal = humanoidBone; break; + case HumanoidBones.leftLittleIntermediate: Storage.gltfVrm.Humanoid.HumanBones.LeftLittleIntermediate = humanoidBone; break; + case HumanoidBones.leftLittleDistal: Storage.gltfVrm.Humanoid.HumanBones.LeftLittleDistal = humanoidBone; break; + case HumanoidBones.rightThumbProximal: Storage.gltfVrm.Humanoid.HumanBones.RightThumbProximal = humanoidBone; break; + case HumanoidBones.rightThumbIntermediate: Storage.gltfVrm.Humanoid.HumanBones.RightThumbIntermediate = humanoidBone; break; + case HumanoidBones.rightThumbDistal: Storage.gltfVrm.Humanoid.HumanBones.RightThumbDistal = humanoidBone; break; + case HumanoidBones.rightIndexProximal: Storage.gltfVrm.Humanoid.HumanBones.RightIndexProximal = humanoidBone; break; + case HumanoidBones.rightIndexIntermediate: Storage.gltfVrm.Humanoid.HumanBones.RightIndexIntermediate = humanoidBone; break; + case HumanoidBones.rightIndexDistal: Storage.gltfVrm.Humanoid.HumanBones.RightIndexDistal = humanoidBone; break; + case HumanoidBones.rightMiddleProximal: Storage.gltfVrm.Humanoid.HumanBones.RightMiddleProximal = humanoidBone; break; + case HumanoidBones.rightMiddleIntermediate: Storage.gltfVrm.Humanoid.HumanBones.RightMiddleIntermediate = humanoidBone; break; + case HumanoidBones.rightMiddleDistal: Storage.gltfVrm.Humanoid.HumanBones.RightMiddleDistal = humanoidBone; break; + case HumanoidBones.rightRingProximal: Storage.gltfVrm.Humanoid.HumanBones.RightRingProximal = humanoidBone; break; + case HumanoidBones.rightRingIntermediate: Storage.gltfVrm.Humanoid.HumanBones.RightRingIntermediate = humanoidBone; break; + case HumanoidBones.rightRingDistal: Storage.gltfVrm.Humanoid.HumanBones.RightRingDistal = humanoidBone; break; + case HumanoidBones.rightLittleProximal: Storage.gltfVrm.Humanoid.HumanBones.RightLittleProximal = humanoidBone; break; + case HumanoidBones.rightLittleIntermediate: Storage.gltfVrm.Humanoid.HumanBones.RightLittleIntermediate = humanoidBone; break; + case HumanoidBones.rightLittleDistal: Storage.gltfVrm.Humanoid.HumanBones.RightLittleDistal = humanoidBone; break; + case HumanoidBones.upperChest: Storage.gltfVrm.Humanoid.HumanBones.UpperChest = humanoidBone; break; + } + + // gltfVrm.Humanoid.HumanBones.Add(kv.Key.ToString(), humanoidBone); + } + } + + public void ExportVrmExpression(ExpressionManager src, List _, List materials, List nodes) + { + foreach (var x in src.ExpressionList) + { + Storage.gltfVrm.Expressions.Add(x.ToGltf(nodes, materials)); + } + } + + public void ExportVrmSpringBone(SpringBoneManager springBone, List nodes) + { + Storage.gltfVrmSpringBone = springBone.ToGltf(nodes, Storage.Gltf.nodes); + } + + public void ExportVrmFirstPersonAndLookAt(FirstPerson firstPerson, LookAt lookat, List meshes, List nodes) + { + Storage.gltfVrm.FirstPerson = firstPerson.ToGltf(nodes); + Storage.gltfVrm.LookAt = lookat.ToGltf(); + } + + public void ExportVrmMaterialProperties(List materials, List textures) + { + // Do nothing + // see + // ExportMaterialPBR + // ExportMaterialUnlit + // ExportMaterialMToon + } + + public void ExportVrmEnd() + { + UniGLTF.Extensions.VRMC_vrm.GltfSerializer.SerializeTo(ref Storage.Gltf.extensions, Storage.gltfVrm); + UniGLTF.Extensions.VRMC_springBone.GltfSerializer.SerializeTo(ref Storage.Gltf.extensions, Storage.gltfVrmSpringBone); + } + } +} diff --git a/Assets/VRM10/Runtime/IO/Vrm10Exporter.cs.meta b/Assets/VRM10/Runtime/IO/Vrm10Exporter.cs.meta new file mode 100644 index 000000000..b6b2f39a6 --- /dev/null +++ b/Assets/VRM10/Runtime/IO/Vrm10Exporter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9831d54a2432bd841a4b9352e47b2d83 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/IO/Vrm10Storage.cs b/Assets/VRM10/Runtime/IO/Vrm10Storage.cs new file mode 100644 index 000000000..1bc7f17fe --- /dev/null +++ b/Assets/VRM10/Runtime/IO/Vrm10Storage.cs @@ -0,0 +1,808 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Runtime.InteropServices; +using UniGLTF; +using VrmLib; +using UniJSON; + + +namespace UniVRM10 +{ + public class Vrm10Storage : IVrmStorage + { + public ArraySegment OriginalJson { get; private set; } + public glTF Gltf + { + get; + private set; + } + + public readonly List Buffers; + + public UniGLTF.Extensions.VRMC_vrm.VRMC_vrm gltfVrm; + + public UniGLTF.Extensions.VRMC_springBone.VRMC_springBone gltfVrmSpringBone; + + /// + /// for export + /// + public Vrm10Storage() + { + Gltf = new glTF() + { + extensionsUsed = new List(), + }; + Buffers = new List() + { + new ArrayByteBuffer10() + }; + } + + /// + /// for import + /// + /// + /// + public Vrm10Storage(ArraySegment json, ArraySegment bin) + { + OriginalJson = json; + Gltf = UniGLTF.GltfDeserializer.Deserialize(json.ParseAsJson()); + + if (UniGLTF.Extensions.VRMC_vrm.GltfDeserializer.TryGet(Gltf.extensions, + out UniGLTF.Extensions.VRMC_vrm.VRMC_vrm vrm)) + { + gltfVrm = vrm; + } + + if (UniGLTF.Extensions.VRMC_springBone.GltfDeserializer.TryGet(Gltf.extensions, + out UniGLTF.Extensions.VRMC_springBone.VRMC_springBone springBone)) + { + gltfVrmSpringBone = springBone; + } + + var array = bin.ToArray(); + Buffers = new List() + { + new ArrayByteBuffer10(array, bin.Count) + }; + } + + public void Reserve(int bytesLength) + { + Buffers[0].ExtendCapacity(bytesLength); + } + + public int AppendToBuffer(int bufferIndex, ArraySegment segment, int stride) + { + Buffers[bufferIndex].Extend(segment, stride, out int offset, out int length); + var viewIndex = Gltf.bufferViews.Count; + Gltf.bufferViews.Add(new glTFBufferView + { + buffer = 0, + byteOffset = offset, + byteLength = length, + }); + return viewIndex; + } + + static ArraySegment RestoreSparseAccessorUInt16(ArraySegment bytes, int accessorCount, ArraySegment indicesBytes, ArraySegment valuesBytes) + where T : struct + { + var stride = Marshal.SizeOf(typeof(T)); + if (bytes.Count == 0) + { + bytes = new ArraySegment(new byte[accessorCount * stride]); + } + var dst = SpanLike.Wrap(bytes); + + var indices = SpanLike.Wrap(indicesBytes); + var values = SpanLike.Wrap(valuesBytes); + + for (int i = 0; i < indices.Length; ++i) + { + var index = indices[i]; + var value = values[i]; + dst[index] = value; + } + + return bytes; + } + + static ArraySegment RestoreSparseAccessorUInt32(ArraySegment bytes, int accessorCount, ArraySegment indicesBytes, ArraySegment valuesBytes) + where T : struct + { + var stride = Marshal.SizeOf(typeof(T)); + if (bytes.Count == 0) + { + bytes = new ArraySegment(new byte[accessorCount * stride]); + } + var dst = SpanLike.Wrap(bytes); + + var indices = SpanLike.Wrap(indicesBytes); + var values = SpanLike.Wrap(valuesBytes); + + for (int i = 0; i < indices.Length; ++i) + { + var index = indices[i]; + var value = values[i]; + dst[index] = value; + } + + return bytes; + } + + public ArraySegment GetAccessorBytes(int accessorIndex) + { + var accessor = Gltf.accessors[accessorIndex]; + var sparse = accessor.sparse; + + ArraySegment bytes = default(ArraySegment); + + if (accessor.bufferView.TryGetValidIndex(Gltf.bufferViews.Count, out int bufferViewIndex)) + { + var view = Gltf.bufferViews[bufferViewIndex]; + if (view.buffer.TryGetValidIndex(Gltf.buffers.Count, out int bufferIndex)) + { + var buffer = Buffers[bufferIndex]; + var bin = buffer.Bytes; + var byteSize = accessor.CalcByteSize(); + bytes = bin.Slice(view.byteOffset, view.byteLength).Slice(accessor.byteOffset, byteSize); + } + } + + if (sparse != null) + { + if (!sparse.indices.bufferView.TryGetValidIndex(Gltf.bufferViews.Count, out int sparseIndicesBufferViewIndex)) + { + throw new Exception(); + } + var sparseIndexView = Gltf.bufferViews[sparseIndicesBufferViewIndex]; + var sparseIndexBin = GetBufferBytes(sparseIndexView); + var sparseIndexBytes = sparseIndexBin + .Slice(sparseIndexView.byteOffset, sparseIndexView.byteLength) + .Slice(sparse.indices.byteOffset, ((AccessorValueType)sparse.indices.componentType).ByteSize() * sparse.count) + ; + + if (!sparse.values.bufferView.TryGetValidIndex(Gltf.bufferViews.Count, out int sparseValuesBufferViewIndex)) + { + throw new Exception(); + } + var sparseValueView = Gltf.bufferViews[sparseValuesBufferViewIndex]; + var sparseValueBin = GetBufferBytes(sparseValueView); + var sparseValueBytes = sparseValueBin + .Slice(sparseValueView.byteOffset, sparseValueView.byteLength) + .Slice(sparse.values.byteOffset, accessor.GetStride() * sparse.count); + ; + + if (sparse.indices.componentType == (glComponentType)AccessorValueType.UNSIGNED_SHORT + && accessor.componentType == (glComponentType)AccessorValueType.FLOAT + && accessor.type == "VEC3") + { + return RestoreSparseAccessorUInt16(bytes, accessor.count, sparseIndexBytes, sparseValueBytes); + } + if (sparse.indices.componentType == (glComponentType)AccessorValueType.UNSIGNED_INT + && accessor.componentType == (glComponentType)AccessorValueType.FLOAT + && accessor.type == "VEC3") + { + return RestoreSparseAccessorUInt32(bytes, accessor.count, sparseIndexBytes, sparseValueBytes); + } + else + { + throw new NotImplementedException(); + } + } + else + { + if (bytes.Count == 0) + { + // sparse and all value is zero + return new ArraySegment(new byte[accessor.GetStride() * accessor.count]); + } + + return bytes; + } + } + + /// + /// submeshのindexが連続した領域に格納されているかを確認する + /// + bool AccessorsIsContinuous(int[] accessorIndices) + { + var firstAccessor = Gltf.accessors[accessorIndices[0]]; + var firstView = Gltf.bufferViews[firstAccessor.bufferView]; + var start = firstView.byteOffset + firstAccessor.byteOffset; + var pos = start; + foreach (var i in accessorIndices) + { + var current = Gltf.accessors[i]; + if (current.type != "SCALAR") + { + throw new ArgumentException($"accessor.type: {current.type}"); + } + if (firstAccessor.componentType != current.componentType) + { + return false; + } + + var view = Gltf.bufferViews[current.bufferView]; + if (pos != view.byteOffset + current.byteOffset) + { + return false; + } + + var begin = view.byteOffset + current.byteOffset; + var byteLength = current.CalcByteSize(); + + pos += byteLength; + } + + return true; + } + + /// + /// Gltfの Primitive[] の indices をひとまとめにした + /// IndexBuffer を返す。 + /// + public BufferAccessor CreateAccessor(int[] accessorIndices) + { + var totalCount = accessorIndices.Sum(x => Gltf.accessors[x].count); + if (AccessorsIsContinuous(accessorIndices)) + { + // IndexBufferが連続して格納されている => Slice でいける + var firstAccessor = Gltf.accessors[accessorIndices[0]]; + var firstView = Gltf.bufferViews[firstAccessor.bufferView]; + var start = firstView.byteOffset + firstAccessor.byteOffset; + if (!firstView.buffer.TryGetValidIndex(Gltf.buffers.Count, out int firstViewBufferIndex)) + { + throw new Exception(); + } + var buffer = Gltf.buffers[firstViewBufferIndex]; + var bin = GetBufferBytes(buffer); + var bytes = bin.Slice(start, totalCount * firstAccessor.GetStride()); + return new BufferAccessor(bytes, + (AccessorValueType)firstAccessor.componentType, + EnumUtil.Parse(firstAccessor.type), + totalCount); + } + else + { + // IndexBufferが連続して格納されていない => Int[] を作り直す + var indices = new byte[totalCount * Marshal.SizeOf(typeof(int))]; + var span = SpanLike.Wrap(new ArraySegment(indices)); + var offset = 0; + foreach (var accessorIndex in accessorIndices) + { + var accessor = Gltf.accessors[accessorIndex]; + if (accessor.type != "SCALAR") + { + throw new ArgumentException($"accessor.type: {accessor.type}"); + } + var view = Gltf.bufferViews[accessor.bufferView]; + if (!view.buffer.TryGetValidIndex(Gltf.buffers.Count, out int viewBufferIndex)) + { + throw new Exception(); + } + var buffer = Gltf.buffers[viewBufferIndex]; + var bin = GetBufferBytes(buffer); + var start = view.byteOffset + accessor.byteOffset; + var bytes = bin.Slice(start, accessor.count * accessor.GetStride()); + var dst = SpanLike.Wrap(new ArraySegment(indices)).Slice(offset, accessor.count); + offset += accessor.count; + switch ((AccessorValueType)accessor.componentType) + { + case AccessorValueType.UNSIGNED_BYTE: + { + var src = SpanLike.Wrap(bytes); + for (int i = 0; i < src.Length; ++i) + { + // byte to int + dst[i] = src[i]; + } + } + break; + + case AccessorValueType.UNSIGNED_SHORT: + { + var src = SpanLike.Wrap(bytes); + for (int i = 0; i < src.Length; ++i) + { + // ushort to int + dst[i] = src[i]; + } + } + break; + + case AccessorValueType.UNSIGNED_INT: + { + Buffer.BlockCopy(bytes.Array, bytes.Offset, dst.Bytes.Array, dst.Bytes.Offset, bytes.Count); + } + break; + + default: + throw new NotImplementedException($"accessor.componentType: {accessor.componentType}"); + } + } + return new BufferAccessor(new ArraySegment(indices), AccessorValueType.UNSIGNED_INT, AccessorVectorType.SCALAR, totalCount); + } + } + + public void CreateBufferAccessorAndAdd(int? accessorIndex, VertexBuffer b, string key) + { + if (accessorIndex.HasValue) + { + CreateBufferAccessorAndAdd(accessorIndex.Value, b, key); + } + } + + public void CreateBufferAccessorAndAdd(int accessorIndex, VertexBuffer b, string key) + { + var a = CreateAccessor(accessorIndex); + if (a != null) + { + b.Add(key, a); + } + } + + public BufferAccessor CreateAccessor(int accessorIndex) + { + if (TryCreateAccessor(accessorIndex, out BufferAccessor ba)) + { + return ba; + } + else + { + return null; + } + } + + public bool TryCreateAccessor(int accessorIndex, out BufferAccessor ba) + { + if (accessorIndex < 0 || accessorIndex >= Gltf.accessors.Count) + { + ba = default; + return false; + } + var accessor = Gltf.accessors[accessorIndex]; + var bytes = GetAccessorBytes(accessorIndex); + var vectorType = EnumUtil.Parse(accessor.type); + ba = new BufferAccessor(bytes, + (AccessorValueType)accessor.componentType, vectorType, accessor.count); + return true; + } + + public string AssetVersion => Gltf.asset.version; + + public string AssetMinVersion => Gltf.asset.minVersion; + + public string AssetGenerator => Gltf.asset.generator; + + public string AssetCopyright => Gltf.asset.copyright; + + public int NodeCount => Gltf.nodes.Count; + + public int ImageCount => Gltf.images.Count; + + public int TextureCount => Gltf.textures.Count; + + public int MaterialCount => Gltf.materials.Count; + + public int SkinCount => Gltf.skins.Count; + + public int MeshCount => Gltf.meshes.Count; + + // TODO: + public int AnimationCount => 0; + + public string VrmExporterVersion => Gltf.asset.generator; + + public bool HasVrm => gltfVrm != null; + + public string VrmSpecVersion => gltfVrm?.SpecVersion; + + public Node CreateNode(int index) + { + var x = Gltf.nodes[index]; + var node = new Node(x.name); + if (x.matrix != null) + { + if (x.matrix.Length != 16) throw new Exception("matrix member is not 16"); + if (x.translation != null && x.translation.Length > 0) throw new Exception("matrix with translation"); + if (x.rotation != null && x.rotation.Length > 0) throw new Exception("matrix with rotation"); + if (x.scale != null && x.scale.Length > 0) throw new Exception("matrix with scale"); + var m = new Matrix4x4( + x.matrix[0], x.matrix[1], x.matrix[2], x.matrix[3], + x.matrix[4], x.matrix[5], x.matrix[6], x.matrix[7], + x.matrix[8], x.matrix[9], x.matrix[10], x.matrix[11], + x.matrix[12], x.matrix[13], x.matrix[14], x.matrix[15] + ); + + node.SetLocalMatrix(m, true); + } + else + { + node.LocalTranslation = x.translation.ToVector3(); + node.LocalRotation = x.rotation.ToQuaternion(); + node.LocalScaling = x.scale.ToVector3(Vector3.One); + } + return node; + } + + public IEnumerable GetChildNodeIndices(int i) + { + var gltfNode = Gltf.nodes[i]; + if (gltfNode.children != null) + { + foreach (var j in gltfNode.children) + { + yield return j; + } + } + } + + public Image CreateImage(int index) + { + return Gltf.images[index].FromGltf(this); + } + + /// + /// sRGB でないテクスチャーを検出する + /// + /// + /// + private (Texture.TextureTypes, glTFMaterial) GetTextureType(int textureIndex) + { + foreach (var material in Gltf.materials) + { + if (UniGLTF.Extensions.VRMC_materials_mtoon.GltfDeserializer.TryGet(material.extensions, + out UniGLTF.Extensions.VRMC_materials_mtoon.VRMC_materials_mtoon mtoon)) + { + if (material.normalTexture?.index == textureIndex) return (Texture.TextureTypes.NormalMap, material); + } + else if (glTF_KHR_materials_unlit.IsEnable(material)) + { + } + else + { + if (material.pbrMetallicRoughness?.baseColorTexture?.index == textureIndex) return (Texture.TextureTypes.Default, material); + if (material.pbrMetallicRoughness?.metallicRoughnessTexture?.index == textureIndex) return (Texture.TextureTypes.MetallicRoughness, material); + if (material.occlusionTexture?.index == textureIndex) return (Texture.TextureTypes.Occlusion, material); + if (material.emissiveTexture?.index == textureIndex) return (Texture.TextureTypes.Emissive, material); + if (material.normalTexture?.index == textureIndex) return (Texture.TextureTypes.NormalMap, material); + } + } + + return (Texture.TextureTypes.Default, null); + } + + private Texture.ColorSpaceTypes GetTextureColorSpaceType(int textureIndex) + { + foreach (var material in Gltf.materials) + { + if (UniGLTF.Extensions.VRMC_materials_mtoon.GltfDeserializer.TryGet(material.extensions, + out UniGLTF.Extensions.VRMC_materials_mtoon.VRMC_materials_mtoon mtoon)) + { + // mtoon + if (material.pbrMetallicRoughness.baseColorTexture.index == textureIndex) return Texture.ColorSpaceTypes.Srgb; + if (mtoon.ShadeMultiplyTexture == textureIndex) return Texture.ColorSpaceTypes.Srgb; + if (material.emissiveTexture?.index == textureIndex) return Texture.ColorSpaceTypes.Srgb; + if (mtoon.RimMultiplyTexture == textureIndex) return Texture.ColorSpaceTypes.Srgb; + if (mtoon.AdditiveTexture == textureIndex) return Texture.ColorSpaceTypes.Srgb; + + if (mtoon.OutlineWidthMultiplyTexture == textureIndex) return Texture.ColorSpaceTypes.Linear; + if (mtoon.UvAnimationMaskTexture == textureIndex) return Texture.ColorSpaceTypes.Linear; + + if (material.normalTexture?.index == textureIndex) return Texture.ColorSpaceTypes.Linear; + } + else if (glTF_KHR_materials_unlit.IsEnable(material)) + { + // unlit + if (material.pbrMetallicRoughness.baseColorTexture?.index == textureIndex) return Texture.ColorSpaceTypes.Srgb; + } + else + { + // Pbr + if (material.pbrMetallicRoughness?.baseColorTexture?.index == textureIndex) return Texture.ColorSpaceTypes.Srgb; + if (material.pbrMetallicRoughness?.metallicRoughnessTexture?.index == textureIndex) return Texture.ColorSpaceTypes.Linear; + if (material.occlusionTexture?.index == textureIndex) return Texture.ColorSpaceTypes.Linear; + if (material.emissiveTexture?.index == textureIndex) return Texture.ColorSpaceTypes.Srgb; + if (material.normalTexture?.index == textureIndex) return Texture.ColorSpaceTypes.Linear; + } + } + + return Texture.ColorSpaceTypes.Srgb; + } + + public Texture CreateTexture(int index, List images) + { + var texture = Gltf.textures[index]; + var textureType = GetTextureType(index); + var colorSpace = GetTextureColorSpaceType(index); + + var sampler = (texture.sampler >= 0 && texture.sampler < Gltf.samplers.Count) + ? Gltf.samplers[texture.sampler] + : new glTFTextureSampler() + ; + + if (textureType.Item1 == Texture.TextureTypes.MetallicRoughness && textureType.Item2.pbrMetallicRoughness != null) + { + var roughnessFactor = textureType.Item2.pbrMetallicRoughness.roughnessFactor; + var name = !string.IsNullOrEmpty(texture.name) ? texture.name : images[texture.source].Name; + return new MetallicRoughnessImageTexture( + name, + sampler.FromGltf(), + images[texture.source], + roughnessFactor, + colorSpace, + textureType.Item1); + } + else + { + return texture.FromGltf(sampler, images, colorSpace, textureType.Item1); + } + } + + public Material CreateMaterial(int index, List textures) + { + var x = Gltf.materials[index]; + return x.FromGltf(textures); + } + + public Skin CreateSkin(int index, List nodes) + { + var x = Gltf.skins[index]; + BufferAccessor inverseMatrices = null; + if (x.inverseBindMatrices != -1) + { + inverseMatrices = CreateAccessor(x.inverseBindMatrices); + } + var skin = new Skin + { + InverseMatrices = inverseMatrices, + Joints = x.joints.Select(y => nodes[y]).ToList(), + }; + if (x.skeleton != -1) // TODO: proto to int + { + skin.Root = nodes[x.skeleton]; + } + return skin; + } + + public MeshGroup CreateMesh(int index, List materials) + { + var x = Gltf.meshes[index]; + var group = x.FromGltf(this, materials); + return group; + } + + public (int, int) GetNodeMeshSkin(int index) + { + var x = Gltf.nodes[index]; + + int meshIndex = -1; + if (x.mesh.TryGetValidIndex(Gltf.meshes.Count, out int mi)) + { + meshIndex = mi; + } + + int skinIndex = -1; + if (x.skin.TryGetValidIndex(Gltf.skins.Count, out int si)) + { + skinIndex = si; + } + + return (meshIndex, skinIndex); + } + + public Animation CreateAnimation(int index, List nodes) + { + throw new NotImplementedException(); + } + + public Meta CreateVrmMeta(List textures) + { + return gltfVrm.Meta.FromGltf(textures); + } + + static void AssignHumanoid(List nodes, UniGLTF.Extensions.VRMC_vrm.HumanBone humanBone, VrmLib.HumanoidBones key) + { + if (humanBone.Node.HasValue) + { + nodes[humanBone.Node.Value].HumanoidBone = key; + } + } + + public void LoadVrmHumanoid(List nodes) + { + if (gltfVrm.Humanoid != null) + { + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.Hips, HumanoidBones.hips); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.LeftUpperLeg, HumanoidBones.leftUpperLeg); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.RightUpperLeg, HumanoidBones.rightUpperLeg); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.LeftLowerLeg, HumanoidBones.leftLowerLeg); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.RightLowerLeg, HumanoidBones.rightLowerLeg); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.LeftFoot, HumanoidBones.leftFoot); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.RightFoot, HumanoidBones.rightFoot); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.Spine, HumanoidBones.spine); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.Chest, HumanoidBones.chest); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.Neck, HumanoidBones.neck); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.Head, HumanoidBones.head); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.LeftShoulder, HumanoidBones.leftShoulder); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.RightShoulder, HumanoidBones.rightShoulder); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.LeftUpperArm, HumanoidBones.leftUpperArm); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.RightUpperArm, HumanoidBones.rightUpperArm); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.LeftLowerArm, HumanoidBones.leftLowerArm); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.RightLowerArm, HumanoidBones.rightLowerArm); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.LeftHand, HumanoidBones.leftHand); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.RightHand, HumanoidBones.rightHand); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.LeftToes, HumanoidBones.leftToes); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.RightToes, HumanoidBones.rightToes); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.LeftEye, HumanoidBones.leftEye); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.RightEye, HumanoidBones.rightEye); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.Jaw, HumanoidBones.jaw); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.LeftThumbProximal, HumanoidBones.leftThumbProximal); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.LeftThumbIntermediate, HumanoidBones.leftThumbIntermediate); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.LeftThumbDistal, HumanoidBones.leftThumbDistal); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.LeftIndexProximal, HumanoidBones.leftIndexProximal); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.LeftIndexIntermediate, HumanoidBones.leftIndexIntermediate); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.LeftIndexDistal, HumanoidBones.leftIndexDistal); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.LeftMiddleProximal, HumanoidBones.leftMiddleProximal); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.LeftMiddleIntermediate, HumanoidBones.leftMiddleIntermediate); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.LeftMiddleDistal, HumanoidBones.leftMiddleDistal); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.LeftRingProximal, HumanoidBones.leftRingProximal); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.LeftRingIntermediate, HumanoidBones.leftRingIntermediate); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.LeftRingDistal, HumanoidBones.leftRingDistal); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.LeftLittleProximal, HumanoidBones.leftLittleProximal); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.LeftLittleIntermediate, HumanoidBones.leftLittleIntermediate); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.LeftLittleDistal, HumanoidBones.leftLittleDistal); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.RightThumbProximal, HumanoidBones.rightThumbProximal); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.RightThumbIntermediate, HumanoidBones.rightThumbIntermediate); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.RightThumbDistal, HumanoidBones.rightThumbDistal); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.RightIndexProximal, HumanoidBones.rightIndexProximal); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.RightIndexIntermediate, HumanoidBones.rightIndexIntermediate); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.RightIndexDistal, HumanoidBones.rightIndexDistal); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.RightMiddleProximal, HumanoidBones.rightMiddleProximal); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.RightMiddleIntermediate, HumanoidBones.rightMiddleIntermediate); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.RightMiddleDistal, HumanoidBones.rightMiddleDistal); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.RightRingProximal, HumanoidBones.rightRingProximal); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.RightRingIntermediate, HumanoidBones.rightRingIntermediate); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.RightRingDistal, HumanoidBones.rightRingDistal); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.RightLittleProximal, HumanoidBones.rightLittleProximal); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.RightLittleIntermediate, HumanoidBones.rightLittleIntermediate); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.RightLittleDistal, HumanoidBones.rightLittleDistal); + AssignHumanoid(nodes, gltfVrm.Humanoid.HumanBones.UpperChest, HumanoidBones.upperChest); + } + } + + public ExpressionManager CreateVrmExpression(List _, List materials, List nodes) + { + if (gltfVrm.Expressions != null) + { + var expressionManager = new ExpressionManager(); + foreach (var x in gltfVrm.Expressions) + { + expressionManager.ExpressionList.Add(x.FromGltf(nodes, materials)); + } + return expressionManager; + } + + return null; + } + + static VrmSpringBoneCollider CreateCollider(UniGLTF.Extensions.VRMC_node_collider.ColliderShape z) + { + if (z.Sphere != null) + { + return VrmSpringBoneCollider.CreateSphere(z.Sphere.Offset.ToVector3(), z.Sphere.Radius.Value); + } + if (z.Capsule != null) + { + return VrmSpringBoneCollider.CreateCapsule(z.Capsule.Offset.ToVector3(), z.Capsule.Radius.Value, z.Capsule.Tail.ToVector3()); + } + throw new NotImplementedException(); + } + + public SpringBoneManager CreateVrmSpringBone(List nodes) + { + if ((gltfVrmSpringBone is null)) + { + return null; + } + + var springBone = new SpringBoneManager(); + + // springs + if (gltfVrmSpringBone.Springs != null) + { + foreach (var group in gltfVrmSpringBone.Springs.GroupBy(x => x.Setting.Value)) + { + var sb = new SpringBone(); + sb.Comment = group.First().Name; + sb.HitRadius = group.First().HitRadius.Value; + var setting = gltfVrmSpringBone.Settings[group.Key]; + sb.DragForce = setting.DragForce.Value; + sb.GravityDir = setting.GravityDir.ToVector3(); + sb.GravityPower = setting.GravityPower.Value; + sb.Stiffness = setting.Stiffness.Value; + + foreach (var spring in group) + { + // root + sb.Bones.Add(nodes[spring.SpringRoot.Value]); + // collider + foreach (var colliderNode in spring.Colliders) + { + var collider = springBone.Colliders.FirstOrDefault(x => x.Node == nodes[colliderNode]); + if (collider == null) + { + if (UniGLTF.Extensions.VRMC_node_collider.GltfDeserializer.TryGet(Gltf.nodes[colliderNode].extensions, + out UniGLTF.Extensions.VRMC_node_collider.VRMC_node_collider extension)) + { + collider = new SpringBoneColliderGroup(nodes[colliderNode], extension.Shapes.Select(x => + { + if (x.Sphere != null) + { + return VrmSpringBoneCollider.CreateSphere(x.Sphere.Offset.ToVector3(), x.Sphere.Radius.Value); + } + else if (x.Capsule != null) + { + return VrmSpringBoneCollider.CreateCapsule(x.Capsule.Offset.ToVector3(), x.Capsule.Radius.Value, x.Capsule.Tail.ToVector3()); + } + else + { + throw new NotImplementedException(); + } + })); + springBone.Colliders.Add(collider); + } + else + { + throw new Exception("collider not found"); + } + } + sb.Colliders.Add(collider); + } + } + + springBone.Springs.Add(sb); + } + } + + return springBone; + } + + public FirstPerson CreateVrmFirstPerson(List nodes, List meshGroups) + { + if (gltfVrm.FirstPerson == null) + { + return null; + } + return gltfVrm.FirstPerson.FromGltf(nodes); + } + + public LookAt CreateVrmLookAt() + { + if (gltfVrm.LookAt == null) + { + return null; + } + return gltfVrm.LookAt.FromGltf(); + } + + public ArraySegment GetBufferBytes(glTFBufferView bufferView) + { + if (!bufferView.buffer.TryGetValidIndex(Gltf.buffers.Count, out int bufferViewBufferIndex)) + { + throw new Exception(); + } + return GetBufferBytes(Gltf.buffers[bufferViewBufferIndex]); + } + + public ArraySegment GetBufferBytes(glTFBuffer buffer) + { + int index = Gltf.buffers.IndexOf(buffer); + return Buffers[index].Bytes; + } + } +} \ No newline at end of file diff --git a/Assets/VRM10/Runtime/IO/Vrm10Storage.cs.meta b/Assets/VRM10/Runtime/IO/Vrm10Storage.cs.meta new file mode 100644 index 000000000..990ea7baa --- /dev/null +++ b/Assets/VRM10/Runtime/IO/Vrm10Storage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f9c990b845a5c4b4a84edbf955d8163f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/IO/VrmExpressionAdapter.cs b/Assets/VRM10/Runtime/IO/VrmExpressionAdapter.cs new file mode 100644 index 000000000..deb5fa37f --- /dev/null +++ b/Assets/VRM10/Runtime/IO/VrmExpressionAdapter.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using VrmLib; + +namespace UniVRM10 +{ + public static class ExpressionAdapter + { + public static VrmLib.Expression FromGltf(this UniGLTF.Extensions.VRMC_vrm.Expression x, List nodes, List materials) + { + var expression = new VrmLib.Expression((VrmLib.ExpressionPreset)x.Preset, + x.Name, + x.IsBinary.HasValue && x.IsBinary.Value) + { + IgnoreBlink = x.IgnoreBlink.GetValueOrDefault(), + IgnoreLookAt = x.IgnoreLookAt.GetValueOrDefault(), + IgnoreMouth = x.IgnoreMouth.GetValueOrDefault(), + }; + + if (x.MorphTargetBinds != null) + { + foreach (var y in x.MorphTargetBinds) + { + var node = nodes[y.Node.Value]; + var blendShapeName = node.Mesh.MorphTargets[y.Index.Value].Name; + var blendShapeBind = new MorphTargetBind(node, blendShapeName, y.Weight.Value); + expression.MorphTargetBinds.Add(blendShapeBind); + } + } + + if (x.MaterialColorBinds != null) + { + foreach (var y in x.MaterialColorBinds) + { + var material = materials[y.Material.Value]; + var materialColorBind = new MaterialColorBind(material, EnumUtil.Cast(y.Type), y.TargetValue.ToVector4(Vector4.Zero)); + expression.MaterialColorBinds.Add(materialColorBind); + } + } + + if (x.TextureTransformBinds != null) + { + foreach (var y in x.TextureTransformBinds) + { + var material = materials[y.Material.Value]; + var materialUVBind = new TextureTransformBind(material, + y.Scaling.ToVector2(Vector2.One), + y.Offset.ToVector2(Vector2.Zero)); + expression.TextureTransformBinds.Add(materialUVBind); + } + } + + return expression; + } + // public static ExpressionManager FromGltf(this UniGLTF.VRMC_vrm.Expression master, List nodes, List materials) + // { + // var manager = new ExpressionManager(); + // foreach (var x in master.BlendShapeGroups) + // { + // VrmLib.Expression expression = FromGltf(x, nodes, materials); + + // manager.ExpressionList.Add(expression); + // }; + // return manager; + // } + + public static UniGLTF.Extensions.VRMC_vrm.MorphTargetBind ToGltf(this MorphTargetBind self, List nodes) + { + var name = self.Name; + var value = self.Value; + var index = self.Node.Mesh.MorphTargets.FindIndex(x => x.Name == name); + if (index < 0) + { + throw new IndexOutOfRangeException(string.Format("MorphTargetName {0} is not found", name)); + } + + return new UniGLTF.Extensions.VRMC_vrm.MorphTargetBind + { + Node = nodes.IndexOfThrow(self.Node), + Index = self.Node.Mesh.MorphTargets.FindIndex(x => x.Name == name), + Weight = value, + }; + } + + public static UniGLTF.Extensions.VRMC_vrm.MaterialColorBind ToGltf(this MaterialColorBind self, List materials) + { + var m = new UniGLTF.Extensions.VRMC_vrm.MaterialColorBind + { + Material = materials.IndexOfThrow(self.Material), + Type = EnumUtil.Cast(self.BindType), + TargetValue = self.Property.Value.ToFloat4() + }; + return m; + } + + public static UniGLTF.Extensions.VRMC_vrm.TextureTransformBind ToGltf(this TextureTransformBind self, List materials) + { + var m = new UniGLTF.Extensions.VRMC_vrm.TextureTransformBind + { + Material = materials.IndexOfThrow(self.Material), + Scaling = self.Scale.ToFloat2(), + Offset = self.Offset.ToFloat2(), + }; + return m; + } + + public static UniGLTF.Extensions.VRMC_vrm.Expression ToGltf(this VrmLib.Expression x, List nodes, List materials) + { + var g = new UniGLTF.Extensions.VRMC_vrm.Expression + { + Preset = (UniGLTF.Extensions.VRMC_vrm.ExpressionPreset)x.Preset, + Name = x.Name, + IsBinary = x.IsBinary, + IgnoreBlink = x.IgnoreBlink, + IgnoreLookAt = x.IgnoreLookAt, + IgnoreMouth = x.IgnoreMouth, + }; + foreach (var blendShapeBind in x.MorphTargetBinds) + { + g.MorphTargetBinds.Add(blendShapeBind.ToGltf(nodes)); + } + foreach (var materialColorBind in x.MaterialColorBinds) + { + g.MaterialColorBinds.Add(materialColorBind.ToGltf(materials)); + } + foreach (var materialUVBind in x.TextureTransformBinds) + { + g.TextureTransformBinds.Add(materialUVBind.ToGltf(materials)); + } + return g; + } + + // public static UniGLTF.VRMC_vrm.BlendShape ToGltf(this ExpressionManager src, List nodes, List materials) + // { + // var blendShape = new UniGLTF.VRMC_vrm.BlendShape + // { + // }; + // if (src != null) + // { + // foreach (var x in src.ExpressionList) + // { + // blendShape.BlendShapeGroups.Add(x.ToGltf(nodes, materials)); + // } + // } + // return blendShape; + // } + } +} diff --git a/Assets/VRM10/Runtime/IO/VrmExpressionAdapter.cs.meta b/Assets/VRM10/Runtime/IO/VrmExpressionAdapter.cs.meta new file mode 100644 index 000000000..5ec3b69eb --- /dev/null +++ b/Assets/VRM10/Runtime/IO/VrmExpressionAdapter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cadc48d0e68ee5847b158dcef9b7b9a8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/IO/VrmFirstPersonAdapter.cs b/Assets/VRM10/Runtime/IO/VrmFirstPersonAdapter.cs new file mode 100644 index 000000000..0101e5f0f --- /dev/null +++ b/Assets/VRM10/Runtime/IO/VrmFirstPersonAdapter.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using VrmLib; + +namespace UniVRM10 +{ + public static class FirstPersonAdapter + { + public static VrmLib.FirstPersonMeshType FromGltf(this UniGLTF.Extensions.VRMC_vrm.FirstPersonType src) + { + switch (src) + { + case UniGLTF.Extensions.VRMC_vrm.FirstPersonType.auto: return FirstPersonMeshType.Auto; + case UniGLTF.Extensions.VRMC_vrm.FirstPersonType.both: return FirstPersonMeshType.Both; + case UniGLTF.Extensions.VRMC_vrm.FirstPersonType.firstPersonOnly: return FirstPersonMeshType.FirstPersonOnly; + case UniGLTF.Extensions.VRMC_vrm.FirstPersonType.thirdPersonOnly: return FirstPersonMeshType.ThirdPersonOnly; + } + + throw new NotImplementedException(); + } + + public static FirstPerson FromGltf(this UniGLTF.Extensions.VRMC_vrm.FirstPerson fp, List nodes) + { + var self = new FirstPerson(); + + if (fp.MeshAnnotations != null) + { + self.Annotations.AddRange(fp.MeshAnnotations + .Select(x => new FirstPersonMeshAnnotation(nodes[x.Node.Value], x.FirstPersonType.FromGltf()))); + } + return self; + } + public static UniGLTF.Extensions.VRMC_vrm.FirstPerson ToGltf(this FirstPerson self, List nodes) + { + if (self == null) + { + return null; + } + + var firstPerson = new UniGLTF.Extensions.VRMC_vrm.FirstPerson + { + + }; + + foreach (var x in self.Annotations) + { + firstPerson.MeshAnnotations.Add(new UniGLTF.Extensions.VRMC_vrm.MeshAnnotation + { + Node = nodes.IndexOfThrow(x.Node), + FirstPersonType = EnumUtil.Cast(x.FirstPersonFlag), + }); + } + return firstPerson; + } + } +} diff --git a/Assets/VRM10/Runtime/IO/VrmFirstPersonAdapter.cs.meta b/Assets/VRM10/Runtime/IO/VrmFirstPersonAdapter.cs.meta new file mode 100644 index 000000000..dd2200fb9 --- /dev/null +++ b/Assets/VRM10/Runtime/IO/VrmFirstPersonAdapter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 32f13c918ad866647bfc8f91f3ec7815 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/IO/VrmLookAtAdapter.cs b/Assets/VRM10/Runtime/IO/VrmLookAtAdapter.cs new file mode 100644 index 000000000..dc10876e5 --- /dev/null +++ b/Assets/VRM10/Runtime/IO/VrmLookAtAdapter.cs @@ -0,0 +1,64 @@ +using System; +using VrmLib; + +namespace UniVRM10 +{ + public static class LookAtAdapter + { + public static LookAtRangeMap FromGltf(this UniGLTF.Extensions.VRMC_vrm.LookAtRangeMap map) + { + return new LookAtRangeMap + { + InputMaxValue = map.InputMaxValue.Value, + OutputScaling = map.OutputScale.Value, + }; + } + + public static LookAtType FromGltf(this UniGLTF.Extensions.VRMC_vrm.LookAtType src) + { + switch (src) + { + case UniGLTF.Extensions.VRMC_vrm.LookAtType.bone: return LookAtType.Bone; + case UniGLTF.Extensions.VRMC_vrm.LookAtType.blendShape: return LookAtType.Expression; + } + + throw new NotImplementedException(); + } + + public static LookAt FromGltf(this UniGLTF.Extensions.VRMC_vrm.LookAt src) + { + return new LookAt + { + OffsetFromHeadBone = src.OffsetFromHeadBone.ToVector3(), + LookAtType = src.LookAtType.FromGltf(), + HorizontalInner = src.LookAtHorizontalInner.FromGltf(), + HorizontalOuter = src.LookAtHorizontalOuter.FromGltf(), + VerticalUp = src.LookAtVerticalUp.FromGltf(), + VerticalDown = src.LookAtVerticalDown.FromGltf(), + }; + } + + public static UniGLTF.Extensions.VRMC_vrm.LookAtRangeMap ToGltf(this LookAtRangeMap map) + { + return new UniGLTF.Extensions.VRMC_vrm.LookAtRangeMap + { + InputMaxValue = map.InputMaxValue, + OutputScale = map.OutputScaling, + }; + } + + public static UniGLTF.Extensions.VRMC_vrm.LookAt ToGltf(this LookAt lookAt) + { + var dst = new UniGLTF.Extensions.VRMC_vrm.LookAt + { + LookAtType = (UniGLTF.Extensions.VRMC_vrm.LookAtType)lookAt.LookAtType, + LookAtHorizontalInner = lookAt.HorizontalInner.ToGltf(), + LookAtHorizontalOuter = lookAt.HorizontalOuter.ToGltf(), + LookAtVerticalUp = lookAt.VerticalUp.ToGltf(), + LookAtVerticalDown = lookAt.VerticalDown.ToGltf(), + OffsetFromHeadBone = lookAt.OffsetFromHeadBone.ToFloat3(), + }; + return dst; + } + } +} \ No newline at end of file diff --git a/Assets/VRM10/Runtime/IO/VrmLookAtAdapter.cs.meta b/Assets/VRM10/Runtime/IO/VrmLookAtAdapter.cs.meta new file mode 100644 index 000000000..636b08d80 --- /dev/null +++ b/Assets/VRM10/Runtime/IO/VrmLookAtAdapter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 68385357d73a5a3428e68eb49742baa9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/IO/VrmMetaAdapter.cs b/Assets/VRM10/Runtime/IO/VrmMetaAdapter.cs new file mode 100644 index 000000000..a801096b4 --- /dev/null +++ b/Assets/VRM10/Runtime/IO/VrmMetaAdapter.cs @@ -0,0 +1,94 @@ +using System.Collections.Generic; +using VrmLib; + +namespace UniVRM10 +{ + public static class VrmMetaAdapter + { + public static AvatarPermission ToAvaterPermission(this UniGLTF.Extensions.VRMC_vrm.Meta self) + { + return new AvatarPermission + { + AvatarUsage = (AvatarUsageType)self.AvatarPermission, + IsAllowedViolentUsage = self.AllowExcessivelyViolentUsage.Value, + IsAllowedSexualUsage = self.AllowExcessivelySexualUsage.Value, + CommercialUsage = (CommercialUsageType)self.CommercialUsage, + // OtherPermissionUrl = self.OtherPermissionUrl, + IsAllowedPoliticalOrReligiousUsage = self.AllowPoliticalOrReligiousUsage.Value, + }; + } + + public static RedistributionLicense ToRedistributionLicense(this UniGLTF.Extensions.VRMC_vrm.Meta self) + { + return new RedistributionLicense + { + CreditNotation = (CreditNotationType)self.CreditNotation, + IsAllowRedistribution = self.AllowRedistribution.Value, + ModificationLicense = (ModificationLicenseType)self.Modification, + OtherLicenseUrl = self.OtherLicenseUrl, + }; + } + + public static Meta FromGltf(this UniGLTF.Extensions.VRMC_vrm.Meta self, List textures) + { + var meta = new Meta + { + Name = self.Name, + Version = self.Version, + ContactInformation = self.ContactInformation, + AvatarPermission = ToAvaterPermission(self), + RedistributionLicense = ToRedistributionLicense(self), + }; + meta.References.AddRange(self.References); + meta.Authors.AddRange(self.Authors); + if (self.ThumbnailImage.HasValue) + { + var texture = textures[self.ThumbnailImage.Value] as ImageTexture; + if (texture != null) + { + meta.Thumbnail = texture.Image; + } + } + + return meta; + } + + public static UniGLTF.Extensions.VRMC_vrm.Meta ToGltf(this Meta self, List textures) + { + var meta = new UniGLTF.Extensions.VRMC_vrm.Meta + { + Name = self.Name, + Version = self.Version, + ContactInformation = self.ContactInformation, + CopyrightInformation = self.CopyrightInformation, + // AvatarPermission + AvatarPermission = (UniGLTF.Extensions.VRMC_vrm.AvatarPermissionType)self.AvatarPermission.AvatarUsage, + AllowExcessivelyViolentUsage = self.AvatarPermission.IsAllowedViolentUsage, + AllowExcessivelySexualUsage = self.AvatarPermission.IsAllowedSexualUsage, + CommercialUsage = (UniGLTF.Extensions.VRMC_vrm.CommercialUsageType)self.AvatarPermission.CommercialUsage, + AllowPoliticalOrReligiousUsage = self.AvatarPermission.IsAllowedPoliticalOrReligiousUsage, + // OtherPermissionUrl = self.AvatarPermission.OtherPermissionUrl, + // RedistributionLicense + CreditNotation = (UniGLTF.Extensions.VRMC_vrm.CreditNotationType)self.RedistributionLicense.CreditNotation, + AllowRedistribution = self.RedistributionLicense.IsAllowRedistribution, + Modification = (UniGLTF.Extensions.VRMC_vrm.ModificationType)self.RedistributionLicense.ModificationLicense, + OtherLicenseUrl = self.RedistributionLicense.OtherLicenseUrl, + References = self.References, + Authors = self.Authors, + }; + if (self.Thumbnail != null) + { + for (int i = 0; i < textures.Count; ++i) + { + var texture = textures[i] as ImageTexture; + if (texture.Image == self.Thumbnail) + { + meta.ThumbnailImage = i; + break; + } + } + } + return meta; + } + } +} diff --git a/Assets/VRM10/Runtime/IO/VrmMetaAdapter.cs.meta b/Assets/VRM10/Runtime/IO/VrmMetaAdapter.cs.meta new file mode 100644 index 000000000..27ecfcd8b --- /dev/null +++ b/Assets/VRM10/Runtime/IO/VrmMetaAdapter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ba45f21a0e4c4fd488473e928f70cd98 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/IO/VrmSpringBoneAdapter.cs b/Assets/VRM10/Runtime/IO/VrmSpringBoneAdapter.cs new file mode 100644 index 000000000..96ee5c1ed --- /dev/null +++ b/Assets/VRM10/Runtime/IO/VrmSpringBoneAdapter.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UniGLTF; +using UniGLTF.Extensions.VRMC_springBone; +using UniGLTF.Extensions.VRMC_node_collider; +using VrmLib; + +namespace UniVRM10 +{ + public static class SpringBoneAdapter + { + public static SpringSetting ToGltf(this SpringBone self, List nodes) + { + var setting = new SpringSetting + { + DragForce = self.DragForce, + GravityPower = self.GravityPower, + Stiffness = self.Stiffness, + GravityDir = self.GravityDir.ToFloat3(), + }; + return setting; + } + + public static VRMC_springBone ToGltf(this SpringBoneManager self, List nodes, + List gltfNodes) + { + if (self == null) + { + return null; + } + + var springBone = new VRMC_springBone(); + + // + // VRMC_node_collider + // + foreach (var x in self.Colliders) + { + var index = nodes.IndexOfThrow(x.Node); + var collider = new VRMC_node_collider(); + foreach (var y in x.Colliders) + { + switch (y.ColliderType) + { + case VrmSpringBoneColliderTypes.Sphere: + { + var sphere = new ColliderShapeSphere + { + Radius = y.Radius, + Offset = y.Offset.ToFloat3(), + }; + collider.Shapes.Add(new ColliderShape + { + Sphere = sphere, + }); + break; + } + + case VrmSpringBoneColliderTypes.Capsule: + { + var capsule = new ColliderShapeCapsule + { + Radius = y.Radius, + Offset = y.Offset.ToFloat3(), + Tail = y.CapsuleTail.ToFloat3(), + }; + collider.Shapes.Add(new ColliderShape + { + Capsule = capsule, + }); + } + break; + + default: + throw new NotImplementedException(); + } + } + + // + // add to node.extensions + // + UniGLTF.Extensions.VRMC_node_collider.GltfSerializer.SerializeTo(ref gltfNodes[index].extensions, collider); + } + + // + // VRMC_springBone + // + foreach (var x in self.Springs) + { + var settingIndex = springBone.Settings.Count; + springBone.Settings.Add(x.ToGltf(nodes)); + foreach (var bone in x.Bones) + { + var spring = new Spring + { + Name = x.Comment, + HitRadius = x.HitRadius, + SpringRoot = nodes.IndexOfThrow(bone), + Setting = settingIndex, + Colliders = x.Colliders.Select(y => nodes.IndexOfThrow(y.Node)).ToArray(), + }; + springBone.Springs.Add(spring); + } + } + + return springBone; + } + } +} diff --git a/Assets/VRM10/Runtime/IO/VrmSpringBoneAdapter.cs.meta b/Assets/VRM10/Runtime/IO/VrmSpringBoneAdapter.cs.meta new file mode 100644 index 000000000..5cbacdf8b --- /dev/null +++ b/Assets/VRM10/Runtime/IO/VrmSpringBoneAdapter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3d523ab537ffdbe40ae4fbd372693cb3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Scenes.meta b/Assets/VRM10/Runtime/Scenes.meta new file mode 100644 index 000000000..9847d8385 --- /dev/null +++ b/Assets/VRM10/Runtime/Scenes.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 43b4b86fab4c0d1428f8123259478c42 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Scenes/ExportDebugUtil.cs b/Assets/VRM10/Runtime/Scenes/ExportDebugUtil.cs new file mode 100644 index 000000000..a15d10603 --- /dev/null +++ b/Assets/VRM10/Runtime/Scenes/ExportDebugUtil.cs @@ -0,0 +1,34 @@ +using UniVRM10; +using VrmLib; + +public static class ExportDebugUtil +{ + public static void SaveJson(VrmLib.Model model, string path) + { + using (var stream = new System.IO.StreamWriter(path)) + { + stream.Write(GetJsonString(model)); + } + } + + public static void SaveVrm(VrmLib.Model model, string path) + { + using (var stream = new System.IO.StreamWriter(path)) + { + stream.Write(model.ToGlb()); + } + } + + public static string GetJsonString(VrmLib.Model model) + { + // export vrm-1.0 + var exporter10 = new Vrm10Exporter(); + var option = new VrmLib.ExportArgs + { + // vrm = false + }; + var glbBytes10 = exporter10.Export(model, option); + var glb10 = VrmLib.Glb.Parse(glbBytes10); + return System.Text.Encoding.UTF8.GetString(glb10.Json.Bytes.Array, glb10.Json.Bytes.Offset, glb10.Json.Bytes.Count); + } +} diff --git a/Assets/VRM10/Runtime/Scenes/ExportDebugUtil.cs.meta b/Assets/VRM10/Runtime/Scenes/ExportDebugUtil.cs.meta new file mode 100644 index 000000000..35a36d750 --- /dev/null +++ b/Assets/VRM10/Runtime/Scenes/ExportDebugUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cee5c63e6473ed848a935b446d60cccf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Scenes/Sample.cs b/Assets/VRM10/Runtime/Scenes/Sample.cs new file mode 100644 index 000000000..86abfc7c6 --- /dev/null +++ b/Assets/VRM10/Runtime/Scenes/Sample.cs @@ -0,0 +1,67 @@ +using System; +using System.IO; +using UnityEngine; +using VrmLib; +using UniVRM10; + +public class Sample : MonoBehaviour +{ + [SerializeField] + string m_vrmPath = "Tests/Models/Alicia_vrm-0.51/AliciaSolid_vrm-0.51.vrm"; + + static UniVRM10.ModelAsset Import(byte[] bytes, FileInfo path) + { + var model = UniVRM10.VrmLoader.CreateVrmModel(bytes, path); + + // UniVRM-0.XXのコンポーネントを構築する + var assets = UniVRM10.RuntimeUnityBuilder.ToUnityAsset(model, showMesh: false); + + // showRenderer = false のときに後で表示する例 + foreach (var renderer in assets.Renderers) + { + renderer.enabled = true; + } + + UniVRM10.ComponentBuilder.Build10(model, assets); + + return assets; + } + + // Start is called before the first frame update + void OnEnable() + { + var src = new FileInfo(m_vrmPath); + var vrm0x = Import(File.ReadAllBytes(m_vrmPath), src); + + // Export 1.0 + var exporter = new UniVRM10.RuntimeVrmConverter(); + var model = exporter.ToModelFrom10(vrm0x.Root); + // 右手系に変換 + model.ConvertCoordinate(VrmLib.Coordinates.Gltf); + var exportedBytes = model.ToGlb(); + + // Import 1.0 + var vrm10 = Import(exportedBytes, src); + var pos = vrm10.Root.transform.position; + pos.x += 1.5f; + vrm10.Root.transform.position = pos; + vrm10.Root.name = vrm10.Root.name + "_Imported_v1_0"; + + // write + var path = Path.GetFullPath("vrm10.vrm"); + Debug.Log($"write : {path}"); + File.WriteAllBytes(path, exportedBytes); + } + + static void Printmatrices(Model model) + { + var matrices = model.Skins[0].InverseMatrices.GetSpan(); + var sb = new System.Text.StringBuilder(); + for (int i = 0; i < matrices.Length; ++i) + { + var m = matrices[i]; + sb.AppendLine($"#{i:00}[{m.M11:.00}, {m.M12:.00}, {m.M13:.00}, {m.M14:.00}][{m.M21:.00}, {m.M22:.00}, {m.M23:.00}, {m.M24:.00}][{m.M31:.00}, {m.M32:.00}, {m.M33:.00}, {m.M34:.00}][{m.M41:.00}, {m.M42:.00}, {m.M43:.00}, {m.M44:.00}]"); + } + Debug.Log(sb.ToString()); + } +} diff --git a/Assets/VRM10/Runtime/Scenes/Sample.cs.meta b/Assets/VRM10/Runtime/Scenes/Sample.cs.meta new file mode 100644 index 000000000..3c4b0c00a --- /dev/null +++ b/Assets/VRM10/Runtime/Scenes/Sample.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d2dd74f31dd71684da8aa80eaa66390e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Scenes/SampleScene.unity b/Assets/VRM10/Runtime/Scenes/SampleScene.unity new file mode 100644 index 000000000..ecb42cea9 --- /dev/null +++ b/Assets/VRM10/Runtime/Scenes/SampleScene.unity @@ -0,0 +1,343 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 9 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 705507994} + m_IndirectSpecularColor: {r: 0.44657898, g: 0.4964133, b: 0.5748178, a: 1} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 11 + m_GIWorkflowMode: 0 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 500 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 500 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 2 + m_PVRDenoiserTypeDirect: 0 + m_PVRDenoiserTypeIndirect: 0 + m_PVRDenoiserTypeAO: 0 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 0 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_UseShadowmask: 1 +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 2 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + accuratePlacement: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &705507993 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 705507995} + - component: {fileID: 705507994} + m_Layer: 0 + m_Name: Directional Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &705507994 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 705507993} + m_Enabled: 1 + serializedVersion: 10 + m_Type: 1 + m_Shape: 0 + m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} + m_Intensity: 1 + m_Range: 10 + m_SpotAngle: 30 + m_InnerSpotAngle: 21.80208 + m_CookieSize: 10 + m_Shadows: + m_Type: 2 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_CullingMatrixOverride: + e00: 1 + e01: 0 + e02: 0 + e03: 0 + e10: 0 + e11: 1 + e12: 0 + e13: 0 + e20: 0 + e21: 0 + e22: 1 + e23: 0 + e30: 0 + e31: 0 + e32: 0 + e33: 1 + m_UseCullingMatrixOverride: 0 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingLayerMask: 1 + m_Lightmapping: 1 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} + m_UseBoundingSphereOverride: 0 + m_ShadowRadius: 0 + m_ShadowAngle: 0 +--- !u!4 &705507995 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 705507993} + m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} + m_LocalPosition: {x: 0, y: 3, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} +--- !u!1 &963194225 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 963194228} + - component: {fileID: 963194227} + - component: {fileID: 963194226} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &963194226 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 963194225} + m_Enabled: 1 +--- !u!20 &963194227 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 963194225} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_FocalLength: 50 + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 0 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &963194228 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 963194225} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 1, z: -10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1706915411 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1706915413} + - component: {fileID: 1706915412} + m_Layer: 0 + m_Name: Sample + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1706915412 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1706915411} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d2dd74f31dd71684da8aa80eaa66390e, type: 3} + m_Name: + m_EditorClassIdentifier: + m_vrmPath: ../UniVRM_1_0/Tests/Models/Alicia_vrm-0.51/AliciaSolid_vrm-0.51.vrm + m_rightHandToLeftHand: 1 +--- !u!4 &1706915413 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1706915411} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} diff --git a/Assets/VRM10/Runtime/Scenes/SampleScene.unity.meta b/Assets/VRM10/Runtime/Scenes/SampleScene.unity.meta new file mode 100644 index 000000000..eb52b1505 --- /dev/null +++ b/Assets/VRM10/Runtime/Scenes/SampleScene.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 25e2f68464c55bf47a9d96514e9d207c +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/StringExtensions.cs b/Assets/VRM10/Runtime/StringExtensions.cs new file mode 100644 index 000000000..efa180350 --- /dev/null +++ b/Assets/VRM10/Runtime/StringExtensions.cs @@ -0,0 +1,73 @@ +using System.IO; +using UnityEngine; + +namespace UniVRM10 +{ + public static class StringExtensions + { + public static string ToLowerCamelCase(this string lower) + { + return lower.Substring(0, 1).ToLower() + lower.Substring(1); + } + public static string ToUpperCamelCase(this string lower) + { + return lower.Substring(0, 1).ToUpper() + lower.Substring(1); + } + + static string m_unityBasePath; + public static string UnityBasePath + { + get + { + if (m_unityBasePath == null) + { + m_unityBasePath = Path.GetFullPath(Application.dataPath + "/..").Replace("\\", "/"); + } + return m_unityBasePath; + } + } + + public static string AssetPathToFullPath(this string path) + { + return UnityBasePath + "/" + path; + } + + public static bool StartsWithUnityAssetPath(this string path) + { + return path.Replace("\\", "/").StartsWith(UnityBasePath + "/Assets"); + } + + public static string ToUnityRelativePath(this string path) + { + path = path.Replace("\\", "/"); + if (path.StartsWith(UnityBasePath)) + { + return path.Substring(UnityBasePath.Length + 1); + } + + //Debug.LogWarningFormat("{0} is starts with {1}", path, basePath); + return path; + } + + static readonly char[] EscapeChars = new char[] + { + '\\', + '/', + ':', + '*', + '?', + '"', + '<', + '>', + '|', + }; + public static string EscapeFilePath(this string path) + { + foreach(var x in EscapeChars) + { + path = path.Replace(x, '+'); + } + return path; + } + } +} diff --git a/Assets/VRM10/Runtime/StringExtensions.cs.meta b/Assets/VRM10/Runtime/StringExtensions.cs.meta new file mode 100644 index 000000000..437e2b7e1 --- /dev/null +++ b/Assets/VRM10/Runtime/StringExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fb97f56629365cd48a8ede4bf2a4e634 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/TextureConvert.meta b/Assets/VRM10/Runtime/TextureConvert.meta new file mode 100644 index 000000000..df4eb024d --- /dev/null +++ b/Assets/VRM10/Runtime/TextureConvert.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 525d94ed40283ed4aa4221e3d2ec77c8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/TextureConvert/Resources.meta b/Assets/VRM10/Runtime/TextureConvert/Resources.meta new file mode 100644 index 000000000..7f47e9549 --- /dev/null +++ b/Assets/VRM10/Runtime/TextureConvert/Resources.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8e4619bbdb366814995d35796e9fe54a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders.meta b/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders.meta new file mode 100644 index 000000000..106573a28 --- /dev/null +++ b/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a283f0df584c01e4d94d8ff1679481fd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/MetallicRoughnessGltfToUnity.shader b/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/MetallicRoughnessGltfToUnity.shader new file mode 100644 index 000000000..a291f51a4 --- /dev/null +++ b/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/MetallicRoughnessGltfToUnity.shader @@ -0,0 +1,54 @@ +Shader "UniVRM/MetallicRoughnessGltfToUnity" +{ + Properties + { + _MainTex ("Texture", 2D) = "white" {} + _Roughness("Roughness", Float) = 1.0 + } + SubShader + { + // No culling or depth + Cull Off ZWrite Off ZTest Always + + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + + #include "UnityCG.cginc" + + struct appdata + { + float4 vertex : POSITION; + float2 uv : TEXCOORD0; + }; + + struct v2f + { + float2 uv : TEXCOORD0; + float4 vertex : SV_POSITION; + }; + + v2f vert (appdata v) + { + v2f o; + o.vertex = UnityObjectToClipPos(v.vertex); + o.uv = v.uv; + return o; + } + + sampler2D _MainTex; + float _Roughness; + + float4 frag (v2f i) : SV_Target + { + float4 col = tex2D(_MainTex, i.uv); + float pixelRoughnessFactor = (col.g * _Roughness); + float pixelSmoothness = 1.0f - sqrt(pixelRoughnessFactor); + return float4(col.b, 0, 0, clamp(pixelSmoothness, 0, 1.0)); + } + ENDCG + } + } +} diff --git a/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/MetallicRoughnessGltfToUnity.shader.meta b/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/MetallicRoughnessGltfToUnity.shader.meta new file mode 100644 index 000000000..4b8ca61aa --- /dev/null +++ b/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/MetallicRoughnessGltfToUnity.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 003d81d58d8b22442919cc6f9f288478 +timeCreated: 1533558728 +licenseType: Free +ShaderImporter: + defaultTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/MetallicRoughnessUnityToGltf.shader b/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/MetallicRoughnessUnityToGltf.shader new file mode 100644 index 000000000..2ab38d79e --- /dev/null +++ b/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/MetallicRoughnessUnityToGltf.shader @@ -0,0 +1,55 @@ +Shader "UniVRM/MetallicRoughnessUnityToGltf" +{ + Properties + { + _MainTex ("Texture", 2D) = "white" {} + _Smoothness("Smoothness", Float) = 1.0 + } + SubShader + { + // No culling or depth + Cull Off ZWrite Off ZTest Always + + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + + #include "UnityCG.cginc" + + struct appdata + { + float4 vertex : POSITION; + float2 uv : TEXCOORD0; + }; + + struct v2f + { + float2 uv : TEXCOORD0; + float4 vertex : SV_POSITION; + }; + + v2f vert (appdata v) + { + v2f o; + o.vertex = UnityObjectToClipPos(v.vertex); + o.uv = v.uv; + return o; + } + + sampler2D _MainTex; + float _Smoothness; + + float4 frag (v2f i) : SV_Target + { + float4 col = tex2D(_MainTex, i.uv); + float pixelSmoothness = (col.a * _Smoothness); + float pixelRoughnessFactorSqrt = (1.0f - pixelSmoothness); + float pixelRoughnessFactor = pixelRoughnessFactorSqrt * pixelRoughnessFactorSqrt; + return float4(0, clamp(pixelRoughnessFactor, 0, 1.0), col.r, 1); + } + ENDCG + } + } +} diff --git a/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/MetallicRoughnessUnityToGltf.shader.meta b/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/MetallicRoughnessUnityToGltf.shader.meta new file mode 100644 index 000000000..edb3ff4fc --- /dev/null +++ b/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/MetallicRoughnessUnityToGltf.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: d2f882a5cd7d0ba479fd8e426f90c53f +timeCreated: 1533558728 +licenseType: Free +ShaderImporter: + defaultTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/NormalMapGltfToUnity.shader b/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/NormalMapGltfToUnity.shader new file mode 100644 index 000000000..80846f845 --- /dev/null +++ b/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/NormalMapGltfToUnity.shader @@ -0,0 +1,63 @@ +Shader "UniVRM/NormalMapGltfToUnity" +{ + Properties + { + _MainTex("Texture", 2D) = "white" {} + } + SubShader + { + // No culling or depth + Cull Off ZWrite Off ZTest Always + + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + + #include "UnityCG.cginc" + + struct appdata + { + float4 vertex : POSITION; + float2 uv : TEXCOORD0; + }; + + struct v2f + { + float2 uv : TEXCOORD0; + float4 vertex : SV_POSITION; + }; + + v2f vert(appdata v) + { + v2f o; + o.vertex = UnityObjectToClipPos(v.vertex); + o.uv = v.uv; + return o; + } + + sampler2D _MainTex; + + fixed4 frag(v2f i) : SV_Target + { + half4 col = tex2D(_MainTex, i.uv); + +#if defined(UNITY_NO_DXT5nm) + // This is a trick from UnpackNormal in UnityCG.cginc !!!! + return col; +#endif + + half4 normal; + normal.x = 1.0; + normal.y = col.y; + normal.z = 1.0; + normal.w = col.x; + + return normal; + } + ENDCG + } + } +} + diff --git a/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/NormalMapGltfToUnity.shader.meta b/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/NormalMapGltfToUnity.shader.meta new file mode 100644 index 000000000..db0717e2d --- /dev/null +++ b/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/NormalMapGltfToUnity.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: d677f9f201a86264e86825ae708b1182 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/NormalMapUnityToGltf.shader b/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/NormalMapUnityToGltf.shader new file mode 100644 index 000000000..15c3ccb24 --- /dev/null +++ b/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/NormalMapUnityToGltf.shader @@ -0,0 +1,54 @@ +Shader "UniVRM/NormalMapUnityToGltf" +{ + Properties + { + _MainTex ("Texture", 2D) = "white" {} + } + SubShader + { + // No culling or depth + Cull Off ZWrite Off ZTest Always + + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + + #include "UnityCG.cginc" + + struct appdata + { + float4 vertex : POSITION; + float2 uv : TEXCOORD0; + }; + + struct v2f + { + float2 uv : TEXCOORD0; + float4 vertex : SV_POSITION; + }; + + v2f vert (appdata v) + { + v2f o; + o.vertex = UnityObjectToClipPos(v.vertex); + o.uv = v.uv; + return o; + } + + sampler2D _MainTex; + + fixed4 frag (v2f i) : SV_Target + { + half4 col = tex2D(_MainTex, i.uv); + + col.xyz = (UnpackNormal(col) + 1) * 0.5; + col.w = 1; + + return col; + } + ENDCG + } + } +} diff --git a/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/NormalMapUnityToGltf.shader.meta b/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/NormalMapUnityToGltf.shader.meta new file mode 100644 index 000000000..3d8fdb299 --- /dev/null +++ b/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/NormalMapUnityToGltf.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: d8e01ada382f1004b83caff6b601af85 +timeCreated: 1533558728 +licenseType: Free +ShaderImporter: + defaultTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/OcclusionGltfToUnity.shader b/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/OcclusionGltfToUnity.shader new file mode 100644 index 000000000..dce77062c --- /dev/null +++ b/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/OcclusionGltfToUnity.shader @@ -0,0 +1,50 @@ +Shader "UniVRM/OcclusionGltfToUnity" +{ + Properties + { + _MainTex ("Texture", 2D) = "white" {} + } + SubShader + { + // No culling or depth + Cull Off ZWrite Off ZTest Always + + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + + #include "UnityCG.cginc" + + struct appdata + { + float4 vertex : POSITION; + float2 uv : TEXCOORD0; + }; + + struct v2f + { + float2 uv : TEXCOORD0; + float4 vertex : SV_POSITION; + }; + + v2f vert (appdata v) + { + v2f o; + o.vertex = UnityObjectToClipPos(v.vertex); + o.uv = v.uv; + return o; + } + + sampler2D _MainTex; + + fixed4 frag (v2f i) : SV_Target + { + half4 col = tex2D(_MainTex, i.uv); + return half4(0, col.r, 0, 1); + } + ENDCG + } + } +} diff --git a/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/OcclusionGltfToUnity.shader.meta b/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/OcclusionGltfToUnity.shader.meta new file mode 100644 index 000000000..4b7ac07b9 --- /dev/null +++ b/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/OcclusionGltfToUnity.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 6e25a54043ee42d47a630b18f133a0bf +timeCreated: 1533558728 +licenseType: Free +ShaderImporter: + defaultTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/OcclusionUnityToGltf.shader b/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/OcclusionUnityToGltf.shader new file mode 100644 index 000000000..c73a3bd03 --- /dev/null +++ b/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/OcclusionUnityToGltf.shader @@ -0,0 +1,50 @@ +Shader "UniVRM/OcclusionUnityToGltf" +{ + Properties + { + _MainTex ("Texture", 2D) = "white" {} + } + SubShader + { + // No culling or depth + Cull Off ZWrite Off ZTest Always + + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + + #include "UnityCG.cginc" + + struct appdata + { + float4 vertex : POSITION; + float2 uv : TEXCOORD0; + }; + + struct v2f + { + float2 uv : TEXCOORD0; + float4 vertex : SV_POSITION; + }; + + v2f vert (appdata v) + { + v2f o; + o.vertex = UnityObjectToClipPos(v.vertex); + o.uv = v.uv; + return o; + } + + sampler2D _MainTex; + + fixed4 frag (v2f i) : SV_Target + { + half4 col = tex2D(_MainTex, i.uv); + return half4(col.g, 0, 0, 1); + } + ENDCG + } + } +} diff --git a/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/OcclusionUnityToGltf.shader.meta b/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/OcclusionUnityToGltf.shader.meta new file mode 100644 index 000000000..ebb47371c --- /dev/null +++ b/Assets/VRM10/Runtime/TextureConvert/Resources/Shaders/OcclusionUnityToGltf.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: f87e9edf9bf3c364db108ff75a8730ce +timeCreated: 1533558728 +licenseType: Free +ShaderImporter: + defaultTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/TextureConvert/TextureConvertMaterial.cs b/Assets/VRM10/Runtime/TextureConvert/TextureConvertMaterial.cs new file mode 100644 index 000000000..4a56e76fd --- /dev/null +++ b/Assets/VRM10/Runtime/TextureConvert/TextureConvertMaterial.cs @@ -0,0 +1,56 @@ +using UnityEngine; + +namespace UniVRM10 +{ + public static class TextureConvertMaterial + { + + #region Normalmap + // GLTF data to Unity texture + // ConvertToNormalValueFromRawColorWhenCompressionIsRequired + public static Material GetNormalMapConvertGltfToUnity() + { + return new Material(Shader.Find("UniVRM/NormalMapGltfToUnity")); + } + + // Unity texture to GLTF data + // ConvertToRawColorWhenNormalValueIsCompressed + public static Material GetNormalMapConvertUnityToGltf() + { + return new Material(Shader.Find("UniVRM/NormalMapUnityToGltf")); + } + #endregion + + #region MetallicRoughness + // GLTF data to Unity texture + public static Material GetMetallicRoughnessGltfToUnity(float roughnessFactor) + { + var material = new Material(Shader.Find("UniVRM/MetallicRoughnessGltfToUnity")); + material.SetFloat("_Roughness", roughnessFactor); + return material; + } + + // Unity texture to GLTF data + public static Material GetMetallicRoughnessUnityToGltf(float smoothness) + { + var material = new Material(Shader.Find("UniVRM/MetallicRoughnessUnityToGltf")); + material.SetFloat("_Smoothness", smoothness); + return material; + } + #endregion + + #region Occlusion + // GLTF data to Unity texture + public static Material GetOcclusionGltfToUnity() + { + return new Material(Shader.Find("UniVRM/OcclusionGltfToUnity")); + } + + // Unity texture to GLTF data + public static Material GetOcclusionUnityToGltf() + { + return new Material(Shader.Find("UniVRM/OcclusionUnityToGltf")); + } + #endregion + } +} diff --git a/Assets/VRM10/Runtime/TextureConvert/TextureConvertMaterial.cs.meta b/Assets/VRM10/Runtime/TextureConvert/TextureConvertMaterial.cs.meta new file mode 100644 index 000000000..fd04ce24b --- /dev/null +++ b/Assets/VRM10/Runtime/TextureConvert/TextureConvertMaterial.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4edaed230e46d9a429f9d4252f19dda3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/UnityBuilder.meta b/Assets/VRM10/Runtime/UnityBuilder.meta new file mode 100644 index 000000000..1c32b98dc --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 49662dd147c59e64e913f70aefd0e999 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/UnityBuilder/ComponentBuilder.cs b/Assets/VRM10/Runtime/UnityBuilder/ComponentBuilder.cs new file mode 100644 index 000000000..47c078174 --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/ComponentBuilder.cs @@ -0,0 +1,311 @@ +using System; +using UnityEngine; +using System.Linq; +using System.Collections.Generic; + +namespace UniVRM10 +{ + public static class ComponentBuilder + { + #region Util + static (Transform, Mesh) GetTransformAndMesh(Transform t) + { + var skinnedMeshRenderer = t.GetComponent(); + if (skinnedMeshRenderer != null) + { + return (t, skinnedMeshRenderer.sharedMesh); + } + + var filter = t.GetComponent(); + if (filter != null) + { + return (t, filter.sharedMesh); + } + + return default; + } + #endregion + + #region Build10 + + static UniVRM10.MorphTargetBinding Build10(this VrmLib.MorphTargetBind bind, GameObject root, ModelMap loader) + { + var node = loader.Nodes[bind.Node].transform; + var mesh = loader.Meshes[bind.Node.MeshGroup]; + // var transformMeshTable = loader.Root.transform.Traverse() + // .Select(GetTransformAndMesh) + // .Where(x => x.Item2 != null) + // .ToDictionary(x => x.Item2, x => x.Item1); + // var node = transformMeshTable[mesh]; + // var transform = loader.Nodes[node].transform; + var relativePath = node.RelativePathFrom(root.transform); + + var names = new List(); + for (int i = 0; i < mesh.blendShapeCount; ++i) + { + names.Add(mesh.GetBlendShapeName(i)); + } + + return new UniVRM10.MorphTargetBinding + { + RelativePath = relativePath, + Index = names.IndexOf(bind.Name), + Weight = bind.Value, + }; + } + + static UniVRM10.MaterialColorBinding? Build10(this VrmLib.MaterialColorBind bind, ModelMap loader) + { + var kv = bind.Property; + var value = kv.Value.ToUnityVector4(); + var material = loader.Materials[bind.Material]; + + var binding = default(UniVRM10.MaterialColorBinding?); + if (material != null) + { + try + { + binding = new UniVRM10.MaterialColorBinding + { + MaterialName = bind.Material.Name, // UniVRM-0Xの実装は名前で持っている + BindType = bind.BindType, + TargetValue = value, + // BaseValue = material.GetColor(kv.Key), + }; + } + catch (Exception) + { + // do nothing + } + } + return binding; + } + + static UniVRM10.MaterialUVBinding? Build10(this VrmLib.TextureTransformBind bind, ModelMap loader) + { + var material = loader.Materials[bind.Material]; + + var binding = default(UniVRM10.MaterialUVBinding?); + if (material != null) + { + try + { + binding = new UniVRM10.MaterialUVBinding + { + MaterialName = bind.Material.Name, // UniVRM-0Xの実装は名前で持っている + Scaling = new Vector2(bind.Scale.X, bind.Scale.Y), + Offset = new Vector2(bind.Offset.X, bind.Offset.Y), + }; + } + catch (Exception) + { + // do nothing + } + } + return binding; + } + + public static void Build10(VrmLib.Model model, ModelAsset asset) + { + // meta + var controller = asset.Root.AddComponent(); + { + var meta = model.Vrm.Meta; + controller.Meta = ScriptableObject.CreateInstance(); + controller.Meta.Name = meta.Name; + controller.Meta.Version = meta.Version; + controller.Meta.CopyrightInformation = meta.CopyrightInformation; + controller.Meta.Authors = meta.Authors.ToArray(); + controller.Meta.ContactInformation = meta.ContactInformation; + controller.Meta.Reference = meta.Reference; + var thumbnailImages = asset.Map.Textures.Where(x => ((VrmLib.ImageTexture)x.Key).Image == meta.Thumbnail); + if (meta.Thumbnail != null && thumbnailImages.Count() > 0) + { + controller.Meta.Thumbnail = thumbnailImages.First().Value; + } + else if (meta.Thumbnail != null && meta.Thumbnail.Bytes.Count > 0) + { + var thumbnail = new Texture2D(2, 2, TextureFormat.ARGB32, false, false); + thumbnail.name = "Thumbnail"; + thumbnail.LoadImage(meta.Thumbnail.Bytes.ToArray()); + controller.Meta.Thumbnail = thumbnail; + asset.Textures.Add(thumbnail); + } + // avatar permission + controller.Meta.AllowedUser = meta.AvatarPermission.AvatarUsage; + controller.Meta.ViolentUsage = meta.AvatarPermission.IsAllowedViolentUsage; + controller.Meta.SexualUsage = meta.AvatarPermission.IsAllowedSexualUsage; + controller.Meta.CommercialUsage = meta.AvatarPermission.CommercialUsage; + controller.Meta.GameUsage = meta.AvatarPermission.IsAllowedGameUsage; + controller.Meta.PoliticalOrReligiousUsage = meta.AvatarPermission.IsAllowedPoliticalOrReligiousUsage; + controller.Meta.OtherPermissionUrl = meta.AvatarPermission.OtherPermissionUrl; + + // redistribution license + controller.Meta.CreditNotation = meta.RedistributionLicense.CreditNotation; + controller.Meta.ModificationLicense = meta.RedistributionLicense.ModificationLicense; + controller.Meta.Redistribution = meta.RedistributionLicense.IsAllowRedistribution; + controller.Meta.OtherLicenseUrl = meta.RedistributionLicense.OtherLicenseUrl; + + asset.ScriptableObjects.Add(controller.Meta); + } + + // expression + { + controller.Expression.ExpressionAvatar = ScriptableObject.CreateInstance(); + asset.ScriptableObjects.Add(controller.Expression.ExpressionAvatar); + if (model.Vrm.ExpressionManager != null) + { + foreach (var expression in model.Vrm.ExpressionManager.ExpressionList) + { + var clip = ScriptableObject.CreateInstance(); + clip.Preset = expression.Preset; + clip.ExpressionName = expression.Name; + clip.IsBinary = expression.IsBinary; + clip.IgnoreBlink = expression.IgnoreBlink; + clip.IgnoreLookAt = expression.IgnoreLookAt; + clip.IgnoreMouth = expression.IgnoreMouth; + + clip.MorphTargetBindings = expression.MorphTargetBinds.Select(x => x.Build10(asset.Root, asset.Map)) + .ToArray(); + clip.MaterialColorBindings = expression.MaterialColorBinds.Select(x => x.Build10(asset.Map)) + .Where(x => x.HasValue) + .Select(x => x.Value) + .ToArray(); + clip.MaterialUVBindings = expression.TextureTransformBinds.Select(x => x.Build10(asset.Map)) + .Where(x => x.HasValue) + .Select(x => x.Value) + .ToArray(); + controller.Expression.ExpressionAvatar.Clips.Add(clip); + asset.ScriptableObjects.Add(clip); + } + } + } + + // firstPerson + { + // VRMFirstPerson + controller.FirstPerson.Renderers = model.Vrm.FirstPerson.Annotations.Select(x => + new UniVRM10.RendererFirstPersonFlags() + { + Renderer = asset.Map.Renderers[x.Node], + FirstPersonFlag = x.FirstPersonFlag + } + ).ToList(); + + // VRMLookAtApplyer + controller.LookAt.OffsetFromHead = model.Vrm.LookAt.OffsetFromHeadBone.ToUnityVector3(); + if (model.Vrm.LookAt.LookAtType == VrmLib.LookAtType.Expression) + { + var lookAtApplyer = controller; + lookAtApplyer.LookAt.LookAtType = VRM10ControllerLookAt.LookAtTypes.Expression; + lookAtApplyer.LookAt.HorizontalOuter = new UniVRM10.CurveMapper( + model.Vrm.LookAt.HorizontalOuter.InputMaxValue, + model.Vrm.LookAt.HorizontalOuter.OutputScaling); + lookAtApplyer.LookAt.VerticalUp = new UniVRM10.CurveMapper( + model.Vrm.LookAt.VerticalUp.InputMaxValue, + model.Vrm.LookAt.VerticalUp.OutputScaling); + lookAtApplyer.LookAt.VerticalDown = new UniVRM10.CurveMapper( + model.Vrm.LookAt.VerticalDown.InputMaxValue, + model.Vrm.LookAt.VerticalDown.OutputScaling); + } + else if (model.Vrm.LookAt.LookAtType == VrmLib.LookAtType.Bone) + { + var lookAtBoneApplyer = controller; + lookAtBoneApplyer.LookAt.HorizontalInner = new UniVRM10.CurveMapper( + model.Vrm.LookAt.HorizontalInner.InputMaxValue, + model.Vrm.LookAt.HorizontalInner.OutputScaling); + lookAtBoneApplyer.LookAt.HorizontalOuter = new UniVRM10.CurveMapper( + model.Vrm.LookAt.HorizontalOuter.InputMaxValue, + model.Vrm.LookAt.HorizontalOuter.OutputScaling); + lookAtBoneApplyer.LookAt.VerticalUp = new UniVRM10.CurveMapper( + model.Vrm.LookAt.VerticalUp.InputMaxValue, + model.Vrm.LookAt.VerticalUp.OutputScaling); + lookAtBoneApplyer.LookAt.VerticalDown = new UniVRM10.CurveMapper( + model.Vrm.LookAt.VerticalDown.InputMaxValue, + model.Vrm.LookAt.VerticalDown.OutputScaling); + } + else + { + throw new NotImplementedException(); + } + } + + // springBone + { + var colliders = new Dictionary(); + if (model.Vrm.SpringBone != null) + { + foreach (var colliderGroup in model.Vrm.SpringBone.Colliders) + { + var go = asset.Map.Nodes[colliderGroup.Node]; + var springBoneColliderGroup = go.AddComponent(); + + springBoneColliderGroup.Colliders = colliderGroup.Colliders.Select(x => + { + switch (x.ColliderType) + { + case VrmLib.VrmSpringBoneColliderTypes.Sphere: + return new UniVRM10.SpringBoneCollider() + { + ColliderType = SpringBoneColliderTypes.Sphere, + Offset = x.Offset.ToUnityVector3(), + Radius = x.Radius + }; + + case VrmLib.VrmSpringBoneColliderTypes.Capsule: + return new UniVRM10.SpringBoneCollider() + { + ColliderType = SpringBoneColliderTypes.Capsule, + Offset = x.Offset.ToUnityVector3(), + Radius = x.Radius, + Tail = x.CapsuleTail.ToUnityVector3(), + }; + + default: + throw new NotImplementedException(); + } + }).ToArray(); ; + + colliders.Add(colliderGroup, springBoneColliderGroup); + } + } + + GameObject springBoneObject = null; + var springBoneTransform = asset.Root.transform.GetChildren().FirstOrDefault(x => x.name == "SpringBone"); + if (springBoneTransform == null) + { + springBoneObject = new GameObject("SpringBone"); + } + else + { + springBoneObject = springBoneTransform.gameObject; + } + + springBoneObject.transform.SetParent(asset.Root.transform); + if (model.Vrm.SpringBone != null) + { + foreach (var spring in model.Vrm.SpringBone.Springs) + { + var springBoneComponent = springBoneObject.AddComponent(); + springBoneComponent.m_comment = spring.Comment; + springBoneComponent.m_stiffnessForce = spring.Stiffness; + springBoneComponent.m_gravityPower = spring.GravityPower; + springBoneComponent.m_gravityDir = spring.GravityDir.ToUnityVector3(); + springBoneComponent.m_dragForce = spring.DragForce; + if (spring.Origin != null && asset.Map.Nodes.TryGetValue(spring.Origin, out GameObject origin)) + { + springBoneComponent.m_center = origin.transform; + } + springBoneComponent.RootBones = spring.Bones.Select(x => asset.Map.Nodes[x].transform).ToList(); + springBoneComponent.m_hitRadius = spring.HitRadius; + springBoneComponent.ColliderGroups = spring.Colliders.Select(x => colliders[x]).ToArray(); + } + } + } + + // Assets + controller.ModelAsset = asset; + } + #endregion + } +} diff --git a/Assets/VRM10/Runtime/UnityBuilder/ComponentBuilder.cs.meta b/Assets/VRM10/Runtime/UnityBuilder/ComponentBuilder.cs.meta new file mode 100644 index 000000000..95f8dff4e --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/ComponentBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a6cc4e6978a4fdf40999cc13aaa472fe +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/UnityBuilder/DictionaryExtensions.cs b/Assets/VRM10/Runtime/UnityBuilder/DictionaryExtensions.cs new file mode 100644 index 000000000..5cddb3de7 --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/DictionaryExtensions.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; + +namespace UniVRM10 +{ + public static class DictionaryExtensions + { + public static U GetOrDefault(this Dictionary d, T key) + { + if(key == null) + { + return default; + } + + if(d.TryGetValue(key, out U value)) + { + return value;; + } + else{ + return default; + } + } + } +} diff --git a/Assets/VRM10/Runtime/UnityBuilder/DictionaryExtensions.cs.meta b/Assets/VRM10/Runtime/UnityBuilder/DictionaryExtensions.cs.meta new file mode 100644 index 000000000..05d8b5981 --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/DictionaryExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 936824d3022642f40a65a003e0ee3b83 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/UnityBuilder/Editor.meta b/Assets/VRM10/Runtime/UnityBuilder/Editor.meta new file mode 100644 index 000000000..bc3af4797 --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 117159fa2bfe3824689e2566a48ea457 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter.meta b/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter.meta new file mode 100644 index 000000000..c768d831b --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ac495244a4434c741850fb75768d4ef9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/GltfScriptedImporter.cs b/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/GltfScriptedImporter.cs new file mode 100644 index 000000000..f006ab792 --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/GltfScriptedImporter.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEditor; +using UnityEditor.Experimental.AssetImporters; +using UnityEngine; +using VrmLib; + +namespace UniVRM10 +{ + + [ScriptedImporter(1, "glb")] + public class GltfScriptedImporter : ScriptedImporter, IExternalUnityObject + { + const string TextureDirName = "Textures"; + const string MaterialDirName = "Materials"; + + public override void OnImportAsset(AssetImportContext ctx) + { + Debug.Log("OnImportAsset to " + ctx.assetPath); + + try + { + // Create model + VrmLib.Model model = CreateGlbModel(ctx.assetPath); + Debug.Log($"ModelLoader.Load: {model}"); + + // Build Unity Model + var assets = EditorUnityBuilder.ToUnityAsset(model, assetPath, this); + + // Texture + var externalTextures = this.GetExternalUnityObjects(); + foreach (var texture in assets.Textures) + { + if (texture == null) + continue; + + if (externalTextures.ContainsValue(texture)) + { + } + else + { + ctx.AddObjectToAsset(texture.name, texture); + } + } + + // Material + var externalMaterials = this.GetExternalUnityObjects(); + foreach (var material in assets.Materials) + { + if (material == null) + continue; + + if (externalMaterials.ContainsValue(material)) + { + + } + else + { + ctx.AddObjectToAsset(material.name, material); + } + } + + // Mesh + foreach (var mesh in assets.Meshes) + { + ctx.AddObjectToAsset(mesh.name, mesh); + } + + // Root + ctx.AddObjectToAsset(assets.Root.name, assets.Root); + ctx.SetMainObject(assets.Root); + + } + catch (System.Exception ex) + { + Debug.LogError(ex); + } + } + + private Model CreateGlbModel(string path) + { + var bytes = File.ReadAllBytes(path); + if (!VrmLib.Glb.TryParse(bytes, out VrmLib.Glb glb, out Exception ex)) + { + throw ex; + } + + VrmLib.Model model = null; + VrmLib.IVrmStorage storage; + storage = new Vrm10Storage(glb.Json.Bytes, glb.Binary.Bytes); + model = VrmLib.ModelLoader.Load(storage, Path.GetFileNameWithoutExtension(path)); + model.ConvertCoordinate(VrmLib.Coordinates.Unity); + + return model; + } + + public void ExtractTextures() + { + this.ExtractTextures(TextureDirName, (path) => { return CreateGlbModel(path); }); + AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate); + } + + public void ExtractMaterials() + { + this.ExtractAssets(MaterialDirName, ".mat"); + AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate); + } + + public void ExtractMaterialsAndTextures() + { + this.ExtractTextures(TextureDirName, (path) => { return CreateGlbModel(path); }, () => { this.ExtractAssets(MaterialDirName, ".mat"); }); + AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate); + } + + + + + public Dictionary GetExternalUnityObjects() where T : UnityEngine.Object + { + return this.GetExternalObjectMap().Where(x => x.Key.type == typeof(T)).ToDictionary(x => x.Key.name, x => (T)x.Value); + } + + public void SetExternalUnityObject(UnityEditor.AssetImporter.SourceAssetIdentifier sourceAssetIdentifier, T obj) where T : UnityEngine.Object + { + this.AddRemap(sourceAssetIdentifier, obj); + AssetDatabase.WriteImportSettingsIfDirty(this.assetPath); + AssetDatabase.ImportAsset(this.assetPath, ImportAssetOptions.ForceUpdate); + } + } +} + + diff --git a/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/GltfScriptedImporter.cs.meta b/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/GltfScriptedImporter.cs.meta new file mode 100644 index 000000000..d016d7b3a --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/GltfScriptedImporter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5f9566aa8f690614f872fc63691397e9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/GltfScriptedImporterEditorGUI.cs b/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/GltfScriptedImporterEditorGUI.cs new file mode 100644 index 000000000..596dc6a66 --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/GltfScriptedImporterEditorGUI.cs @@ -0,0 +1,65 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEditor.Experimental.AssetImporters; +using UnityEngine; + +namespace UniVRM10 +{ + [CustomEditor(typeof(GltfScriptedImporter))] + public class GltfScriptedImporterEditorGUI : ScriptedImporterEditor + { + + private bool _isOpen = true; + + public override void OnInspectorGUI() + { + var importer = target as GltfScriptedImporter; + + EditorGUILayout.LabelField("Extract settings"); + + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.PrefixLabel("Materials And Textures"); + GUI.enabled = !(importer.GetExternalUnityObjects().Any() + && importer.GetExternalUnityObjects().Any()); + if (GUILayout.Button("Extract")) + { + importer.ExtractMaterialsAndTextures(); + } + GUI.enabled = !GUI.enabled; + if (GUILayout.Button("Clear")) + { + importer.ClearExternalObjects(); + importer.ClearExternalObjects(); + } + GUI.enabled = true; + EditorGUILayout.EndHorizontal(); + + // ObjectMap + DrawRemapGUI("Material Remap", importer); + DrawRemapGUI("Texture Remap", importer); + + base.OnInspectorGUI(); + } + + private void DrawRemapGUI(string title, GltfScriptedImporter importer) where T: UnityEngine.Object + { + EditorGUILayout.Foldout(_isOpen, title); + EditorGUI.indentLevel++; + var objects = importer.GetExternalObjectMap().Where(x => x.Key.type == typeof(T)); + foreach (var obj in objects) + { + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.PrefixLabel(obj.Key.name); + var asset = EditorGUILayout.ObjectField(obj.Value, obj.Key.type, true) as T; + if(asset != obj.Value) + { + importer.SetExternalUnityObject(obj.Key, asset); + } + EditorGUILayout.EndHorizontal(); + } + EditorGUI.indentLevel--; + } + } +} diff --git a/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/GltfScriptedImporterEditorGUI.cs.meta b/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/GltfScriptedImporterEditorGUI.cs.meta new file mode 100644 index 000000000..6efbfa7a9 --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/GltfScriptedImporterEditorGUI.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d4faa0a7a13c9c1489b5eed3a9575a01 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/IExternalUnityObject.cs b/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/IExternalUnityObject.cs new file mode 100644 index 000000000..996e9be8b --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/IExternalUnityObject.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace UniVRM10 +{ + public interface IExternalUnityObject + { + Dictionary GetExternalUnityObjects() where T : UnityEngine.Object; + void SetExternalUnityObject(UnityEditor.AssetImporter.SourceAssetIdentifier sourceAssetIdentifier, T obj) where T : UnityEngine.Object; + } +} + + diff --git a/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/IExternalUnityObject.cs.meta b/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/IExternalUnityObject.cs.meta new file mode 100644 index 000000000..fe35916e7 --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/IExternalUnityObject.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8e6d15fc36df79649bb4724b06b5eae3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/ScriptedImporterExtension.cs b/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/ScriptedImporterExtension.cs new file mode 100644 index 000000000..361a492ba --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/ScriptedImporterExtension.cs @@ -0,0 +1,176 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEditor.Experimental.AssetImporters; +using UnityEditor; +using System; +using UnityEngine; + +namespace UniVRM10 +{ + public static class ScriptedImporterExtension + { + public static void ClearExternalObjects(this ScriptedImporter importer) where T : UnityEngine.Object + { + foreach (var extarnalObject in importer.GetExternalObjectMap().Where(x => x.Key.type == typeof(T))) + { + importer.RemoveRemap(extarnalObject.Key); + } + + AssetDatabase.WriteImportSettingsIfDirty(importer.assetPath); + AssetDatabase.ImportAsset(importer.assetPath, ImportAssetOptions.ForceUpdate); + } + + public static void ClearExtarnalObjects(this ScriptedImporter importer) + { + foreach (var extarnalObject in importer.GetExternalObjectMap()) + { + importer.RemoveRemap(extarnalObject.Key); + } + + AssetDatabase.WriteImportSettingsIfDirty(importer.assetPath); + AssetDatabase.ImportAsset(importer.assetPath, ImportAssetOptions.ForceUpdate); + } + + private static T GetSubAsset(this ScriptedImporter importer, string assetPath) where T : UnityEngine.Object + { + return importer.GetSubAssets(assetPath) + .FirstOrDefault(); + } + + public static IEnumerable GetSubAssets(this ScriptedImporter importer, string assetPath) where T : UnityEngine.Object + { + return AssetDatabase + .LoadAllAssetsAtPath(assetPath) + .Where(x => AssetDatabase.IsSubAsset(x)) + .Where(x => x is T) + .Select(x => x as T); + } + + private static void ExtractFromAsset(UnityEngine.Object subAsset, string destinationPath, bool isForceUpdate) + { + string assetPath = AssetDatabase.GetAssetPath(subAsset); + + var clone = UnityEngine.Object.Instantiate(subAsset); + AssetDatabase.CreateAsset(clone, destinationPath); + + var assetImporter = AssetImporter.GetAtPath(assetPath); + assetImporter.AddRemap(new AssetImporter.SourceAssetIdentifier(subAsset), clone); + + if (isForceUpdate) + { + AssetDatabase.WriteImportSettingsIfDirty(assetPath); + AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate); + } + } + + public static void ExtractAssets(this ScriptedImporter importer, string dirName, string extension) where T : UnityEngine.Object + { + if (string.IsNullOrEmpty(importer.assetPath)) + return; + + var subAssets = importer.GetSubAssets(importer.assetPath); + + var path = string.Format("{0}/{1}.{2}", + Path.GetDirectoryName(importer.assetPath), + Path.GetFileNameWithoutExtension(importer.assetPath), + dirName + ); + + var info = importer.SafeCreateDirectory(path); + + foreach (var asset in subAssets) + { + ExtractFromAsset(asset, string.Format("{0}/{1}{2}", path, asset.name, extension), false); + } + } + + + public static void ExtractTextures(this ScriptedImporter importer, string dirName, Func CreateModel, Action onComplited = null) + { + if (string.IsNullOrEmpty(importer.assetPath)) + return; + + var subAssets = importer.GetSubAssets(importer.assetPath); + + var path = string.Format("{0}/{1}.{2}", + Path.GetDirectoryName(importer.assetPath), + Path.GetFileNameWithoutExtension(importer.assetPath), + dirName + ); + + importer.SafeCreateDirectory(path); + + Dictionary targetPaths = new Dictionary(); + + // Reload Model + var model = CreateModel(importer.assetPath); + var mimeTypeReg = new System.Text.RegularExpressions.Regex("image/(?.*)$"); + int count = 0; + foreach (var texture in model.Textures) + { + var imageTexture = texture as VrmLib.ImageTexture; + if (imageTexture == null) continue; + + var mimeType = mimeTypeReg.Match(imageTexture.Image.MimeType); + var assetName = !string.IsNullOrEmpty(imageTexture.Name) ? imageTexture.Name : string.Format("{0}_img{1}", model.Root.Name, count); + var targetPath = string.Format("{0}/{1}.{2}", + path, + assetName, + mimeType.Groups["mime"].Value); + imageTexture.Name = assetName; + + if (imageTexture.TextureType == VrmLib.Texture.TextureTypes.MetallicRoughness + || imageTexture.TextureType == VrmLib.Texture.TextureTypes.Occlusion) + { + var subAssetTexture = subAssets.Where(x => x.name == imageTexture.Name).FirstOrDefault(); + File.WriteAllBytes(targetPath, subAssetTexture.EncodeToPNG()); + } + else + { + File.WriteAllBytes(targetPath, imageTexture.Image.Bytes.ToArray()); + } + + AssetDatabase.ImportAsset(targetPath); + targetPaths.Add(imageTexture, targetPath); + + count++; + } + + EditorApplication.delayCall += () => + { + foreach (var targetPath in targetPaths) + { + var imageTexture = targetPath.Key; + var targetTextureImporter = AssetImporter.GetAtPath(targetPath.Value) as TextureImporter; + targetTextureImporter.sRGBTexture = (imageTexture.ColorSpace == VrmLib.Texture.ColorSpaceTypes.Srgb); + if (imageTexture.TextureType == VrmLib.Texture.TextureTypes.NormalMap) + { + targetTextureImporter.textureType = TextureImporterType.NormalMap; + } + targetTextureImporter.SaveAndReimport(); + + var externalObject = AssetDatabase.LoadAssetAtPath(targetPath.Value, typeof(UnityEngine.Texture2D)); + importer.AddRemap(new AssetImporter.SourceAssetIdentifier(typeof(UnityEngine.Texture2D), imageTexture.Name), externalObject); + } + + //AssetDatabase.WriteImportSettingsIfDirty(assetPath); + AssetDatabase.ImportAsset(importer.assetPath, ImportAssetOptions.ForceUpdate); + + if (onComplited != null) + { + onComplited(); + } + }; + } + + public static DirectoryInfo SafeCreateDirectory(this ScriptedImporter importer, string path) + { + if (Directory.Exists(path)) + { + return null; + } + return Directory.CreateDirectory(path); + } + } +} \ No newline at end of file diff --git a/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/ScriptedImporterExtension.cs.meta b/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/ScriptedImporterExtension.cs.meta new file mode 100644 index 000000000..8cfda53c8 --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/ScriptedImporterExtension.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eb950c51864585a4688048266e6c3a00 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/VrmScriptedImporter.cs b/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/VrmScriptedImporter.cs new file mode 100644 index 000000000..928ff55d1 --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/VrmScriptedImporter.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.Serialization.Json; +using System.IO; +using System.Linq; +using UnityEngine; +using UnityEngine.Assertions; +using UnityEditor.Experimental.AssetImporters; +using UnityEditor; +using VrmLib; + +namespace UniVRM10 +{ + + [ScriptedImporter(1, "vrm")] + public class VrmScriptedImporter : ScriptedImporter, IExternalUnityObject + { + const string TextureDirName = "Textures"; + const string MaterialDirName = "Materials"; + const string MetaDirName = "MetaObjects"; + const string ExpressionDirName = "Expressions"; + + public override void OnImportAsset(AssetImportContext ctx) + { + Debug.Log("OnImportAsset to " + ctx.assetPath); + + try + { + // Create Vrm Model + VrmLib.Model model = VrmLoader.CreateVrmModel(ctx.assetPath); + if (model == null) + { + // maybe VRM-0.X + return; + } + Debug.Log($"VrmLoader.CreateVrmModel: {model}"); + + // Build Unity Model + var assets = EditorUnityBuilder.ToUnityAsset(model, assetPath, this); + ComponentBuilder.Build10(model, assets); + + // Texture + var externalTextures = this.GetExternalUnityObjects(); + foreach (var texture in assets.Textures) + { + if (texture == null) + continue; + + if (externalTextures.ContainsValue(texture)) + { + } + else + { + ctx.AddObjectToAsset(texture.name, texture); + } + } + + // Material + var externalMaterials = this.GetExternalUnityObjects(); + foreach (var material in assets.Materials) + { + if (material == null) + continue; + + if (externalMaterials.ContainsValue(material)) + { + + } + else + { + ctx.AddObjectToAsset(material.name, material); + } + } + + // Mesh + foreach (var mesh in assets.Meshes) + { + ctx.AddObjectToAsset(mesh.name, mesh); + } + + //// ScriptableObject + // avatar + ctx.AddObjectToAsset("avatar", assets.HumanoidAvatar); + + // meta + { + var external = this.GetExternalUnityObjects().FirstOrDefault(); + if (external.Value != null) + { + var controller = assets.Root.GetComponent(); + if (controller != null) + { + controller.Meta = external.Value; + } + } + else + { + var meta = assets.ScriptableObjects + .FirstOrDefault(x => x.GetType() == typeof(UniVRM10.VRM10MetaObject)) as UniVRM10.VRM10MetaObject; + if (meta != null) + { + meta.name = "meta"; + ctx.AddObjectToAsset(meta.name, meta); + } + } + } + + // expression + { + var external = this.GetExternalUnityObjects(); + if (external.Any()) + { + } + else + { + var expression = assets.ScriptableObjects + .Where(x => x.GetType() == typeof(UniVRM10.VRM10Expression)) + .Select(x => x as UniVRM10.VRM10Expression); + foreach (var clip in expression) + { + clip.name = clip.ExpressionName; + ctx.AddObjectToAsset(clip.ExpressionName, clip); + } + } + } + { + var external = this.GetExternalUnityObjects().FirstOrDefault(); + if (external.Value != null) + { + var controller = assets.Root.GetComponent(); + if (controller != null) + { + controller.Expression.ExpressionAvatar = external.Value; + } + } + else + { + var expressionAvatar = assets.ScriptableObjects + .FirstOrDefault(x => x.GetType() == typeof(UniVRM10.VRM10ExpressionAvatar)) as UniVRM10.VRM10ExpressionAvatar; + if (expressionAvatar != null) + { + expressionAvatar.name = "expressionAvatar"; + ctx.AddObjectToAsset(expressionAvatar.name, expressionAvatar); + } + } + } + + // Root + ctx.AddObjectToAsset(assets.Root.name, assets.Root); + ctx.SetMainObject(assets.Root); + + } + catch (System.Exception ex) + { + Debug.LogError(ex); + } + } + + public void ExtractTextures() + { + this.ExtractTextures(TextureDirName, (path) => { return VrmLoader.CreateVrmModel(path); }); + AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate); + } + + public void ExtractMaterials() + { + this.ExtractAssets(MaterialDirName, ".mat"); + AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate); + } + + public void ExtractMaterialsAndTextures() + { + this.ExtractTextures(TextureDirName, (path) => { return VrmLoader.CreateVrmModel(path); }, () => { this.ExtractAssets(MaterialDirName, ".mat"); }); + AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate); + } + + public void ExtractMeta() + { + this.ExtractAssets(MetaDirName, ".asset"); + var metaObject = this.GetExternalUnityObjects().FirstOrDefault(); + var metaObjectPath = AssetDatabase.GetAssetPath(metaObject.Value); + if (!string.IsNullOrEmpty(metaObjectPath)) + { + EditorUtility.SetDirty(metaObject.Value); + AssetDatabase.WriteImportSettingsIfDirty(metaObjectPath); + } + AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate); + } + + public void ExtractExpressions() + { + this.ExtractAssets(ExpressionDirName, ".asset"); + this.ExtractAssets(ExpressionDirName, ".asset"); + + var expressionAvatar = this.GetExternalUnityObjects().FirstOrDefault(); + var expressions = this.GetExternalUnityObjects(); + + expressionAvatar.Value.Clips = expressions.Select(x => x.Value).ToList(); + var avatarPath = AssetDatabase.GetAssetPath(expressionAvatar.Value); + if (!string.IsNullOrEmpty(avatarPath)) + { + EditorUtility.SetDirty(expressionAvatar.Value); + AssetDatabase.WriteImportSettingsIfDirty(avatarPath); + } + + AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate); + } + + public Dictionary GetExternalUnityObjects() where T : UnityEngine.Object + { + return this.GetExternalObjectMap().Where(x => x.Key.type == typeof(T)).ToDictionary(x => x.Key.name, x => (T)x.Value); + } + + public void SetExternalUnityObject(UnityEditor.AssetImporter.SourceAssetIdentifier sourceAssetIdentifier, T obj) where T : UnityEngine.Object + { + this.AddRemap(sourceAssetIdentifier, obj); + AssetDatabase.WriteImportSettingsIfDirty(this.assetPath); + AssetDatabase.ImportAsset(this.assetPath, ImportAssetOptions.ForceUpdate); + } + } +} \ No newline at end of file diff --git a/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/VrmScriptedImporter.cs.meta b/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/VrmScriptedImporter.cs.meta new file mode 100644 index 000000000..3d076041d --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/VrmScriptedImporter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f66ead3390398f443aa127b741826ad9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/VrmScriptedImporterEditorGUI.cs b/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/VrmScriptedImporterEditorGUI.cs new file mode 100644 index 000000000..d51eef909 --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/VrmScriptedImporterEditorGUI.cs @@ -0,0 +1,100 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEditor.Experimental.AssetImporters; +using UnityEngine; + +namespace UniVRM10 +{ + [CustomEditor(typeof(VrmScriptedImporter))] + public class VrmScriptedImporterEditorGUI : ScriptedImporterEditor + { + + private bool _isOpen = true; + + public override void OnInspectorGUI() + { + var importer = target as VrmScriptedImporter; + + EditorGUILayout.LabelField("Extract settings"); + + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.PrefixLabel("Materials And Textures"); + GUI.enabled = !(importer.GetExternalUnityObjects().Any() + && importer.GetExternalUnityObjects().Any()); + if (GUILayout.Button("Extract")) + { + importer.ExtractMaterialsAndTextures(); + } + GUI.enabled = !GUI.enabled; + if (GUILayout.Button("Clear")) + { + importer.ClearExternalObjects(); + importer.ClearExternalObjects(); + } + GUI.enabled = true; + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.PrefixLabel("Meta"); + GUI.enabled = !importer.GetExternalUnityObjects().Any(); + if (GUILayout.Button("Extract")) + { + importer.ExtractMeta(); + } + GUI.enabled = !GUI.enabled; + if (GUILayout.Button("Clear")) + { + importer.ClearExternalObjects(); + } + GUI.enabled = true; + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.PrefixLabel("Expressions"); + GUI.enabled = !(importer.GetExternalUnityObjects().Any() + && importer.GetExternalUnityObjects().Any()); + if (GUILayout.Button("Extract")) + { + importer.ExtractExpressions(); + } + GUI.enabled = !GUI.enabled; + if (GUILayout.Button("Clear")) + { + importer.ClearExternalObjects(); + importer.ClearExternalObjects(); + } + GUI.enabled = true; + EditorGUILayout.EndHorizontal(); + + // ObjectMap + DrawRemapGUI("Material Remap", importer); + DrawRemapGUI("Texture Remap", importer); + DrawRemapGUI("Meta Remap", importer); + DrawRemapGUI("ExpressionAvatar Remap", importer); + DrawRemapGUI("Expression Remap", importer); + + base.OnInspectorGUI(); + } + + private void DrawRemapGUI(string title, VrmScriptedImporter importer) where T : UnityEngine.Object + { + EditorGUILayout.Foldout(_isOpen, title); + EditorGUI.indentLevel++; + var objects = importer.GetExternalObjectMap().Where(x => x.Key.type == typeof(T)); + foreach (var obj in objects) + { + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.PrefixLabel(obj.Key.name); + var asset = EditorGUILayout.ObjectField(obj.Value, obj.Key.type, true) as T; + if (asset != obj.Value) + { + importer.SetExternalUnityObject(obj.Key, asset); + } + EditorGUILayout.EndHorizontal(); + } + EditorGUI.indentLevel--; + } + } +} diff --git a/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/VrmScriptedImporterEditorGUI.cs.meta b/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/VrmScriptedImporterEditorGUI.cs.meta new file mode 100644 index 000000000..9908c2e22 --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/Editor/ScriptedImporter/VrmScriptedImporterEditorGUI.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7cfa5b7712433dc498caffa961acb7ee +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/UnityBuilder/EditorUnityBuilder.cs b/Assets/VRM10/Runtime/UnityBuilder/EditorUnityBuilder.cs new file mode 100644 index 000000000..948851801 --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/EditorUnityBuilder.cs @@ -0,0 +1,139 @@ +using System; +using UnityEngine; +using System.Linq; +using MeshUtility; +using VrmLib; + +namespace UniVRM10 +{ + public static class EditorUnityBuilder + { + static public ModelAsset ToUnityAsset(VrmLib.Model model, string assetPath, IExternalUnityObject scriptedImporter) + { + var modelAsset = new ModelAsset(); + CreateTextureAsset(model, modelAsset, scriptedImporter); + CreateMaterialAsset(model, modelAsset, scriptedImporter); + CreateMeshAsset(model, modelAsset); + + // node + RuntimeUnityBuilder.CreateNodes(model.Root, null, modelAsset.Map.Nodes); + modelAsset.Root = modelAsset.Map.Nodes[model.Root]; + + // renderer + var map = modelAsset.Map; + foreach (var (node, go) in map.Nodes) + { + if (node.MeshGroup is null) + { + continue; + } + + if (node.MeshGroup.Meshes.Count > 1) + { + throw new NotImplementedException("invalid isolated vertexbuffer"); + } + + var renderer = RuntimeUnityBuilder.CreateRenderer(node, go, map); + map.Renderers.Add(node, renderer); + modelAsset.Renderers.Add(renderer); + } + + if (model.Vrm != null) + { + // humanoid + var humanoid = modelAsset.Root.AddComponent(); + humanoid.AssignBones(modelAsset.Map.Nodes.Select(x => (x.Key.HumanoidBone.GetValueOrDefault().ToUnity(), x.Value.transform))); + modelAsset.HumanoidAvatar = humanoid.CreateAvatar(); + modelAsset.HumanoidAvatar.name = "VRM"; + + var animator = modelAsset.Root.AddComponent(); + animator.avatar = modelAsset.HumanoidAvatar; + } + + return modelAsset; + } + + static private void CreateTextureAsset(VrmLib.Model model, ModelAsset modelAsset, IExternalUnityObject scriptedImporter) + { + var externalObjects = scriptedImporter.GetExternalUnityObjects(); + + // textures + for (int i = 0; i < model.Textures.Count; ++i) + { + if (model.Textures[i] is VrmLib.ImageTexture imageTexture) + { + if (string.IsNullOrEmpty(model.Textures[i].Name)) + { + model.Textures[i].Name = string.Format("{0}_img{1}", model.Root.Name, i); + } + if (externalObjects.ContainsKey(model.Textures[i].Name)) + { + modelAsset.Map.Textures.Add(imageTexture, externalObjects[model.Textures[i].Name]); + modelAsset.Textures.Add(externalObjects[model.Textures[i].Name]); + } + else + { + var name = !string.IsNullOrEmpty(imageTexture.Name) + ? imageTexture.Name + : string.Format("{0}_img{1}", model.Root.Name, i); + + var texture = RuntimeUnityBuilder.CreateTexture(imageTexture); + texture.name = name; + + modelAsset.Map.Textures.Add(imageTexture, texture); + modelAsset.Textures.Add(texture); + } + } + else + { + Debug.LogWarning($"{i} not ImageTexture"); + } + } + } + + + static private void CreateMaterialAsset(VrmLib.Model model, ModelAsset modelAsset, IExternalUnityObject scriptedImporter) + { + var externalObjects = scriptedImporter.GetExternalUnityObjects(); + + foreach (var src in model.Materials) + { + if (externalObjects.ContainsKey(src.Name)) + { + modelAsset.Map.Materials.Add(src, externalObjects[src.Name]); + modelAsset.Materials.Add(externalObjects[src.Name]); + } + else + { + // TODO: material has VertexColor + var material = RuntimeUnityMaterialBuilder.CreateMaterialAsset(src, hasVertexColor: false, modelAsset.Map.Textures); + material.name = src.Name; + modelAsset.Map.Materials.Add(src, material); + modelAsset.Materials.Add(material); + } + } + } + + static private void CreateMeshAsset(VrmLib.Model model, ModelAsset modelAsset) + { + for (int i = 0; i < model.MeshGroups.Count; ++i) + { + var src = model.MeshGroups[i]; + if (src.Meshes.Count == 1) + { + // submesh 方式 + var mesh = new UnityEngine.Mesh(); + mesh.name = src.Name; + mesh.LoadMesh(src.Meshes[0], src.Skin); + modelAsset.Map.Meshes.Add(src, mesh); + modelAsset.Meshes.Add(mesh); + } + else + { + // 頂点バッファの連結が必用 + throw new NotImplementedException(); + } + } + } + } +} diff --git a/Assets/VRM10/Runtime/UnityBuilder/EditorUnityBuilder.cs.meta b/Assets/VRM10/Runtime/UnityBuilder/EditorUnityBuilder.cs.meta new file mode 100644 index 000000000..643e6fb40 --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/EditorUnityBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6b3f83007d3c2144787097da4e3b92af +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/UnityBuilder/MToonLoader.cs b/Assets/VRM10/Runtime/UnityBuilder/MToonLoader.cs new file mode 100644 index 000000000..9212dca73 --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/MToonLoader.cs @@ -0,0 +1,170 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + + +namespace UniVRM10 +{ + public static class MToonLoader + { + public delegate Texture2D GetTextureFunc(VrmLib.TextureInfo texture); + + public static MToon.MToonDefinition ToUnity(this VrmLib.MToon.MToonDefinition src, Dictionary textures) + { + if (src == null) return null; + return new MToon.MToonDefinition + { + Color = src.Color.ToUnity(textures), + Emission = src.Emission.ToUnity(textures), + Lighting = src.Lighting.ToUnity(textures), + MatCap = src.MatCap.ToUnity(textures), + Meta = src.Meta.ToUnity(), + Outline = src.Outline.ToUnity(textures), + Rendering = src.Rendering.ToUnity(), + Rim = src.Rim.ToUnity(textures), + TextureOption = src.TextureOption.ToUnity(textures), + }; + } + + static Vector2 ToUnity(this System.Numerics.Vector2 src) + { + return new Vector2(src.X, src.Y); + } + + static MToon.ColorDefinition ToUnity(this VrmLib.MToon.ColorDefinition src, Dictionary textures) + { + if (src == null) return null; + return new MToon.ColorDefinition + { + CutoutThresholdValue = src.CutoutThresholdValue, + LitColor = src.LitColor.ToUnitySRGB(), + LitMultiplyTexture = textures.GetOrDefault(src.LitMultiplyTexture?.Texture), + ShadeColor = src.ShadeColor.ToUnitySRGB(), + ShadeMultiplyTexture = textures.GetOrDefault(src.ShadeMultiplyTexture?.Texture), + }; + } + + static MToon.EmissionDefinition ToUnity(this VrmLib.MToon.EmissionDefinition src, Dictionary textures) + { + if (src == null) return null; + return new MToon.EmissionDefinition + { + EmissionColor = src.EmissionColor.ToUnityLinear(), + EmissionMultiplyTexture = textures.GetOrDefault(src.EmissionMultiplyTexture?.Texture), + }; + } + + static MToon.LightingDefinition ToUnity(this VrmLib.MToon.LightingDefinition src, Dictionary textures) + { + if (src == null) return null; + return new MToon.LightingDefinition + { + LightingInfluence = src.LightingInfluence.ToUnity(), + LitAndShadeMixing = src.LitAndShadeMixing.ToUnity(textures), + Normal = src.Normal.ToUnity(textures), + }; + } + + static MToon.LightingInfluenceDefinition ToUnity(this VrmLib.MToon.LightingInfluenceDefinition src) + { + if (src == null) return null; + return new MToon.LightingInfluenceDefinition + { + GiIntensityValue = src.GiIntensityValue, + LightColorAttenuationValue = src.LightColorAttenuationValue, + }; + } + + static MToon.LitAndShadeMixingDefinition ToUnity(this VrmLib.MToon.LitAndShadeMixingDefinition src, Dictionary textures) + { + if (src == null) return null; + return new MToon.LitAndShadeMixingDefinition + { + ShadingShiftValue = src.ShadingShiftValue, + ShadingToonyValue = src.ShadingToonyValue, + }; + } + + static MToon.NormalDefinition ToUnity(this VrmLib.MToon.NormalDefinition src, Dictionary textures) + { + if (src == null) return null; + return new MToon.NormalDefinition + { + NormalScaleValue = src.NormalScaleValue, + NormalTexture = textures.GetOrDefault(src.NormalTexture?.Texture), + }; + } + + static MToon.MatCapDefinition ToUnity(this VrmLib.MToon.MatCapDefinition src, Dictionary textures) + { + if (src == null) return null; + return new MToon.MatCapDefinition + { + AdditiveTexture = textures.GetOrDefault(src.AdditiveTexture?.Texture), + }; + } + + static MToon.MetaDefinition ToUnity(this VrmLib.MToon.MetaDefinition src) + { + if (src == null) return null; + return new MToon.MetaDefinition + { + Implementation = src.Implementation, + VersionNumber = src.VersionNumber, + }; + } + + static MToon.OutlineDefinition ToUnity(this VrmLib.MToon.OutlineDefinition src, Dictionary textures) + { + if (src == null) return null; + return new MToon.OutlineDefinition + { + OutlineColor = src.OutlineColor.ToUnitySRGB(), + OutlineColorMode = (MToon.OutlineColorMode)src.OutlineColorMode, + OutlineLightingMixValue = src.OutlineLightingMixValue, + OutlineScaledMaxDistanceValue = src.OutlineScaledMaxDistanceValue, + OutlineWidthMode = (MToon.OutlineWidthMode)src.OutlineWidthMode, + OutlineWidthMultiplyTexture = textures.GetOrDefault(src.OutlineWidthMultiplyTexture?.Texture), + OutlineWidthValue = src.OutlineWidthValue, + }; + } + + static MToon.RenderingDefinition ToUnity(this VrmLib.MToon.RenderingDefinition src) + { + if (src == null) return null; + return new MToon.RenderingDefinition + { + CullMode = (MToon.CullMode)src.CullMode, + RenderMode = (MToon.RenderMode)src.RenderMode, + RenderQueueOffsetNumber = src.RenderQueueOffsetNumber, + }; + } + + static MToon.RimDefinition ToUnity(this VrmLib.MToon.RimDefinition src, Dictionary textures) + { + if (src == null) return null; + return new MToon.RimDefinition + { + RimColor = src.RimColor.ToUnityLinear(), + RimFresnelPowerValue = src.RimFresnelPowerValue, + RimLiftValue = src.RimLiftValue, + RimLightingMixValue = src.RimLightingMixValue, + RimMultiplyTexture = textures.GetOrDefault(src.RimMultiplyTexture?.Texture), + }; + } + + static MToon.TextureUvCoordsDefinition ToUnity(this VrmLib.MToon.TextureUvCoordsDefinition src, Dictionary textures) + { + if (src == null) return null; + return new MToon.TextureUvCoordsDefinition + { + MainTextureLeftBottomOriginOffset = src.MainTextureLeftBottomOriginOffset.ToUnity(), + MainTextureLeftBottomOriginScale = src.MainTextureLeftBottomOriginScale.ToUnity(), + UvAnimationMaskTexture = textures.GetOrDefault(src.UvAnimationMaskTexture?.Texture), + UvAnimationRotationSpeedValue = src.UvAnimationRotationSpeedValue, + UvAnimationScrollXSpeedValue = src.UvAnimationScrollXSpeedValue, + UvAnimationScrollYSpeedValue = src.UvAnimationScrollYSpeedValue, + }; + } + } +} diff --git a/Assets/VRM10/Runtime/UnityBuilder/MToonLoader.cs.meta b/Assets/VRM10/Runtime/UnityBuilder/MToonLoader.cs.meta new file mode 100644 index 000000000..2a767777d --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/MToonLoader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8fb046f799feabd48bcdf7bcaf0f0a86 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/UnityBuilder/MeshLoader.cs b/Assets/VRM10/Runtime/UnityBuilder/MeshLoader.cs new file mode 100644 index 000000000..1dd0c844d --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/MeshLoader.cs @@ -0,0 +1,87 @@ +using System; +using UnityEngine; +using UnityEngine.Rendering; + +namespace UniVRM10 +{ + public static class MeshLoader + { + public static void LoadMesh(this Mesh mesh, VrmLib.Mesh src, VrmLib.Skin skin = null) + { + mesh.vertices = src.VertexBuffer.Positions.GetSpan().ToArray(); + mesh.normals = src.VertexBuffer.Normals?.GetSpan().ToArray(); + mesh.uv = src.VertexBuffer.TexCoords?.GetSpan().ToArray(); + mesh.colors = src.VertexBuffer.Colors?.GetSpan().ToArray(); + if (src.VertexBuffer.Weights != null && src.VertexBuffer.Joints != null) + { + var boneWeights = new BoneWeight[mesh.vertexCount]; + if (src.VertexBuffer.Weights.Count != mesh.vertexCount || src.VertexBuffer.Joints.Count != mesh.vertexCount) + { + throw new ArgumentException(); + } + var weights = src.VertexBuffer.Weights.GetSpan(); + var joints = src.VertexBuffer.Joints.GetSpan(); + if (skin != null) + { + mesh.bindposes = skin.InverseMatrices.GetSpan().ToArray(); + } + + for (int i = 0; i < weights.Length; ++i) + { + var w = weights[i]; + boneWeights[i].weight0 = w.x; + boneWeights[i].weight1 = w.y; + boneWeights[i].weight2 = w.z; + boneWeights[i].weight3 = w.w; + } + for (int i = 0; i < joints.Length; ++i) + { + var j = joints[i]; + boneWeights[i].boneIndex0 = j.Joint0; + boneWeights[i].boneIndex1 = j.Joint1; + boneWeights[i].boneIndex2 = j.Joint2; + boneWeights[i].boneIndex3 = j.Joint3; + } + mesh.boneWeights = boneWeights; + } + + mesh.subMeshCount = src.Submeshes.Count; + +#if UNITY_2019 + var triangles = src.IndexBuffer.GetAsIntArray(); + mesh.triangles = triangles + var flags = MeshUpdateFlags.DontRecalculateBounds | MeshUpdateFlags.DontResetBoneBounds; + for (int i = 0; i < src.Submeshes.Count; ++i) + { + var submesh = src.Submeshes[i]; + mesh.SetSubMesh(i, new SubMeshDescriptor + { + indexStart = submesh.Offset, + indexCount = submesh.DrawCount, + }, + flags); + } +#else + var triangles = src.IndexBuffer.GetAsIntList(); + for (int i = 0; i < src.Submeshes.Count; ++i) + { + var submesh = src.Submeshes[i]; + mesh.SetTriangles(triangles.GetRange(submesh.Offset, submesh.DrawCount), i); + } +#endif + + foreach (var morphTarget in src.MorphTargets) + { + var positions = + morphTarget.VertexBuffer.Positions != null + ? morphTarget.VertexBuffer.Positions.GetSpan().ToArray() + : new Vector3[mesh.vertexCount] // dummy + ; + mesh.AddBlendShapeFrame(morphTarget.Name, 100.0f, positions, null, null); + + mesh.RecalculateBounds(); + mesh.RecalculateTangents(); + } + } + } +} diff --git a/Assets/VRM10/Runtime/UnityBuilder/MeshLoader.cs.meta b/Assets/VRM10/Runtime/UnityBuilder/MeshLoader.cs.meta new file mode 100644 index 000000000..808a8fdc7 --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/MeshLoader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d80d88b180871234ea03afcc24e118da +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/UnityBuilder/ModelAsset.cs b/Assets/VRM10/Runtime/UnityBuilder/ModelAsset.cs new file mode 100644 index 000000000..57026b4db --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/ModelAsset.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using UnityEngine; +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace UniVRM10 +{ + + [Serializable] + public class ModelAsset : IDisposable + { + public GameObject Root; + public Avatar HumanoidAvatar; + public List Textures = new List(); + public List Materials = new List(); + public List Meshes = new List(); + public List Renderers = new List(); + + public readonly ModelMap Map = new ModelMap(); + public List ScriptableObjects = new List(); + + private Animator _animator; + public Animator Animator + { + get + { + if (_animator == null) + { + _animator = Root.GetComponent(); + } + return _animator; + } + } + + public void Dispose() + { + GameObject.Destroy(Root); + UnityEngine.Object.Destroy(HumanoidAvatar); + foreach (var v in Textures) + { + UnityEngine.Object.DestroyImmediate(v); + } + foreach (var v in Materials) + { + UnityEngine.Object.DestroyImmediate(v); + } + foreach (var v in Meshes) + { + UnityEngine.Object.DestroyImmediate(v); + } + foreach (var v in ScriptableObjects) + { + ScriptableObject.DestroyImmediate(v); + } + } + +#if UNITY_EDITOR + public void DisposeEditor() + { + if (!Application.isPlaying) + { + if (string.IsNullOrEmpty(AssetDatabase.GetAssetPath(Root))) + { + GameObject.DestroyImmediate(Root); + } + if (string.IsNullOrEmpty(AssetDatabase.GetAssetPath(HumanoidAvatar))) + { + UnityEngine.Object.DestroyImmediate(HumanoidAvatar); + } + + foreach (var v in Textures) + { + if (string.IsNullOrEmpty(AssetDatabase.GetAssetPath(v))) + { + UnityEngine.Object.DestroyImmediate(v); + } + } + foreach (var v in Materials) + { + if (string.IsNullOrEmpty(AssetDatabase.GetAssetPath(v))) + { + UnityEngine.Object.DestroyImmediate(v); + } + } + foreach (var v in Meshes) + { + if (string.IsNullOrEmpty(AssetDatabase.GetAssetPath(v))) + { + UnityEngine.Object.DestroyImmediate(v); + } + } + foreach (var v in ScriptableObjects) + { + if (string.IsNullOrEmpty(AssetDatabase.GetAssetPath(v))) + { + ScriptableObject.DestroyImmediate(v); + } + } + } + } +#endif + } +} diff --git a/Assets/VRM10/Runtime/UnityBuilder/ModelAsset.cs.meta b/Assets/VRM10/Runtime/UnityBuilder/ModelAsset.cs.meta new file mode 100644 index 000000000..84b3cb403 --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/ModelAsset.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 578298355dd7ed4409b08bb8e5cc4316 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/UnityBuilder/ModelMap.cs b/Assets/VRM10/Runtime/UnityBuilder/ModelMap.cs new file mode 100644 index 000000000..2eae00f45 --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/ModelMap.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace UniVRM10 +{ + public class ModelMap + { + public readonly Dictionary Nodes = new Dictionary(); + public readonly Dictionary Textures = new Dictionary(); + public readonly Dictionary Materials = new Dictionary(); + public readonly Dictionary Meshes = new Dictionary(); + public readonly Dictionary Renderers = new Dictionary(); + } +} diff --git a/Assets/VRM10/Runtime/UnityBuilder/ModelMap.cs.meta b/Assets/VRM10/Runtime/UnityBuilder/ModelMap.cs.meta new file mode 100644 index 000000000..d9a7c497a --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/ModelMap.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1a93e36cc818c4f4e87cd0c1daaf16a8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/UnityBuilder/RuntimeUnityBuilder.cs b/Assets/VRM10/Runtime/UnityBuilder/RuntimeUnityBuilder.cs new file mode 100644 index 000000000..163f56d17 --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/RuntimeUnityBuilder.cs @@ -0,0 +1,235 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using MeshUtility; +using UnityEngine; +using VrmLib; + +namespace UniVRM10 +{ + /// + /// VrmLib.Model から UnityPrefab を構築する + /// + public static class RuntimeUnityBuilder + { + /// + /// モデル(Transform + Renderer)を構築する。 + /// + public static ModelAsset ToUnityAsset(VrmLib.Model model, bool showMesh = true) + { + var modelAsset = new ModelAsset(); + + // texture + for (int i = 0; i < model.Textures.Count; ++i) + { + var src = model.Textures[i]; + var name = !string.IsNullOrEmpty(src.Name) + ? src.Name + : string.Format("{0}_img{1}", model.Root.Name, i); + if (src is VrmLib.ImageTexture imageTexture) + { + var texture = CreateTexture(imageTexture); + texture.name = name; + modelAsset.Map.Textures.Add(src, texture); + modelAsset.Textures.Add(texture); + } + else + { + Debug.LogWarning($"{name} not ImageTexture"); + } + } + + // material + foreach (var src in model.Materials) + { + // TODO: material has VertexColor + var material = RuntimeUnityMaterialBuilder.CreateMaterialAsset(src, hasVertexColor: false, modelAsset.Map.Textures); + material.name = src.Name; + modelAsset.Map.Materials.Add(src, material); + modelAsset.Materials.Add(material); + } + + // mesh + for (int i = 0; i < model.MeshGroups.Count; ++i) + { + var src = model.MeshGroups[i]; + if (src.Meshes.Count == 1) + { + // submesh 方式 + var mesh = new UnityEngine.Mesh(); + mesh.name = src.Name; + mesh.LoadMesh(src.Meshes[0], src.Skin); + modelAsset.Map.Meshes.Add(src, mesh); + modelAsset.Meshes.Add(mesh); + } + else + { + // 頂点バッファの連結が必用 + throw new NotImplementedException(); + } + } + + // node: recursive + CreateNodes(model.Root, null, modelAsset.Map.Nodes); + modelAsset.Root = modelAsset.Map.Nodes[model.Root]; + + // renderer + var map = modelAsset.Map; + foreach (var (node, go) in map.Nodes) + { + if (node.MeshGroup is null) + { + continue; + } + + if (node.MeshGroup.Meshes.Count > 1) + { + throw new NotImplementedException("invalid isolated vertexbuffer"); + } + + var renderer = CreateRenderer(node, go, map); + if (!showMesh) + { + renderer.enabled = false; + } + map.Renderers.Add(node, renderer); + modelAsset.Renderers.Add(renderer); + } + + var humanoid = modelAsset.Root.AddComponent(); + humanoid.AssignBones(map.Nodes.Select(x => (x.Key.HumanoidBone.GetValueOrDefault().ToUnity(), x.Value.transform))); + modelAsset.HumanoidAvatar = humanoid.CreateAvatar(); + modelAsset.HumanoidAvatar.name = "VRM"; + + var animator = modelAsset.Root.AddComponent(); + animator.avatar = modelAsset.HumanoidAvatar; + + return modelAsset; + } + + public static HumanBodyBones ToUnity(this VrmLib.HumanoidBones bone) + { + if (bone == VrmLib.HumanoidBones.unknown) + { + return HumanBodyBones.LastBone; + } + return VrmLib.EnumUtil.Cast(bone); + } + + private static RenderTextureReadWrite GetRenderTextureReadWrite(VrmLib.Texture.ColorSpaceTypes type) + { + return (type == VrmLib.Texture.ColorSpaceTypes.Linear) ? RenderTextureReadWrite.Linear : RenderTextureReadWrite.sRGB; + } + + /// + /// 画像のバイト列からテクスチャを作成する + /// + public static Texture2D CreateTexture(VrmLib.ImageTexture imageTexture) + { + Texture2D dstTexture = null; + UnityEngine.Material convertMaterial = null; + var texture = new Texture2D(2, 2, TextureFormat.ARGB32, false, imageTexture.ColorSpace == VrmLib.Texture.ColorSpaceTypes.Linear); + texture.LoadImage(imageTexture.Image.Bytes.ToArray()); + + // Convert Texture Gltf to Unity + if (imageTexture.TextureType == VrmLib.Texture.TextureTypes.NormalMap) + { + convertMaterial = TextureConvertMaterial.GetNormalMapConvertGltfToUnity(); + dstTexture = UnityTextureUtil.CopyTexture( + texture, + GetRenderTextureReadWrite(imageTexture.ColorSpace), + convertMaterial); + } + else if (imageTexture.TextureType == VrmLib.Texture.TextureTypes.MetallicRoughness) + { + var metallicRoughnessImage = imageTexture as VrmLib.MetallicRoughnessImageTexture; + convertMaterial = TextureConvertMaterial.GetMetallicRoughnessGltfToUnity(metallicRoughnessImage.RoughnessFactor); + dstTexture = UnityTextureUtil.CopyTexture( + texture, + GetRenderTextureReadWrite(imageTexture.ColorSpace), + convertMaterial); + } + else if (imageTexture.TextureType == VrmLib.Texture.TextureTypes.Occlusion) + { + convertMaterial = TextureConvertMaterial.GetOcclusionGltfToUnity(); + dstTexture = UnityTextureUtil.CopyTexture( + texture, + GetRenderTextureReadWrite(imageTexture.ColorSpace), + convertMaterial); + } + + if (dstTexture != null) + { + if (texture != null) + { + UnityEngine.Object.DestroyImmediate(texture); + } + texture = dstTexture; + } + + if (convertMaterial != null) + { + UnityEngine.Object.DestroyImmediate(convertMaterial); + } + + return texture; + } + + /// + /// ヒエラルキーを再帰的に構築する + /// + public static void CreateNodes(VrmLib.Node node, GameObject parent, Dictionary nodes) + { + GameObject go = new GameObject(node.Name); + go.transform.SetPositionAndRotation(node.Translation.ToUnityVector3(), node.Rotation.ToUnityQuaternion()); + nodes.Add(node, go); + if (parent != null) + { + go.transform.SetParent(parent.transform); + } + + if (node.Children.Count > 0) + { + for (int n = 0; n < node.Children.Count; n++) + { + CreateNodes(node.Children[n], go, nodes); + } + } + } + + /// + /// MeshFilter + MeshRenderer もしくは SkinnedMeshRenderer を構築する + /// + public static Renderer CreateRenderer(VrmLib.Node node, GameObject go, ModelMap map) + { + var mesh = node.MeshGroup.Meshes[0]; + + Renderer renderer = null; + var hasBlendShape = mesh.MorphTargets.Any(); + if (node.MeshGroup.Skin != null || hasBlendShape) + { + var skinnedMeshRenderer = go.AddComponent(); + renderer = skinnedMeshRenderer; + skinnedMeshRenderer.sharedMesh = map.Meshes[node.MeshGroup]; + if (node.MeshGroup.Skin != null) + { + skinnedMeshRenderer.bones = node.MeshGroup.Skin.Joints.Select(x => map.Nodes[x].transform).ToArray(); + if (node.MeshGroup.Skin.Root != null) + { + skinnedMeshRenderer.rootBone = map.Nodes[node.MeshGroup.Skin.Root].transform; + } + } + } + else + { + var meshFilter = go.AddComponent(); + renderer = go.AddComponent(); + meshFilter.sharedMesh = map.Meshes[node.MeshGroup]; + } + var materials = mesh.Submeshes.Select(x => map.Materials[x.Material]).ToArray(); + renderer.sharedMaterials = materials; + + return renderer; + } + } +} diff --git a/Assets/VRM10/Runtime/UnityBuilder/RuntimeUnityBuilder.cs.meta b/Assets/VRM10/Runtime/UnityBuilder/RuntimeUnityBuilder.cs.meta new file mode 100644 index 000000000..be6f19107 --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/RuntimeUnityBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 962f584a4519d62419f01b8151f99169 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/UnityBuilder/RuntimeUnityMaterialBuilder.cs b/Assets/VRM10/Runtime/UnityBuilder/RuntimeUnityMaterialBuilder.cs new file mode 100644 index 000000000..6bfacd2e7 --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/RuntimeUnityMaterialBuilder.cs @@ -0,0 +1,215 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UniVRM10 +{ + public static class RuntimeUnityMaterialBuilder + { + public static UnityEngine.Material CreateMaterialAsset(VrmLib.Material src, bool hasVertexColor, Dictionary textures) + { + if (src is VrmLib.MToonMaterial mtoonSrc) + { + // MTOON + var material = new Material(Shader.Find(MToon.Utils.ShaderName)); + MToon.Utils.SetMToonParametersToMaterial(material, mtoonSrc.Definition.ToUnity(textures)); + return material; + } + + if (src is VrmLib.UnlitMaterial unlitSrc) + { + return CreateUnlitMaterial(unlitSrc, hasVertexColor, textures); + } + + if (src is VrmLib.PBRMaterial pbrSrc) + { + return CreateStandardMaterial(pbrSrc, textures); + } + + throw new NotImplementedException($"unknown material: {src}"); + } + + static UnityEngine.Material CreateUnlitMaterial(VrmLib.UnlitMaterial src, bool hasVertexColor, Dictionary textures) + { + var material = new Material(Shader.Find(UniGLTF.UniUnlit.Utils.ShaderName)); + + // texture + if (src.BaseColorTexture != null) + { + material.mainTexture = textures[src.BaseColorTexture.Texture]; + } + + // color + material.color = src.BaseColorFactor.ToUnitySRGB(); + + //renderMode + switch (src.AlphaMode) + { + case VrmLib.AlphaModeType.OPAQUE: + UniGLTF.UniUnlit.Utils.SetRenderMode(material, UniGLTF.UniUnlit.UniUnlitRenderMode.Opaque); + break; + + case VrmLib.AlphaModeType.BLEND: + UniGLTF.UniUnlit.Utils.SetRenderMode(material, UniGLTF.UniUnlit.UniUnlitRenderMode.Transparent); + break; + + case VrmLib.AlphaModeType.MASK: + UniGLTF.UniUnlit.Utils.SetRenderMode(material, UniGLTF.UniUnlit.UniUnlitRenderMode.Cutout); + material.SetFloat(UniGLTF.UniUnlit.Utils.PropNameCutoff, src.AlphaCutoff); + break; + + default: + UniGLTF.UniUnlit.Utils.SetRenderMode(material, UniGLTF.UniUnlit.UniUnlitRenderMode.Opaque); + break; + } + + // culling + if (src.DoubleSided) + { + UniGLTF.UniUnlit.Utils.SetCullMode(material, UniGLTF.UniUnlit.UniUnlitCullMode.Off); + } + else + { + UniGLTF.UniUnlit.Utils.SetCullMode(material, UniGLTF.UniUnlit.UniUnlitCullMode.Back); + } + + // VColor + if (hasVertexColor) + { + UniGLTF.UniUnlit.Utils.SetVColBlendMode(material, UniGLTF.UniUnlit.UniUnlitVertexColorBlendOp.Multiply); + } + + UniGLTF.UniUnlit.Utils.ValidateProperties(material, true); + + return material; + } + + // https://forum.unity.com/threads/standard-material-shader-ignoring-setfloat-property-_mode.344557/#post-2229980 + internal enum BlendMode + { + Opaque, + Cutout, + Fade, // Old school alpha-blending mode, fresnel does not affect amount of transparency + Transparent // Physically plausible transparency mode, implemented as alpha pre-multiply + } + + static UnityEngine.Material CreateStandardMaterial(VrmLib.PBRMaterial x, Dictionary textures) + { + var material = new Material(Shader.Find("Standard")); + + material.color = x.BaseColorFactor.ToUnitySRGB(); + + if (x.BaseColorTexture != null) + { + material.mainTexture = textures[x.BaseColorTexture.Texture]; + } + + if (x.MetallicRoughnessTexture != null) + { + material.EnableKeyword("_METALLICGLOSSMAP"); + var texture = textures[x.MetallicRoughnessTexture]; + if (texture != null) + { + var prop = "_MetallicGlossMap"; + material.SetTexture(prop, texture); + } + + material.SetFloat("_Metallic", 1.0f); + // Set 1.0f as hard-coded. See: https://github.com/dwango/UniVRM/issues/212. + material.SetFloat("_GlossMapScale", 1.0f); + } + else + { + material.SetFloat("_Metallic", x.MetallicFactor); + material.SetFloat("_Glossiness", 1.0f - x.RoughnessFactor); + } + + if (x.NormalTexture != null) + { + material.EnableKeyword("_NORMALMAP"); + var texture = textures[x.NormalTexture]; + if (texture != null) + { + var prop = "_BumpMap"; + material.SetTexture(prop, texture); + material.SetFloat("_BumpScale", x.NormalTextureScale); + } + } + + if (x.OcclusionTexture != null) + { + var texture = textures[x.OcclusionTexture]; + if (texture != null) + { + var prop = "_OcclusionMap"; + material.SetTexture(prop, texture); + material.SetFloat("_OcclusionStrength", x.OcclusionTextureStrength); + } + } + + if (x.EmissiveFactor != System.Numerics.Vector3.Zero || x.EmissiveTexture != null) + { + material.EnableKeyword("_EMISSION"); + material.globalIlluminationFlags &= ~MaterialGlobalIlluminationFlags.EmissiveIsBlack; + + material.SetColor("_EmissionColor", x.EmissiveFactor.ToUnityColor()); + + if (x.EmissiveTexture != null) + { + var texture = textures[x.EmissiveTexture]; + if (texture != null) + { + material.SetTexture("_EmissionMap", texture); + } + } + } + + BlendMode blendMode = BlendMode.Opaque; + // https://forum.unity.com/threads/standard-material-shader-ignoring-setfloat-property-_mode.344557/#post-2229980 + switch (x.AlphaMode) + { + case VrmLib.AlphaModeType.BLEND: + blendMode = BlendMode.Fade; + material.SetOverrideTag("RenderType", "Transparent"); + material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha); + material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha); + material.SetInt("_ZWrite", 0); + material.DisableKeyword("_ALPHATEST_ON"); + material.EnableKeyword("_ALPHABLEND_ON"); + material.DisableKeyword("_ALPHAPREMULTIPLY_ON"); + material.renderQueue = 3000; + break; + + case VrmLib.AlphaModeType.MASK: + blendMode = BlendMode.Cutout; + material.SetOverrideTag("RenderType", "TransparentCutout"); + material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One); + material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero); + material.SetInt("_ZWrite", 1); + material.SetFloat("_Cutoff", x.AlphaCutoff); + material.EnableKeyword("_ALPHATEST_ON"); + material.DisableKeyword("_ALPHABLEND_ON"); + material.DisableKeyword("_ALPHAPREMULTIPLY_ON"); + material.renderQueue = 2450; + + break; + + default: // OPAQUE + blendMode = BlendMode.Opaque; + material.SetOverrideTag("RenderType", ""); + material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One); + material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero); + material.SetInt("_ZWrite", 1); + material.DisableKeyword("_ALPHATEST_ON"); + material.DisableKeyword("_ALPHABLEND_ON"); + material.DisableKeyword("_ALPHAPREMULTIPLY_ON"); + material.renderQueue = -1; + break; + } + + material.SetFloat("_Mode", (float)blendMode); + return material; + } + + } +} diff --git a/Assets/VRM10/Runtime/UnityBuilder/RuntimeUnityMaterialBuilder.cs.meta b/Assets/VRM10/Runtime/UnityBuilder/RuntimeUnityMaterialBuilder.cs.meta new file mode 100644 index 000000000..49ad66594 --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/RuntimeUnityMaterialBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2a34b3bcd188eb54d8153527bb11d248 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/UnityBuilder/UnityExtension.cs b/Assets/VRM10/Runtime/UnityBuilder/UnityExtension.cs new file mode 100644 index 000000000..97e827351 --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/UnityExtension.cs @@ -0,0 +1,120 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using VrmLib; + +namespace UniVRM10 +{ + public static class UnityExtension + { + public static Vector3 ToUnityVector3(this System.Numerics.Vector3 value) + { + return new Vector3(value.X, value.Y, value.Z); + } + + public static float[] ToFloat3(this System.Numerics.Vector3 value) + { + return new[] { value.X, value.Y, value.Z }; + } + + public static Color ToUnityColor(this System.Numerics.Vector3 value) + { + return new Color(value.X, value.Y, value.Z, 1); + } + + public static Vector4 ToUnityVector4(this System.Numerics.Vector4 value) + { + return new Vector4(value.X, value.Y, value.Z, value.W); + } + + public static Color ToUnityColor(this System.Numerics.Vector4 value) + { + return new Color(value.X, value.Y, value.Z, value.W); + } + + public static Color ToUnitySRGB(this VrmLib.LinearColor value) + { + return value.RGBA.ToUnityColor().gamma; + } + + public static Color ToUnityLinear(this VrmLib.LinearColor value) + { + return value.RGBA.ToUnityColor(); + } + + public static Quaternion ToUnityQuaternion(this System.Numerics.Quaternion value) + { + return new Quaternion(value.X, value.Y, value.Z, value.W); + } + + public static float[] ToFloat4(this System.Numerics.Quaternion value) + { + return new float[] { value.X, value.Y, value.Z, value.W }; + } + + public static System.Numerics.Vector2 ToNumericsVector2(this Vector2 value) + { + return new System.Numerics.Vector2(value.x, value.y); + } + + public static System.Numerics.Vector3 ToNumericsVector3(this Vector3 value) + { + return new System.Numerics.Vector3(value.x, value.y, value.z); + } + + public static System.Numerics.Vector4 ToNumericsVector4(this Vector4 value) + { + return new System.Numerics.Vector4(value.x, value.y, value.z, value.w); + } + + /// UnityのMaterialのColor値はSRGBで格納されている + public static VrmLib.LinearColor FromUnitySrgbToLinear(this Color value) + { + value = value.linear; + return new VrmLib.LinearColor + { + RGBA = new System.Numerics.Vector4(value.r, value.g, value.b, value.a) + }; + } + + public static VrmLib.LinearColor FromUnityLinear(this Color value) + { + return new VrmLib.LinearColor + { + RGBA = new System.Numerics.Vector4(value.r, value.g, value.b, value.a) + }; + } + + public static System.Numerics.Vector4 ToVector4(this Color value) + { + return new System.Numerics.Vector4(value.r, value.g, value.b, value.a); + } + + public static System.Numerics.Quaternion ToNumericsQuaternion(this Quaternion value) + { + return new System.Numerics.Quaternion(value.x, value.y, value.z, value.w); + } + + public static System.Numerics.Matrix4x4 ToNumericsMatrix4x4(this Matrix4x4 value) + { + return new System.Numerics.Matrix4x4( + value.m00, value.m01, value.m02, value.m03, + value.m10, value.m11, value.m12, value.m13, + value.m20, value.m21, value.m22, value.m23, + value.m30, value.m31, value.m32, value.m33 + ); + } + + public static VrmLib.Image ToPngImage(this UnityEngine.Texture2D texture, VrmLib.ImageUsage imageUsage) + { + if (texture != null) + { + return new VrmLib.Image(texture.name, "image/png", imageUsage, new System.ArraySegment(texture.EncodeToPNG())); + } + else + { + return null; + } + } + } +} \ No newline at end of file diff --git a/Assets/VRM10/Runtime/UnityBuilder/UnityExtension.cs.meta b/Assets/VRM10/Runtime/UnityBuilder/UnityExtension.cs.meta new file mode 100644 index 000000000..53aba7905 --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/UnityExtension.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 65d06a069acdcb642b8dde60f250e5e2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/UnityBuilder/VrmLoader.cs b/Assets/VRM10/Runtime/UnityBuilder/VrmLoader.cs new file mode 100644 index 000000000..f2889f8f4 --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/VrmLoader.cs @@ -0,0 +1,63 @@ +using System; +using System.IO; +using VrmLib; +using UniJSON; + +namespace UniVRM10 +{ + /// + /// utility for load VrmLib Model from byte[] + /// + public static class VrmLoader + { + // TODO: + const string VRM0X_LICENSE_URL = "https://vrm-consortium.org/"; + + /// + /// Load VRM10 or VRM0x from path + /// + public static Model CreateVrmModel(string path) + { + var bytes = File.ReadAllBytes(path); + return CreateVrmModel(bytes, new FileInfo(path)); + } + + public static Model CreateVrmModel(byte[] bytes, FileInfo path) + { + if (!Glb.TryParse(bytes, out Glb glb, out Exception ex)) + { + throw ex; + } + + var json = glb.Json.Bytes.ParseAsJson(); + + var extensions = json["extensions"]; + + foreach (var kv in extensions.ObjectItems()) + { + switch (kv.Key.GetString()) + { + // case "VRM": + // { + // var storage = new Vrm10Storage(glb.Json.Bytes, glb.Binary.Bytes); + // var model = ModelLoader.Load(storage, path.Name); + // model.ConvertCoordinate(Coordinates.Unity); + // return model; + // } + + case "VRMC_vrm": + { + var storage = new Vrm10Storage(glb.Json.Bytes, glb.Binary.Bytes); + var model = ModelLoader.Load(storage, path.Name); + model.ConvertCoordinate(Coordinates.Unity); + return model; + } + } + } + + // this is error + // throw new NotImplementedException(); + return null; + } + } +} diff --git a/Assets/VRM10/Runtime/UnityBuilder/VrmLoader.cs.meta b/Assets/VRM10/Runtime/UnityBuilder/VrmLoader.cs.meta new file mode 100644 index 000000000..af911b20b --- /dev/null +++ b/Assets/VRM10/Runtime/UnityBuilder/VrmLoader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ef069ccddc286cc4e930ba33447b0094 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/UnityExtensions.cs b/Assets/VRM10/Runtime/UnityExtensions.cs new file mode 100644 index 000000000..2e9e0796a --- /dev/null +++ b/Assets/VRM10/Runtime/UnityExtensions.cs @@ -0,0 +1,313 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +#if UNITY_EDITOR +using UnityEditor; +#endif + + +namespace UniVRM10 +{ + public struct PosRot + { + public Vector3 Position; + public Quaternion Rotation; + + public static PosRot FromGlobalTransform(Transform t) + { + return new PosRot + { + Position = t.position, + Rotation = t.rotation, + }; + } + } + + public class BlendShape + { + public string Name; + + public BlendShape(string name) + { + Name = name; + } + + public List Positions = new List(); + public List Normals = new List(); + public List Tangents = new List(); + } + + public static class UnityExtensions + { + public static Vector4 ReverseZ(this Vector4 v) + { + return new Vector4(v.x, v.y, -v.z, v.w); + } + + public static Vector3 ReverseZ(this Vector3 v) + { + return new Vector3(v.x, v.y, -v.z); + } + + [Obsolete] + public static Vector2 ReverseY(this Vector2 v) + { + return new Vector2(v.x, -v.y); + } + + public static Vector2 ReverseUV(this Vector2 v) + { + return new Vector2(v.x, 1.0f - v.y); + } + + public static Quaternion ReverseZ(this Quaternion q) + { + float angle; + Vector3 axis; + q.ToAngleAxis(out angle, out axis); + return Quaternion.AngleAxis(-angle, ReverseZ(axis)); + } + + public static Matrix4x4 Matrix4x4FromColumns(Vector4 c0, Vector4 c1, Vector4 c2, Vector4 c3) + { +#if UNITY_2017_1_OR_NEWER + return new Matrix4x4(c0, c1, c2, c3); +#else + var m = default(Matrix4x4); + m.SetColumn(0, c0); + m.SetColumn(1, c1); + m.SetColumn(2, c2); + m.SetColumn(3, c3); + return m; +#endif + } + + public static Matrix4x4 Matrix4x4FromRotation(Quaternion q) + { +#if UNITY_2017_1_OR_NEWER + return Matrix4x4.Rotate(q); +#else + var m = default(Matrix4x4); + m.SetTRS(Vector3.zero, q, Vector3.one); + return m; +#endif + } + + public static Matrix4x4 ReverseZ(this Matrix4x4 m) + { + m.SetTRS(m.ExtractPosition().ReverseZ(), m.ExtractRotation().ReverseZ(), m.ExtractScale()); + return m; + } + + public static Matrix4x4 MatrixFromArray(float[] values) + { + var m = new Matrix4x4(); + m.m00 = values[0]; + m.m10 = values[1]; + m.m20 = values[2]; + m.m30 = values[3]; + m.m01 = values[4]; + m.m11 = values[5]; + m.m21 = values[6]; + m.m31 = values[7]; + m.m02 = values[8]; + m.m12 = values[9]; + m.m22 = values[10]; + m.m32 = values[11]; + m.m03 = values[12]; + m.m13 = values[13]; + m.m23 = values[14]; + m.m33 = values[15]; + return m; + } + + // https://forum.unity.com/threads/how-to-assign-matrix4x4-to-transform.121966/ + public static Quaternion ExtractRotation(this Matrix4x4 matrix) + { + Vector3 forward; + forward.x = matrix.m02; + forward.y = matrix.m12; + forward.z = matrix.m22; + + Vector3 upwards; + upwards.x = matrix.m01; + upwards.y = matrix.m11; + upwards.z = matrix.m21; + + return Quaternion.LookRotation(forward, upwards); + } + + public static Vector3 ExtractPosition(this Matrix4x4 matrix) + { + Vector3 position; + position.x = matrix.m03; + position.y = matrix.m13; + position.z = matrix.m23; + return position; + } + + public static Vector3 ExtractScale(this Matrix4x4 matrix) + { + Vector3 scale; + scale.x = new Vector4(matrix.m00, matrix.m10, matrix.m20, matrix.m30).magnitude; + scale.y = new Vector4(matrix.m01, matrix.m11, matrix.m21, matrix.m31).magnitude; + scale.z = new Vector4(matrix.m02, matrix.m12, matrix.m22, matrix.m32).magnitude; + return scale; + } + + public static string RelativePathFrom(this Transform self, Transform root) + { + var path = new List(); + for (var current = self; current != null; current = current.parent) + { + if (current == root) + { + return String.Join("/", path.ToArray()); + } + + path.Insert(0, current.name); + } + + throw new Exception("no RelativePath"); + } + + public static Transform GetChildByName(this Transform self, string childName) + { + foreach (Transform child in self) + { + if (child.name == childName) + { + return child; + } + } + + throw new KeyNotFoundException(); + } + + public static Transform GetFromPath(this Transform self, string path) + { + var current = self; + + var splited = path.Split('/'); + + foreach (var childName in splited) + { + current = current.GetChildByName(childName); + } + + return current; + } + + public static IEnumerable GetChildren(this Transform self) + { + foreach (Transform child in self) + { + yield return child; + } + } + + public static IEnumerable Traverse(this Transform t) + { + yield return t; + foreach (Transform x in t) + { + foreach (Transform y in x.Traverse()) + { + yield return y; + } + } + } + + public static Transform FindDescenedant(this Transform t, string name) + { + return t.Traverse().First(x => x.name == name); + } + + public static IEnumerable Ancestors(this Transform t) + { + yield return t; + if (t.parent != null) + { + foreach (Transform x in t.parent.Ancestors()) + { + yield return x; + } + } + } + + public static float[] ToArray(this Quaternion q) + { + return new float[] { q.x, q.y, q.z, q.w }; + } + + public static float[] ToArray(this Vector3 v) + { + return new float[] { v.x, v.y, v.z }; + } + + public static float[] ToArray(this Vector4 v) + { + return new float[] { v.x, v.y, v.z, v.w }; + } + + public static float[] ToArray(this Color c) + { + return new float[] { c.r, c.g, c.b, c.a }; + } + + public static void ReverseZRecursive(this Transform root) + { + var globalMap = root.Traverse().ToDictionary(x => x, x => PosRot.FromGlobalTransform(x)); + + foreach (var x in root.Traverse()) + { + x.position = globalMap[x].Position.ReverseZ(); + x.rotation = globalMap[x].Rotation.ReverseZ(); + } + } + + public static Mesh GetSharedMesh(this Transform t) + { + var meshFilter = t.GetComponent(); + if (meshFilter != null) + { + return meshFilter.sharedMesh; + } + + var skinnedMeshRenderer = t.GetComponent(); + if (skinnedMeshRenderer != null) + { + return skinnedMeshRenderer.sharedMesh; + } + + return null; + } + + public static Material[] GetSharedMaterials(this Transform t) + { + var renderer = t.GetComponent(); + if (renderer != null) + { + return renderer.sharedMaterials; + } + + return new Material[] { }; + } + + public static bool Has(this Transform transform, T t) where T : Component + { + return transform.GetComponent() == t; + } + + public static T GetOrAddComponent(this GameObject go) where T : Component + { + var c = go.GetComponent(); + if (c != null) + { + return c; + } + return go.AddComponent(); + } + } +} diff --git a/Assets/VRM10/Runtime/UnityExtensions.cs.meta b/Assets/VRM10/Runtime/UnityExtensions.cs.meta new file mode 100644 index 000000000..a3f1b6a88 --- /dev/null +++ b/Assets/VRM10/Runtime/UnityExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5cb49bc8f0aa24c46b4e36387e1ed308 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/VRM10.asmdef b/Assets/VRM10/Runtime/VRM10.asmdef new file mode 100644 index 000000000..8bec55972 --- /dev/null +++ b/Assets/VRM10/Runtime/VRM10.asmdef @@ -0,0 +1,19 @@ +{ + "name": "VRM10", + "references": [ + "VrmLib", + "UniUnlit", + "MToon", + "MeshUtility", + "MeshUtility.Editor", + "UniGLTF" + ], + "optionalUnityReferences": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [] +} \ No newline at end of file diff --git a/Assets/VRM10/Runtime/VRM10.asmdef.meta b/Assets/VRM10/Runtime/VRM10.asmdef.meta new file mode 100644 index 000000000..a1b2b094c --- /dev/null +++ b/Assets/VRM10/Runtime/VRM10.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e47c917724578cc43b5506c17a27e9a0 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/VRMConverter.meta b/Assets/VRM10/Runtime/VRMConverter.meta new file mode 100644 index 000000000..40c354149 --- /dev/null +++ b/Assets/VRM10/Runtime/VRMConverter.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ee97e5dbbc50ad74593d5099908bc26b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/VRMConverter/MToonExport.cs b/Assets/VRM10/Runtime/VRMConverter/MToonExport.cs new file mode 100644 index 000000000..0b1def87d --- /dev/null +++ b/Assets/VRM10/Runtime/VRMConverter/MToonExport.cs @@ -0,0 +1,146 @@ +using System; +using UnityEngine; + +namespace UniVRM10 +{ + public static class MToonExtensions + { + public static VrmLib.TextureMagFilterType ToVrmLibMagFilter(this FilterMode mode) + { + switch (mode) + { + case FilterMode.Bilinear: + case FilterMode.Trilinear: + return VrmLib.TextureMagFilterType.LINEAR; + case FilterMode.Point: + return VrmLib.TextureMagFilterType.NEAREST; + + default: + throw new NotImplementedException(); + } + } + + public static VrmLib.TextureMinFilterType ToVrmLibMinFilter(this FilterMode mode) + { + switch (mode) + { + case FilterMode.Bilinear: + case FilterMode.Trilinear: + return VrmLib.TextureMinFilterType.LINEAR; + case FilterMode.Point: + return VrmLib.TextureMinFilterType.NEAREST; + + default: + throw new NotImplementedException(); + } + } + + public static VrmLib.TextureWrapType ToVrmLib(this TextureWrapMode mode) + { + switch (mode) + { + case TextureWrapMode.Clamp: + return VrmLib.TextureWrapType.CLAMP_TO_EDGE; + + case TextureWrapMode.Repeat: + return VrmLib.TextureWrapType.REPEAT; + + case TextureWrapMode.Mirror: + return VrmLib.TextureWrapType.MIRRORED_REPEAT; + + default: + throw new NotImplementedException(); + } + } + + /// + /// MToon.MToonDefinition(Unity) を VrmLib.MToon.MToonDefinition に変換する + /// + public static VrmLib.MToon.MToonDefinition ToVrmLib(this global::MToon.MToonDefinition unity, + Material material, + GetOrCreateTextureDelegate getOrCreateTexture) + { + return new VrmLib.MToon.MToonDefinition + { + Color = new VrmLib.MToon.ColorDefinition + { + CutoutThresholdValue = unity.Color.CutoutThresholdValue, + LitColor = unity.Color.LitColor.FromUnitySrgbToLinear(), + LitMultiplyTexture = unity.Color.LitMultiplyTexture.ToVrmLib(getOrCreateTexture, material, VrmLib.Texture.ColorSpaceTypes.Srgb), + ShadeColor = unity.Color.ShadeColor.FromUnitySrgbToLinear(), + ShadeMultiplyTexture = unity.Color.ShadeMultiplyTexture.ToVrmLib(getOrCreateTexture, material, VrmLib.Texture.ColorSpaceTypes.Srgb), + }, + Emission = new VrmLib.MToon.EmissionDefinition + { + EmissionColor = unity.Emission.EmissionColor.FromUnityLinear(), + EmissionMultiplyTexture = unity.Emission.EmissionMultiplyTexture.ToVrmLib(getOrCreateTexture, material, VrmLib.Texture.ColorSpaceTypes.Srgb), + }, + Lighting = new VrmLib.MToon.LightingDefinition + { + LightingInfluence = new VrmLib.MToon.LightingInfluenceDefinition + { + GiIntensityValue = unity.Lighting.LightingInfluence.GiIntensityValue, + LightColorAttenuationValue = unity.Lighting.LightingInfluence.LightColorAttenuationValue, + }, + LitAndShadeMixing = new VrmLib.MToon.LitAndShadeMixingDefinition + { + ShadingShiftValue = unity.Lighting.LitAndShadeMixing.ShadingShiftValue, + ShadingToonyValue = unity.Lighting.LitAndShadeMixing.ShadingToonyValue, + }, + Normal = new VrmLib.MToon.NormalDefinition + { + NormalScaleValue = unity.Lighting.Normal.NormalScaleValue, + NormalTexture = unity.Lighting.Normal.NormalTexture.ToVrmLib(getOrCreateTexture, material, VrmLib.Texture.ColorSpaceTypes.Linear, VrmLib.Texture.TextureTypes.NormalMap), + }, + }, + MatCap = new VrmLib.MToon.MatCapDefinition + { + AdditiveTexture = unity.MatCap.AdditiveTexture.ToVrmLib(getOrCreateTexture, material, VrmLib.Texture.ColorSpaceTypes.Srgb), + }, + Meta = new VrmLib.MToon.MetaDefinition + { + Implementation = unity.Meta.Implementation, + VersionNumber = unity.Meta.VersionNumber, + }, + Outline = new VrmLib.MToon.OutlineDefinition + { + OutlineColor = unity.Outline.OutlineColor.FromUnitySrgbToLinear(), + OutlineColorMode = (VrmLib.MToon.OutlineColorMode)unity.Outline.OutlineColorMode, + OutlineLightingMixValue = unity.Outline.OutlineLightingMixValue, + OutlineScaledMaxDistanceValue = unity.Outline.OutlineScaledMaxDistanceValue, + OutlineWidthMode = (VrmLib.MToon.OutlineWidthMode)unity.Outline.OutlineWidthMode, + OutlineWidthMultiplyTexture = unity.Outline.OutlineWidthMultiplyTexture.ToVrmLib(getOrCreateTexture, material, VrmLib.Texture.ColorSpaceTypes.Linear), + OutlineWidthValue = unity.Outline.OutlineWidthValue, + }, + Rendering = new VrmLib.MToon.RenderingDefinition + { + CullMode = (VrmLib.MToon.CullMode)unity.Rendering.CullMode, + RenderMode = (VrmLib.MToon.RenderMode)unity.Rendering.RenderMode, + RenderQueueOffsetNumber = unity.Rendering.RenderQueueOffsetNumber, + }, + Rim = new VrmLib.MToon.RimDefinition + { + RimColor = unity.Rim.RimColor.FromUnityLinear(), + RimFresnelPowerValue = unity.Rim.RimFresnelPowerValue, + RimLiftValue = unity.Rim.RimLiftValue, + RimLightingMixValue = unity.Rim.RimLightingMixValue, + RimMultiplyTexture = unity.Rim.RimMultiplyTexture.ToVrmLib(getOrCreateTexture, material, VrmLib.Texture.ColorSpaceTypes.Srgb), + }, + TextureOption = new VrmLib.MToon.TextureUvCoordsDefinition + { + MainTextureLeftBottomOriginOffset = unity.TextureOption.MainTextureLeftBottomOriginOffset.ToNumericsVector2(), + MainTextureLeftBottomOriginScale = unity.TextureOption.MainTextureLeftBottomOriginScale.ToNumericsVector2(), + UvAnimationMaskTexture = unity.TextureOption.UvAnimationMaskTexture.ToVrmLib(getOrCreateTexture, material, VrmLib.Texture.ColorSpaceTypes.Linear), + UvAnimationRotationSpeedValue = unity.TextureOption.UvAnimationRotationSpeedValue, + UvAnimationScrollXSpeedValue = unity.TextureOption.UvAnimationScrollXSpeedValue, + UvAnimationScrollYSpeedValue = unity.TextureOption.UvAnimationScrollYSpeedValue, + }, + }; + } + + static VrmLib.TextureInfo ToVrmLib(this Texture2D src, GetOrCreateTextureDelegate map, Material material, VrmLib.Texture.ColorSpaceTypes colorSpace, VrmLib.Texture.TextureTypes textureType = VrmLib.Texture.TextureTypes.Default) + { + return map(material, src, colorSpace, textureType); + } + } +} diff --git a/Assets/VRM10/Runtime/VRMConverter/MToonExport.cs.meta b/Assets/VRM10/Runtime/VRMConverter/MToonExport.cs.meta new file mode 100644 index 000000000..1498f72f5 --- /dev/null +++ b/Assets/VRM10/Runtime/VRMConverter/MToonExport.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a54f187c333215b438b2711790de1237 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/VRMConverter/RuntimeVrmConverter.cs b/Assets/VRM10/Runtime/VRMConverter/RuntimeVrmConverter.cs new file mode 100644 index 000000000..e38e53a5b --- /dev/null +++ b/Assets/VRM10/Runtime/VRMConverter/RuntimeVrmConverter.cs @@ -0,0 +1,856 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using MeshUtility; +using UnityEngine; +using VrmLib; + +namespace UniVRM10 +{ + public delegate VrmLib.TextureInfo GetOrCreateTextureDelegate(UnityEngine.Material material, UnityEngine.Texture srcTexture, VrmLib.Texture.ColorSpaceTypes colorSpace, VrmLib.Texture.TextureTypes textureType); + public class RuntimeVrmConverter + { + public VrmLib.Model Model; + + public Dictionary Nodes = new Dictionary(); + public Dictionary Textures = new Dictionary(); + public Dictionary Materials = new Dictionary(); + public Dictionary Meshes = new Dictionary(); + + static string GetSupportedMime(string path) + { + var ext = Path.GetExtension(path).ToLower(); + switch (ext) + { + case ".png": return "image/png"; + case ".jpg": return "image/jpeg"; + } + + // .tga etc + return null; + } + + /// + /// return (bytes, mime string) + /// + static (byte[], string) GetImageEncodedBytes(UnityEngine.Texture src, RenderTextureReadWrite renderTextureReadWrite, UnityEngine.Material renderMaterial = null) + { +#if false + /// 元になるアセットがあればそれを得る(png, jpgのみ) + var assetPath = UnityEditor.AssetDatabase.GetAssetPath(src); + if (!string.IsNullOrEmpty(assetPath)) + { + var mime = GetSupportedMime(assetPath); + if (!string.IsNullOrEmpty(mime)) + { + return (File.ReadAllBytes(assetPath), GetSupportedMime(assetPath)); + } + } +#endif + + var copy = UnityTextureUtil.CopyTexture(src, renderTextureReadWrite, renderMaterial); + return (copy.EncodeToPNG(), "image/png"); + } + + public VrmLib.TextureInfo GetOrCreateTexture(UnityEngine.Material material, UnityEngine.Texture srcTexture, VrmLib.Texture.ColorSpaceTypes colorSpace, VrmLib.Texture.TextureTypes textureType) + { + var texture = srcTexture as Texture2D; + if (texture is null) + { + return null; + } + + if (!Textures.TryGetValue(texture, out VrmLib.TextureInfo info)) + { + UnityEngine.Material converter = null; + if (textureType == VrmLib.Texture.TextureTypes.NormalMap) + { + converter = TextureConvertMaterial.GetNormalMapConvertUnityToGltf(); + } + else if (textureType == VrmLib.Texture.TextureTypes.MetallicRoughness) + { + float smoothness = 0.0f; + if (material.HasProperty("_GlossMapScale")) + { + smoothness = material.GetFloat("_GlossMapScale"); + } + + converter = TextureConvertMaterial.GetMetallicRoughnessUnityToGltf(smoothness); + } + else if (textureType == VrmLib.Texture.TextureTypes.Occlusion) + { + converter = TextureConvertMaterial.GetOcclusionUnityToGltf(); + } + + var (bytes, mime) = GetImageEncodedBytes( + texture, + (colorSpace == VrmLib.Texture.ColorSpaceTypes.Linear) ? RenderTextureReadWrite.Linear : RenderTextureReadWrite.sRGB, + converter + ); + + if (converter != null) + { + UnityEngine.Object.DestroyImmediate(converter); + } + + var sampler = new VrmLib.TextureSampler + { + MagFilter = texture.filterMode.ToVrmLibMagFilter(), + MinFilter = texture.filterMode.ToVrmLibMinFilter(), + WrapS = texture.wrapMode.ToVrmLib(), + WrapT = texture.wrapMode.ToVrmLib(), + }; + var image = new VrmLib.Image(texture.name, mime, VrmLib.ImageUsage.None, new ArraySegment(bytes)); + info = new VrmLib.TextureInfo(new VrmLib.ImageTexture(texture.name, sampler, image, colorSpace, textureType)); + Textures.Add(texture, info); + + if (Model != null) + { + Model.Images.Add(image); + Model.Textures.Add(info.Texture); + } + } + + return info; + } + + #region Export 1.0 + /// + /// VRM-0.X の MaterialBindValue を VRM-1.0 仕様に変換する + /// + /// * Property名 => enum MaterialBindType + /// * 特に _MainTex_ST の場合、MaterialBindType.UvScale + MaterialBindType.UvScale 2つになりうる + /// + /// + VrmLib.Expression ToVrmLib(VRM10Expression clip, GameObject root) + { + var expression = new VrmLib.Expression(clip.Preset, clip.ExpressionName, clip.IsBinary); + expression.IgnoreBlink = clip.IgnoreBlink; + expression.IgnoreLookAt = clip.IgnoreLookAt; + expression.IgnoreMouth = clip.IgnoreMouth; + + foreach (var binding in clip.MorphTargetBindings) + { + var transform = GetTransformFromRelativePath(root.transform, binding.RelativePath); + if (transform == null) + continue; + var renderer = transform.gameObject.GetComponent(); + if (renderer == null) + continue; + var mesh = renderer.sharedMesh; + if (mesh == null) + continue; + + var names = new List(); + for (int i = 0; i < mesh.blendShapeCount; ++i) + { + names.Add(mesh.GetBlendShapeName(i)); + } + + var node = Nodes[transform.gameObject]; + var blendShapeValue = new VrmLib.MorphTargetBind( + node, + names[binding.Index], + binding.Weight + ); + expression.MorphTargetBinds.Add(blendShapeValue); + } + + foreach (var binding in clip.MaterialColorBindings) + { + var materialPair = Materials.FirstOrDefault(x => x.Key.name == binding.MaterialName); + if (materialPair.Value != null) + { + var bind = new VrmLib.MaterialColorBind( + materialPair.Value, + binding.BindType, + binding.TargetValue.ToNumericsVector4() + ); + expression.MaterialColorBinds.Add(bind); + } + } + + foreach (var binding in clip.MaterialUVBindings) + { + var materialPair = Materials.FirstOrDefault(x => x.Key.name == binding.MaterialName); + if (materialPair.Value != null) + { + var bind = new VrmLib.TextureTransformBind( + materialPair.Value, + binding.Scaling.ToNumericsVector2(), + binding.Offset.ToNumericsVector2() + ); + expression.TextureTransformBinds.Add(bind); + } + } + + return expression; + } + + /// + /// metaObject が null のときは、root から取得する + /// + public VrmLib.Model ToModelFrom10(GameObject root, VRM10MetaObject metaObject = null) + { + Model = new VrmLib.Model(VrmLib.Coordinates.Unity); + + if (metaObject is null) + { + var vrmController = root.GetComponent(); + if (vrmController is null || vrmController.Meta is null) + { + throw new NullReferenceException("metaObject is null"); + } + metaObject = vrmController.Meta; + } + + ToGlbModel(root); + + // meta + var meta = new VrmLib.Meta(); + meta.Name = metaObject.Name; + meta.Version = metaObject.Version; + meta.CopyrightInformation = metaObject.CopyrightInformation; + meta.Authors.AddRange(metaObject.Authors); + meta.ContactInformation = metaObject.ContactInformation; + meta.Reference = metaObject.Reference; + meta.Thumbnail = metaObject.Thumbnail.ToPngImage(VrmLib.ImageUsage.None); + + meta.AvatarPermission = new VrmLib.AvatarPermission + { + AvatarUsage = metaObject.AllowedUser, + IsAllowedViolentUsage = metaObject.ViolentUsage, + IsAllowedSexualUsage = metaObject.SexualUsage, + CommercialUsage = metaObject.CommercialUsage, + IsAllowedGameUsage = metaObject.GameUsage, + IsAllowedPoliticalOrReligiousUsage = metaObject.PoliticalOrReligiousUsage, + OtherPermissionUrl = metaObject.OtherPermissionUrl, + }; + meta.RedistributionLicense = new VrmLib.RedistributionLicense + { + CreditNotation = metaObject.CreditNotation, + IsAllowRedistribution = metaObject.Redistribution, + ModificationLicense = metaObject.ModificationLicense, + OtherLicenseUrl = metaObject.OtherLicenseUrl, + }; + Model.Vrm = new VrmLib.Vrm(meta, UniVRM10.VRMVersion.VERSION, UniVRM10.VRMSpecVersion.Version); + + // humanoid + { + var humanoid = root.GetComponent(); + if (humanoid is null) + { + humanoid = root.AddComponent(); + humanoid.AssignBonesFromAnimator(); + } + + foreach (HumanBodyBones humanBoneType in Enum.GetValues(typeof(HumanBodyBones))) + { + var transform = humanoid.GetBoneTransform(humanBoneType); + if (transform != null && Nodes.TryGetValue(transform.gameObject, out VrmLib.Node node)) + { + node.HumanoidBone = (VrmLib.HumanoidBones)Enum.Parse(typeof(VrmLib.HumanoidBones), humanBoneType.ToString(), true); + } + } + } + + + // blendShape + var controller = root.GetComponent(); + { + Model.Vrm.ExpressionManager = new VrmLib.ExpressionManager(); + if (controller != null) + { + foreach (var clip in controller.Expression.ExpressionAvatar.Clips) + { + var expression = ToVrmLib(clip, root); + if (expression != null) + { + Model.Vrm.ExpressionManager.ExpressionList.Add(expression); + } + } + } + } + + // firstPerson + { + var firstPerson = new VrmLib.FirstPerson(); + if (controller != null) + { + foreach (var annotation in controller.FirstPerson.Renderers) + { + firstPerson.Annotations.Add( + new VrmLib.FirstPersonMeshAnnotation(Nodes[annotation.Renderer.gameObject], + annotation.FirstPersonFlag) + ); + } + Model.Vrm.FirstPerson = firstPerson; + } + } + + // lookAt + { + var lookAt = new VrmLib.LookAt(); + if (controller != null) + { + if (controller.LookAt.LookAtType == VRM10ControllerLookAt.LookAtTypes.Expression) + { + lookAt.HorizontalInner = new VrmLib.LookAtRangeMap(); + lookAt.HorizontalOuter = new VrmLib.LookAtRangeMap() + { + InputMaxValue = controller.LookAt.HorizontalOuter.CurveXRangeDegree, + OutputScaling = controller.LookAt.HorizontalOuter.CurveYRangeDegree + }; + lookAt.VerticalUp = new VrmLib.LookAtRangeMap() + { + InputMaxValue = controller.LookAt.VerticalUp.CurveXRangeDegree, + OutputScaling = controller.LookAt.VerticalUp.CurveYRangeDegree, + }; + lookAt.VerticalDown = new VrmLib.LookAtRangeMap() + { + InputMaxValue = controller.LookAt.VerticalDown.CurveXRangeDegree, + OutputScaling = controller.LookAt.VerticalDown.CurveYRangeDegree, + }; + } + else if (controller.LookAt.LookAtType == VRM10ControllerLookAt.LookAtTypes.Bone) + { + lookAt.HorizontalInner = new VrmLib.LookAtRangeMap() + { + InputMaxValue = controller.LookAt.HorizontalInner.CurveXRangeDegree, + OutputScaling = controller.LookAt.HorizontalInner.CurveYRangeDegree + }; + lookAt.HorizontalOuter = new VrmLib.LookAtRangeMap() + { + InputMaxValue = controller.LookAt.HorizontalOuter.CurveXRangeDegree, + OutputScaling = controller.LookAt.HorizontalOuter.CurveYRangeDegree + }; + lookAt.VerticalUp = new VrmLib.LookAtRangeMap() + { + InputMaxValue = controller.LookAt.VerticalUp.CurveXRangeDegree, + OutputScaling = controller.LookAt.VerticalUp.CurveYRangeDegree, + }; + lookAt.VerticalDown = new VrmLib.LookAtRangeMap() + { + InputMaxValue = controller.LookAt.VerticalDown.CurveXRangeDegree, + OutputScaling = controller.LookAt.VerticalDown.CurveYRangeDegree, + }; + } + lookAt.OffsetFromHeadBone = controller.LookAt.OffsetFromHead.ToNumericsVector3(); + } + Model.Vrm.LookAt = lookAt; + } + + // springBone + { + var springBoneColliderGroups = root.GetComponentsInChildren(); + if (springBoneColliderGroups != null) + { + Model.Vrm.SpringBone = new VrmLib.SpringBoneManager(); + var colliders = new Dictionary(); + foreach (var colliderGroup in springBoneColliderGroups) + { + var colliderGroups = colliderGroup.Colliders.Select(x => + { + switch (x.ColliderType) + { + case SpringBoneColliderTypes.Sphere: + return VrmLib.VrmSpringBoneCollider.CreateSphere(x.Offset.ToNumericsVector3(), x.Radius); + + case SpringBoneColliderTypes.Capsule: + return VrmLib.VrmSpringBoneCollider.CreateCapsule(x.Offset.ToNumericsVector3(), x.Radius, x.Tail.ToNumericsVector3()); + + default: + throw new NotImplementedException(); + } + }); + var vrmColliderGroup = new VrmLib.SpringBoneColliderGroup(Nodes[colliderGroup.gameObject], colliderGroups); + Model.Vrm.SpringBone.Colliders.Add(vrmColliderGroup); + + colliders.Add(colliderGroup, vrmColliderGroup); + } + + var springBones = root.GetComponentsInChildren(); + foreach (var springBone in springBones) + { + var vrmSpringBone = new VrmLib.SpringBone() + { + Comment = springBone.m_comment, + Stiffness = springBone.m_stiffnessForce, + GravityPower = springBone.m_gravityPower, + GravityDir = springBone.m_gravityDir.ToNumericsVector3(), + DragForce = springBone.m_dragForce, + Origin = (springBone.m_center != null) ? Nodes[springBone.m_center.gameObject] : null, + HitRadius = springBone.m_hitRadius, + }; + + foreach (var rootBone in springBone.RootBones) + { + vrmSpringBone.Bones.Add(Nodes[rootBone.gameObject]); + } + + foreach (var collider in springBone.ColliderGroups) + { + vrmSpringBone.Colliders.Add(colliders[collider]); + } + + Model.Vrm.SpringBone.Springs.Add(vrmSpringBone); + } + } + } + + return Model; + } + + public VrmLib.Model ToGlbModel(GameObject root) + { + if (Model == null) + { + Model = new VrmLib.Model(VrmLib.Coordinates.Unity); + } + + // node + { + Model.Root.Name = root.name; + CreateNodes(root.transform, Model.Root, Nodes); + Model.Nodes = Nodes + .Where(x => x.Value != Model.Root) + .Select(x => x.Value).ToList(); + } + + // material and textures + var rendererComponents = root.GetComponentsInChildren(); + { + foreach (var renderer in rendererComponents) + { + var materials = renderer.sharedMaterials; // avoid copy + foreach (var material in materials) + { + if (Materials.ContainsKey(material)) + { + continue; + } + + var vrmMaterial = Export10(material, GetOrCreateTexture); + Model.Materials.Add(vrmMaterial); + Materials.Add(material, vrmMaterial); + } + } + } + + // mesh + { + foreach (var renderer in rendererComponents) + { + if (renderer is SkinnedMeshRenderer skinnedMeshRenderer) + { + if (skinnedMeshRenderer.sharedMesh != null) + { + var mesh = CreateMesh(skinnedMeshRenderer.sharedMesh, skinnedMeshRenderer, Materials); + var skin = CreateSkin(skinnedMeshRenderer, Nodes, root); + if (skin != null) + { + // blendshape only で skinning が無いやつがある + mesh.Skin = skin; + Model.Skins.Add(mesh.Skin); + } + Model.MeshGroups.Add(mesh); + Nodes[renderer.gameObject].MeshGroup = mesh; + Meshes.Add(skinnedMeshRenderer.sharedMesh, mesh); + } + } + else if (renderer is MeshRenderer meshRenderer) + { + var filter = meshRenderer.gameObject.GetComponent(); + if (filter != null && filter.sharedMesh != null) + { + var mesh = CreateMesh(filter.sharedMesh, meshRenderer, Materials); + Model.MeshGroups.Add(mesh); + Nodes[renderer.gameObject].MeshGroup = mesh; + Meshes.Add(filter.sharedMesh, mesh); + } + } + } + } + + return Model; + } + #endregion + + public VrmLib.Material Export10(UnityEngine.Material src, GetOrCreateTextureDelegate map) + { + switch (src.shader.name) + { + case "VRM/MToon": + { + var def = MToon.Utils.GetMToonParametersFromMaterial(src); + return new VrmLib.MToonMaterial(src.name) + { + Definition = def.ToVrmLib(src, map), + }; + } + + case "Unlit/Color": + return new VrmLib.UnlitMaterial(src.name) + { + BaseColorFactor = src.color.FromUnitySrgbToLinear(), + }; + + case "Unlit/Texture": + return new VrmLib.UnlitMaterial(src.name) + { + BaseColorTexture = map(src, src.mainTexture as Texture2D, VrmLib.Texture.ColorSpaceTypes.Srgb, VrmLib.Texture.TextureTypes.Default), + }; + + case "Unlit/Transparent": + return new VrmLib.UnlitMaterial(src.name) + { + BaseColorTexture = map(src, src.mainTexture as Texture2D, VrmLib.Texture.ColorSpaceTypes.Srgb, VrmLib.Texture.TextureTypes.Default), + AlphaMode = VrmLib.AlphaModeType.BLEND, + }; + + case "Unlit/Transparent Cutout": + return new VrmLib.UnlitMaterial(src.name) + { + BaseColorTexture = map(src, src.mainTexture as Texture2D, VrmLib.Texture.ColorSpaceTypes.Srgb, VrmLib.Texture.TextureTypes.Default), + AlphaMode = VrmLib.AlphaModeType.MASK, + AlphaCutoff = src.GetFloat("_Cutoff"), + }; + + case "UniGLTF/UniUnlit": + case "VRM/UniUnlit": + { + var material = new VrmLib.UnlitMaterial(src.name) + { + BaseColorFactor = src.color.FromUnitySrgbToLinear(), + BaseColorTexture = map(src, src.mainTexture as Texture2D, VrmLib.Texture.ColorSpaceTypes.Srgb, VrmLib.Texture.TextureTypes.Default), + AlphaMode = GetAlphaMode(src), + DoubleSided = UniGLTF.UniUnlit.Utils.GetCullMode(src) == UniGLTF.UniUnlit.UniUnlitCullMode.Off, + }; + if (material.AlphaMode == VrmLib.AlphaModeType.MASK) + { + material.AlphaCutoff = src.GetFloat("_Cutoff"); + } + // TODO: VertexColorMode + return material; + } + + default: + return ExportStandard(src, map); + } + } + + static VrmLib.AlphaModeType GetAlphaMode(UnityEngine.Material m) + { + switch (UniGLTF.UniUnlit.Utils.GetRenderMode(m)) + { + case UniGLTF.UniUnlit.UniUnlitRenderMode.Opaque: return VrmLib.AlphaModeType.OPAQUE; + case UniGLTF.UniUnlit.UniUnlitRenderMode.Cutout: return VrmLib.AlphaModeType.MASK; + case UniGLTF.UniUnlit.UniUnlitRenderMode.Transparent: return VrmLib.AlphaModeType.BLEND; + } + throw new NotImplementedException(); + } + + static VrmLib.PBRMaterial ExportStandard(UnityEngine.Material src, GetOrCreateTextureDelegate map) + { + var material = new VrmLib.PBRMaterial(src.name) + { + }; + + switch (src.GetTag("RenderType", true)) + { + case "Transparent": + material.AlphaMode = VrmLib.AlphaModeType.BLEND; + break; + + case "TransparentCutout": + material.AlphaMode = VrmLib.AlphaModeType.MASK; + material.AlphaCutoff = src.GetFloat("_Cutoff"); + break; + + default: + material.AlphaMode = VrmLib.AlphaModeType.OPAQUE; + break; + } + + if (src.HasProperty("_Color")) + { + material.BaseColorFactor = src.color.linear.FromUnitySrgbToLinear(); + } + + if (src.HasProperty("_MainTex")) + { + material.BaseColorTexture = map(src, src.GetTexture("_MainTex"), VrmLib.Texture.ColorSpaceTypes.Srgb, VrmLib.Texture.TextureTypes.Default); + } + + if (src.HasProperty("_MetallicGlossMap")) + { + // float smoothness = 0.0f; + // if (m.HasProperty("_GlossMapScale")) + // { + // smoothness = m.GetFloat("_GlossMapScale"); + // } + + material.MetallicRoughnessTexture = map( + src, + src.GetTexture("_MetallicGlossMap"), + VrmLib.Texture.ColorSpaceTypes.Linear, + VrmLib.Texture.TextureTypes.MetallicRoughness)?.Texture; + if (material.MetallicRoughnessTexture != null) + { + material.MetallicFactor = 1.0f; + // Set 1.0f as hard-coded. See: https://github.com/vrm-c/UniVRM/issues/212. + material.RoughnessFactor = 1.0f; + } + } + + if (material.MetallicRoughnessTexture == null) + { + if (src.HasProperty("_Metallic")) + { + material.MetallicFactor = src.GetFloat("_Metallic"); + } + + if (src.HasProperty("_Glossiness")) + { + material.RoughnessFactor = 1.0f - src.GetFloat("_Glossiness"); + } + } + + if (src.HasProperty("_BumpMap")) + { + material.NormalTexture = map(src, src.GetTexture("_BumpMap"), VrmLib.Texture.ColorSpaceTypes.Linear, VrmLib.Texture.TextureTypes.NormalMap)?.Texture; + + if (src.HasProperty("_BumpScale")) + { + material.NormalTextureScale = src.GetFloat("_BumpScale"); + } + } + + if (src.HasProperty("_OcclusionMap")) + { + material.OcclusionTexture = map(src, src.GetTexture("_OcclusionMap"), VrmLib.Texture.ColorSpaceTypes.Linear, VrmLib.Texture.TextureTypes.Occlusion)?.Texture; + + if (src.HasProperty("_OcclusionStrength")) + { + material.OcclusionTextureStrength = src.GetFloat("_OcclusionStrength"); + } + } + + if (src.IsKeywordEnabled("_EMISSION")) + { + if (src.HasProperty("_EmissionColor")) + { + var color = src.GetColor("_EmissionColor"); + if (color.maxColorComponent > 1) + { + color /= color.maxColorComponent; + } + material.EmissiveFactor = new System.Numerics.Vector3(color.r, color.g, color.b); + } + + if (src.HasProperty("_EmissionMap")) + { + material.EmissiveTexture = map(src, src.GetTexture("_EmissionMap"), VrmLib.Texture.ColorSpaceTypes.Srgb, VrmLib.Texture.TextureTypes.Emissive)?.Texture; + } + } + + return material; + } + + private static void CreateNodes( + Transform parentTransform, + VrmLib.Node parentNode, + Dictionary nodes) + { + // parentNode.SetMatrix(parentTransform.localToWorldMatrix.ToNumericsMatrix4x4(), false); + parentNode.LocalTranslation = parentTransform.localPosition.ToNumericsVector3(); + parentNode.LocalRotation = parentTransform.localRotation.ToNumericsQuaternion(); + parentNode.LocalScaling = parentTransform.localScale.ToNumericsVector3(); + nodes.Add(parentTransform.gameObject, parentNode); + + foreach (Transform child in parentTransform) + { + var childNode = new VrmLib.Node(child.gameObject.name); + CreateNodes(child, childNode, nodes); + parentNode.Add(childNode); + } + } + + private static Transform GetTransformFromRelativePath(Transform root, string relativePath) + { + var paths = new Queue(relativePath.Split('/')); + return GetTransformFromRelativePath(root, paths); + } + + private static Transform GetTransformFromRelativePath(Transform root, Queue relativePath) + { + var name = relativePath.Dequeue(); + foreach (Transform node in root) + { + if (node.gameObject.name == name) + { + if (relativePath.Count == 0) + { + return node; + } + else + { + return GetTransformFromRelativePath(node, relativePath); + } + } + } + + return null; + } + + private static VrmLib.MeshGroup CreateMesh(UnityEngine.Mesh mesh, Renderer renderer, Dictionary materials) + { + var meshGroup = new VrmLib.MeshGroup(mesh.name); + var vrmMesh = new VrmLib.Mesh(); + vrmMesh.VertexBuffer = new VrmLib.VertexBuffer(); + vrmMesh.VertexBuffer.Add(VrmLib.VertexBuffer.PositionKey, ToBufferAccessor(mesh.vertices)); + + if (mesh.boneWeights.Length == mesh.vertexCount) + { + vrmMesh.VertexBuffer.Add( + VrmLib.VertexBuffer.WeightKey, + ToBufferAccessor(mesh.boneWeights.Select(x => + new Vector4(x.weight0, x.weight1, x.weight2, x.weight3)).ToArray() + )); + vrmMesh.VertexBuffer.Add( + VrmLib.VertexBuffer.JointKey, + ToBufferAccessor(mesh.boneWeights.Select(x => + new VrmLib.SkinJoints((ushort)x.boneIndex0, (ushort)x.boneIndex1, (ushort)x.boneIndex2, (ushort)x.boneIndex3)).ToArray() + )); + } + if (mesh.uv.Length == mesh.vertexCount) vrmMesh.VertexBuffer.Add(VrmLib.VertexBuffer.TexCoordKey, ToBufferAccessor(mesh.uv)); + if (mesh.normals.Length == mesh.vertexCount) vrmMesh.VertexBuffer.Add(VrmLib.VertexBuffer.NormalKey, ToBufferAccessor(mesh.normals)); + if (mesh.colors.Length == mesh.vertexCount) vrmMesh.VertexBuffer.Add(VrmLib.VertexBuffer.ColorKey, ToBufferAccessor(mesh.colors)); + vrmMesh.IndexBuffer = ToBufferAccessor(mesh.triangles); + + int offset = 0; + for (int i = 0; i < mesh.subMeshCount; i++) + { +#if UNITY_2019 + var subMesh = mesh.GetSubMesh(i); + try + { + vrmMesh.Submeshes.Add(new VrmLib.Submesh(offset, subMesh.indexCount, materials[renderer.sharedMaterials[i]])); + } + catch (Exception ex) + { + Debug.LogError(ex); + } + offset += subMesh.indexCount; +#else + var triangles = mesh.GetTriangles(i); + try + { + vrmMesh.Submeshes.Add(new VrmLib.Submesh(offset, triangles.Length, materials[renderer.sharedMaterials[i]])); + } + catch (Exception ex) + { + Debug.LogError(ex); + } + offset += triangles.Length; +#endif + } + + for (int i = 0; i < mesh.blendShapeCount; i++) + { + var blendShapeVertices = mesh.vertices; + var usePosition = blendShapeVertices != null && blendShapeVertices.Length > 0; + + var blendShapeNormals = mesh.normals; + var useNormal = usePosition && blendShapeNormals != null && blendShapeNormals.Length == blendShapeVertices.Length; + // var useNormal = usePosition && blendShapeNormals != null && blendShapeNormals.Length == blendShapeVertices.Length && !exportOnlyBlendShapePosition; + + var blendShapeTangents = mesh.tangents.Select(y => (Vector3)y).ToArray(); + //var useTangent = usePosition && blendShapeTangents != null && blendShapeTangents.Length == blendShapeVertices.Length; + // var useTangent = false; + + var frameCount = mesh.GetBlendShapeFrameCount(i); + mesh.GetBlendShapeFrameVertices(i, frameCount - 1, blendShapeVertices, blendShapeNormals, null); + + if (usePosition) + { + var morphTarget = new VrmLib.MorphTarget(mesh.GetBlendShapeName(i)); + morphTarget.VertexBuffer = new VrmLib.VertexBuffer(); + morphTarget.VertexBuffer.Add(VrmLib.VertexBuffer.PositionKey, ToBufferAccessor(blendShapeVertices)); + vrmMesh.MorphTargets.Add(morphTarget); + } + } + + meshGroup.Meshes.Add(vrmMesh); + return meshGroup; + } + + private static VrmLib.Skin CreateSkin( + SkinnedMeshRenderer skinnedMeshRenderer, + Dictionary nodes, + GameObject root) + { + if (skinnedMeshRenderer.bones == null || skinnedMeshRenderer.bones.Length == 0) + { + return null; + } + + var skin = new VrmLib.Skin(); + skin.InverseMatrices = ToBufferAccessor(skinnedMeshRenderer.sharedMesh.bindposes); + if (skinnedMeshRenderer.rootBone != null) + { + skin.Root = nodes[skinnedMeshRenderer.rootBone.gameObject]; + } + + skin.Joints = skinnedMeshRenderer.bones.Select(x => nodes[x.gameObject]).ToList(); + return skin; + } + + private static VrmLib.BufferAccessor ToBufferAccessor(VrmLib.SkinJoints[] values) + { + return ToBufferAccessor(values, VrmLib.AccessorValueType.UNSIGNED_SHORT, VrmLib.AccessorVectorType.VEC4); + } + + private static VrmLib.BufferAccessor ToBufferAccessor(Color[] colors) + { + return ToBufferAccessor(colors, VrmLib.AccessorValueType.FLOAT, VrmLib.AccessorVectorType.VEC4); + } + + private static VrmLib.BufferAccessor ToBufferAccessor(Vector4[] vectors) + { + return ToBufferAccessor(vectors, VrmLib.AccessorValueType.FLOAT, VrmLib.AccessorVectorType.VEC4); + } + + private static VrmLib.BufferAccessor ToBufferAccessor(Vector3[] vectors) + { + return ToBufferAccessor(vectors, VrmLib.AccessorValueType.FLOAT, VrmLib.AccessorVectorType.VEC3); + } + + private static VrmLib.BufferAccessor ToBufferAccessor(Vector2[] vectors) + { + return ToBufferAccessor(vectors, VrmLib.AccessorValueType.FLOAT, VrmLib.AccessorVectorType.VEC2); + } + + private static VrmLib.BufferAccessor ToBufferAccessor(int[] scalars) + { + return ToBufferAccessor(scalars, VrmLib.AccessorValueType.UNSIGNED_INT, VrmLib.AccessorVectorType.SCALAR); + } + + private static VrmLib.BufferAccessor ToBufferAccessor(Matrix4x4[] matrixes) + { + return ToBufferAccessor(matrixes, VrmLib.AccessorValueType.FLOAT, VrmLib.AccessorVectorType.MAT4); + } + + private static VrmLib.BufferAccessor ToBufferAccessor(T[] value, VrmLib.AccessorValueType valueType, VrmLib.AccessorVectorType vectorType) where T : struct + { + var span = VrmLib.SpanLike.CopyFrom(value); + return new VrmLib.BufferAccessor( + span.Bytes, + valueType, + vectorType, + value.Length + ); + } + } +} diff --git a/Assets/VRM10/Runtime/VRMConverter/RuntimeVrmConverter.cs.meta b/Assets/VRM10/Runtime/VRMConverter/RuntimeVrmConverter.cs.meta new file mode 100644 index 000000000..a49ea9a83 --- /dev/null +++ b/Assets/VRM10/Runtime/VRMConverter/RuntimeVrmConverter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6b619b24f8ac1494290458193ebca6aa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/VRMConverter/UnityTextureUtil.cs b/Assets/VRM10/Runtime/VRMConverter/UnityTextureUtil.cs new file mode 100644 index 000000000..4603f3bde --- /dev/null +++ b/Assets/VRM10/Runtime/VRMConverter/UnityTextureUtil.cs @@ -0,0 +1,85 @@ +using System; +using UnityEngine; + +namespace UniVRM10 +{ + public static class UnityTextureUtil + { + struct ColorSpaceScope : IDisposable + { + bool m_sRGBWrite; + + public ColorSpaceScope(RenderTextureReadWrite dstColorSpace) + { + m_sRGBWrite = GL.sRGBWrite; + switch (dstColorSpace) + { + case RenderTextureReadWrite.Linear: + GL.sRGBWrite = false; + break; + + case RenderTextureReadWrite.sRGB: + default: + GL.sRGBWrite = true; + break; + } + } + public ColorSpaceScope(bool sRGBWrite) + { + m_sRGBWrite = GL.sRGBWrite; + GL.sRGBWrite = sRGBWrite; + } + + public void Dispose() + { + GL.sRGBWrite = m_sRGBWrite; + } + } + + /// + /// Copy texture for export. + /// Use when source texture is not Texture2D or isReadable==false. + /// + public static Texture2D CopyTexture(Texture src, RenderTextureReadWrite dstColorSpace, Material material = null) + { + Texture2D dst = null; + + var renderTexture = new RenderTexture(src.width, src.height, 0, RenderTextureFormat.ARGB32, dstColorSpace); + + using (var scope = new ColorSpaceScope(dstColorSpace)) + { + if (material != null) + { + Graphics.Blit(src, renderTexture, material); + } + else + { + Graphics.Blit(src, renderTexture); + } + } + + dst = new Texture2D(src.width, src.height, TextureFormat.ARGB32, false, dstColorSpace == RenderTextureReadWrite.Linear); + dst.ReadPixels(new Rect(0, 0, src.width, src.height), 0, 0); + dst.name = src.name; + dst.anisoLevel = src.anisoLevel; + dst.filterMode = src.filterMode; + dst.mipMapBias = src.mipMapBias; + dst.wrapMode = src.wrapMode; + dst.wrapModeU = src.wrapModeU; + dst.wrapModeV = src.wrapModeV; + dst.wrapModeW = src.wrapModeW; + dst.Apply(); + + RenderTexture.active = null; + if (Application.isEditor) + { + GameObject.DestroyImmediate(renderTexture); + } + else + { + GameObject.Destroy(renderTexture); + } + return dst; + } + } +} \ No newline at end of file diff --git a/Assets/VRM10/Runtime/VRMConverter/UnityTextureUtil.cs.meta b/Assets/VRM10/Runtime/VRMConverter/UnityTextureUtil.cs.meta new file mode 100644 index 000000000..26e18cfdb --- /dev/null +++ b/Assets/VRM10/Runtime/VRMConverter/UnityTextureUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 09e01588153da3641a905b8f76d91568 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Version.meta b/Assets/VRM10/Runtime/Version.meta new file mode 100644 index 000000000..8e4ebc6c3 --- /dev/null +++ b/Assets/VRM10/Runtime/Version.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1324c667b838ac543a6780bd7e258c7b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Version/VRMSpecVersion.cs b/Assets/VRM10/Runtime/Version/VRMSpecVersion.cs new file mode 100644 index 000000000..7fe7d3c90 --- /dev/null +++ b/Assets/VRM10/Runtime/Version/VRMSpecVersion.cs @@ -0,0 +1,27 @@ +using System; + +namespace UniVRM10 +{ + /// + /// https://github.com/vrm-c/vrm-specification/tree/master/specification + /// + /// spec version として解釈できる git tag を運用するべきか。 + /// + /// コード生成を通して自動で更新する必要がある。 + /// + public class VRMSpecVersion + { + public const int Major = 1; + public const int Minor = 0; + + public static string Version + { + get + { + return String.Format("{0}.{1}.draft", Major, Minor); + } + } + + public const string VERSION = "1.0.draft"; + } +} diff --git a/Assets/VRM10/Runtime/Version/VRMSpecVersion.cs.meta b/Assets/VRM10/Runtime/Version/VRMSpecVersion.cs.meta new file mode 100644 index 000000000..6345e6eaf --- /dev/null +++ b/Assets/VRM10/Runtime/Version/VRMSpecVersion.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 34998e0a5a307d847a66bc4de26fbf33 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Version/VRMVersion.cs b/Assets/VRM10/Runtime/Version/VRMVersion.cs new file mode 100644 index 000000000..d1a33a915 --- /dev/null +++ b/Assets/VRM10/Runtime/Version/VRMVersion.cs @@ -0,0 +1,13 @@ + +namespace UniVRM10 +{ + public static partial class VRMVersion + { + public const int MAJOR = 1; + public const int MINOR = 0; + public const int PATCH = 0; + public const string PRE_ID = ""; + + public const string VERSION = "1.0.0.beta"; + } +} diff --git a/Assets/VRM10/Runtime/Version/VRMVersion.cs.meta b/Assets/VRM10/Runtime/Version/VRMVersion.cs.meta new file mode 100644 index 000000000..b107aec7b --- /dev/null +++ b/Assets/VRM10/Runtime/Version/VRMVersion.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b7339aecd19a8ec4fbe33ca20d8ef675 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Version/VRMVersionPartial.cs b/Assets/VRM10/Runtime/Version/VRMVersionPartial.cs new file mode 100644 index 000000000..045add381 --- /dev/null +++ b/Assets/VRM10/Runtime/Version/VRMVersionPartial.cs @@ -0,0 +1,109 @@ +using System; +using System.IO; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Json; +using System.Text.RegularExpressions; + +namespace UniVRM10 +{ + public static partial class VRMVersion + { + /// + /// Returns true if a passed version is newer than current UniVRM. + /// + /// + /// + public static bool IsNewer(string version) + { + if (string.IsNullOrEmpty(version)) + { + return false; + } + + var prefix = "UniVRM-"; + if (version.StartsWith(prefix)) + { + version = version.Substring(prefix.Length); + } + + return IsNewer(version, VERSION); + } + + public static bool IsNewer(string newer, string older) + { + Version newerVersion; + if (!ParseVersion(newer, out newerVersion)) + { + return false; + } + + Version olderVersion; + if (!ParseVersion(older, out olderVersion)) + { + return false; + } + + if (newerVersion.Major > olderVersion.Major) + { + return true; + } + + if (newerVersion.Minor > olderVersion.Minor) + { + return true; + } + + if (newerVersion.Patch > olderVersion.Patch) + { + return true; + } + + if (String.Compare(newerVersion.Pre, olderVersion.Pre) > 0) + { + return true; + } + + return false; + } + + private static readonly Regex VersionSpec = + new Regex(@"(?\d+)\.(?\d+)(\.(?\d+))?(-(?
[0-9A-Za-z-]+))?");
+
+        public static bool ParseVersion(string version, out Version v)
+        {
+            var match = VersionSpec.Match(version);
+            if (!match.Success)
+            {
+                v = new Version();
+                return false;
+            }
+
+            v = new Version();
+            try
+            {
+                v.Major = int.Parse(match.Groups["major"].Value);
+                v.Minor = int.Parse(match.Groups["minor"].Value);
+                v.Patch = match.Groups["patch"].Success ? int.Parse(match.Groups["patch"].Value) : 0;
+                v.Pre = match.Groups["pre"].Success ? match.Groups["pre"].Value : "";
+
+                return true;
+            }
+            catch (Exception)
+            {
+                return false;
+            }
+        }
+
+        public struct Version
+        {
+            public int Major;
+            public int Minor;
+            public int Patch;
+            public string Pre;
+        }
+
+        public const string VRM_VERSION = "UniVRM-" + VERSION;
+        public const string MENU = "VRM/" + VRM_VERSION;
+
+    }
+}
diff --git a/Assets/VRM10/Runtime/Version/VRMVersionPartial.cs.meta b/Assets/VRM10/Runtime/Version/VRMVersionPartial.cs.meta
new file mode 100644
index 000000000..4e7afa499
--- /dev/null
+++ b/Assets/VRM10/Runtime/Version/VRMVersionPartial.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: daece183c090e0b4aa9097151202c316
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/Tests.PlayMode.meta b/Assets/VRM10/Tests.PlayMode.meta
new file mode 100644
index 000000000..1997f7fec
--- /dev/null
+++ b/Assets/VRM10/Tests.PlayMode.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 16a99b9dfa782ad45ad9be86c078ca3f
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/Tests.PlayMode/ApiSampleTests.cs b/Assets/VRM10/Tests.PlayMode/ApiSampleTests.cs
new file mode 100644
index 000000000..3a2a6a527
--- /dev/null
+++ b/Assets/VRM10/Tests.PlayMode/ApiSampleTests.cs
@@ -0,0 +1,81 @@
+using System;
+using System.IO;
+using UnityEngine;
+using UnityEngine.TestTools;
+
+namespace UniVRM10.Test
+{
+    public class ApiSampleTests
+    {
+        VrmLib.Model ReadModel(string path)
+        {
+            var bytes = File.ReadAllBytes(path);
+
+            if (!VrmLib.Glb.TryParse(bytes, out VrmLib.Glb glb, out Exception ex))
+            {
+                Debug.LogError($"fail to Glb.TryParse: {path} => {ex}");
+                return null;
+            }
+
+            var model = UniVRM10.VrmLoader.CreateVrmModel(path);
+            return model;
+        }
+
+        ModelAsset BuildGameObject(VrmLib.Model model, bool showMesh)
+        {
+            var assets = RuntimeUnityBuilder.ToUnityAsset(model, showMesh);
+            UniVRM10.ComponentBuilder.Build10(model, assets);
+            return assets;
+        }
+
+        VrmLib.Model ToModel(UnityEngine.GameObject target)
+        {
+            var exporter = new UniVRM10.RuntimeVrmConverter();
+            var model = exporter.ToModelFrom10(target);
+            return model;
+        }
+
+        byte[] ToVrm10(VrmLib.Model model)
+        {
+            // 右手系に変換
+            VrmLib.ModelExtensionsForCoordinates.ConvertCoordinate(model, VrmLib.Coordinates.Gltf);
+            var bytes = UniVRM10.ModelExtensions.ToGlb(model);
+            return bytes;
+        }
+
+        [UnityTest]
+        public bool Sample()
+        {
+            var path = "Tests/Models/Alicia_vrm-1.00/AliciaSolid_vrm-1.00.vrm";
+            Debug.Log($"load: {path}");
+
+            // import
+            var srcModel = ReadModel(path);
+            Debug.Log(srcModel);
+
+            var asset = BuildGameObject(srcModel, false);
+            Debug.Log(asset);
+
+            // renderer setting
+            foreach (var render in asset.Renderers)
+            {
+                // show when RuntimeUnityBuilder.ToUnity(showMesh = false)
+                render.enabled = true;
+                // avoid culling
+                if (render is SkinnedMeshRenderer skinned)
+                {
+                    skinned.updateWhenOffscreen = true;
+                }
+            }
+
+            // export
+            var dstModel = ToModel(asset.Root);
+            Debug.Log(dstModel);
+
+            var vrmBytes = ToVrm10(dstModel);
+            Debug.Log($"export {vrmBytes.Length} bytes");
+
+            return true;
+        }
+    }
+}
diff --git a/Assets/VRM10/Tests.PlayMode/ApiSampleTests.cs.meta b/Assets/VRM10/Tests.PlayMode/ApiSampleTests.cs.meta
new file mode 100644
index 000000000..16861ae48
--- /dev/null
+++ b/Assets/VRM10/Tests.PlayMode/ApiSampleTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: c937dce0cf7760a4b9c26ea23c3e567c
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/Tests.PlayMode/MaterialTests.cs b/Assets/VRM10/Tests.PlayMode/MaterialTests.cs
new file mode 100644
index 000000000..a859550b6
--- /dev/null
+++ b/Assets/VRM10/Tests.PlayMode/MaterialTests.cs
@@ -0,0 +1,294 @@
+using System;
+using System.Collections;
+using System.IO;
+using System.Linq;
+using NUnit.Framework;
+using UnityEngine;
+using UnityEngine.TestTools;
+using UniVRM10;
+using VrmLib;
+using VrmLib.Diff;
+
+namespace UniVRM10.Test
+{
+    public class MaterialTests
+    {
+        const string _vrmPath = "Tests/Models/Alicia_vrm-1.00/AliciaSolid_vrm-1.00.vrm";
+
+        string[] _mtooSrgbTextureProperties = {
+            VrmLib.MToon.Utils.PropMainTex,
+            VrmLib.MToon.Utils.PropShadeTexture,
+            VrmLib.MToon.Utils.PropEmissionMap,
+            VrmLib.MToon.Utils.PropSphereAdd,
+            VrmLib.MToon.Utils.PropRimTexture,
+        };
+
+        string[] _mtoonLinearTextureProperties = {
+            VrmLib.MToon.Utils.PropBumpMap,
+            VrmLib.MToon.Utils.PropOutlineWidthTexture,
+            VrmLib.MToon.Utils.PropUvAnimMaskTexture
+        };
+
+        private ModelAsset ToUnity(string path)
+        {
+            var fi = new FileInfo(_vrmPath);
+            var bytes = File.ReadAllBytes(fi.FullName);
+            return ToUnity(bytes);
+        }
+
+        private ModelAsset ToUnity(byte[] bytes)
+        {
+            // Vrm => Model
+            var model = VrmLoader.CreateVrmModel(bytes, new FileInfo("tmp.vrm"));
+            model.RemoveSecondary();
+
+            return ToUnity(model);
+        }
+
+        private ModelAsset ToUnity(Model model)
+        {
+            // Model => Unity
+            var assets = RuntimeUnityBuilder.ToUnityAsset(model);
+            UniVRM10.ComponentBuilder.Build10(model, assets);
+            return assets;
+        }
+
+        private Model ToVrmModel(GameObject root)
+        {
+            var exporter = new UniVRM10.RuntimeVrmConverter();
+            var model = exporter.ToModelFrom10(root, root.GetComponent().Meta);
+
+            model.ConvertCoordinate(VrmLib.Coordinates.Gltf, ignoreVrm: false);
+            return model;
+        }
+
+        void EqualColor(Color color1, Color color2)
+        {
+            Assert.AreEqual(color1.r, color2.r, 0.001f);
+            Assert.AreEqual(color1.g, color2.g, 0.001f);
+            Assert.AreEqual(color1.b, color2.b, 0.001f);
+            Assert.AreEqual(color1.a, color2.a, 0.001f);
+        }
+
+        void EqualVector4(System.Numerics.Vector4 vec1, System.Numerics.Vector4 vec2)
+        {
+            Assert.AreEqual(vec1.X, vec2.X, 0.001f);
+            Assert.AreEqual(vec1.Y, vec2.Y, 0.001f);
+            Assert.AreEqual(vec1.Z, vec2.Z, 0.001f);
+            Assert.AreEqual(vec1.W, vec2.W, 0.001f);
+        }
+
+        #region Color
+
+        [UnityTest]
+        public IEnumerator ColorSpace_UnityBaseColorToLiner()
+        {
+            var assets = ToUnity(_vrmPath);
+            var srcMaterial = assets.Map.Materials.First();
+            var key = srcMaterial.Key;
+            var srcColor = new Color(0.5f, 0.5f, 0.5f, 0.5f);
+            var srcGammaColor = srcColor;
+            var srclinerColor = srcColor.linear;
+
+            srcMaterial.Value.color = srcColor;
+
+            var model = ToVrmModel(assets.Root);
+            var dstMaterial = model.Materials.First(x => x.Name == key.Name);
+
+            EqualColor(srclinerColor, dstMaterial.BaseColorFactor.RGBA.ToUnityColor());
+
+            yield return null;
+        }
+
+        [UnityTest]
+        public IEnumerator ColorSpace_GltfBaseColorToGamma()
+        {
+            var assets = ToUnity(_vrmPath);
+            var srcMaterial = assets.Map.Materials.First();
+            var key = srcMaterial.Key;
+            var srcColor = new Color(0.5f, 0.5f, 0.5f, 0.5f);
+            var srclinerColor = srcColor.ToVector4();
+            var srcGammaColor = srcColor.gamma.ToVector4();
+
+
+            var model = ToVrmModel(assets.Root);
+            var gltfMaterial = model.Materials.First(x => x.Name == key.Name);
+            gltfMaterial.BaseColorFactor = new LinearColor
+            {
+                RGBA = srclinerColor
+            };
+
+            var bytes = model.ToGlb();
+
+            var dstAssets = ToUnity(bytes);
+            var dstMaterial = dstAssets.Map.Materials.First(x => x.Value.name == key.Name);
+
+            EqualColor(srcGammaColor.ToUnityColor(), dstMaterial.Value.color);
+
+            yield return null;
+        }
+
+        [UnityTest]
+        public IEnumerator MToonUnityColorToGltf()
+        {
+            var assets = ToUnity(_vrmPath);
+            var srcMaterial = assets.Map.Materials.First();
+            var key = srcMaterial.Key;
+            var srcColor = new Color(0.5f, 0.5f, 0.5f, 0.5f);
+            var srcGammaColor = srcColor;
+            var srclinerColor = srcColor.linear;
+
+            srcMaterial.Value.SetColor(VrmLib.MToon.Utils.PropColor, srcColor);
+            srcMaterial.Value.SetColor(VrmLib.MToon.Utils.PropShadeColor, srcColor);
+            srcMaterial.Value.SetColor(VrmLib.MToon.Utils.PropEmissionColor, srcColor);
+            srcMaterial.Value.SetColor(VrmLib.MToon.Utils.PropRimColor, srcColor);
+            srcMaterial.Value.SetColor(VrmLib.MToon.Utils.PropOutlineColor, srcColor);
+
+            var model = ToVrmModel(assets.Root);
+            var dstMaterial = model.Materials.First(x => x.Name == key.Name) as VrmLib.MToonMaterial;
+
+            // sRGB
+            EqualColor(srclinerColor, dstMaterial.Definition.Color.LitColor.RGBA.ToUnityColor());
+            EqualColor(srclinerColor, dstMaterial.Definition.Color.ShadeColor.RGBA.ToUnityColor());
+            EqualColor(srclinerColor, dstMaterial.Definition.Outline.OutlineColor.RGBA.ToUnityColor());
+            // HDR Color
+            EqualColor(srcColor, dstMaterial.Definition.Emission.EmissionColor.RGBA.ToUnityColor());
+            EqualColor(srcColor, dstMaterial.Definition.Rim.RimColor.RGBA.ToUnityColor());
+
+            yield return null;
+        }
+
+        [UnityTest]
+        public IEnumerator MtoonGltfColorToUnity()
+        {
+            var assets = ToUnity(_vrmPath);
+            var srcMaterial = assets.Map.Materials.First();
+            var key = srcMaterial.Key;
+            var srcColor = new Color(0.5f, 0.5f, 0.5f, 0.5f);
+            var srclinerColor = srcColor.ToVector4();
+            var srcGammaColor = srcColor.gamma.ToVector4();
+
+            var model = ToVrmModel(assets.Root);
+            var gltfMaterial = model.Materials.First(x => x.Name == key.Name) as VrmLib.MToonMaterial;
+            gltfMaterial.Definition.Color.LitColor = new LinearColor { RGBA = srclinerColor };
+            gltfMaterial.Definition.Color.ShadeColor = new LinearColor { RGBA = srclinerColor };
+            gltfMaterial.Definition.Outline.OutlineColor = new LinearColor { RGBA = srclinerColor };
+            gltfMaterial.Definition.Emission.EmissionColor = new LinearColor { RGBA = srclinerColor };
+            gltfMaterial.Definition.Rim.RimColor = new LinearColor { RGBA = srclinerColor };
+
+            var bytes = model.ToGlb();
+
+            var dstAssets = ToUnity(bytes);
+            var dstMaterial = dstAssets.Map.Materials.First(x => x.Value.name == key.Name).Value;
+            // sRGB
+            EqualColor(srcGammaColor.ToUnityColor(), dstMaterial.GetColor(VrmLib.MToon.Utils.PropColor));
+            EqualColor(srcGammaColor.ToUnityColor(), dstMaterial.GetColor(VrmLib.MToon.Utils.PropShadeColor));
+            EqualColor(srcGammaColor.ToUnityColor(), dstMaterial.GetColor(VrmLib.MToon.Utils.PropOutlineColor));
+            // HDR Color
+            EqualColor(srcColor, dstMaterial.GetColor(VrmLib.MToon.Utils.PropEmissionColor));
+            EqualColor(srcColor, dstMaterial.GetColor(VrmLib.MToon.Utils.PropRimColor));
+
+            yield return null;
+        }
+        #endregion
+
+        #region Texture
+
+        Texture2D CreateMonoTexture(float mono, float alpha, bool isLinear)
+        {
+            Texture2D texture = new Texture2D(128, 128, TextureFormat.ARGB32, mipChain: false, linear: isLinear);
+            Color col = new Color(mono, mono, mono, alpha);
+            for (int y = 0; y < texture.height; y++)
+            {
+                for (int x = 0; x < texture.width; x++)
+                {
+                    texture.SetPixel(x, y, col);
+                }
+            }
+            texture.Apply();
+            return texture;
+        }
+
+        void EqualTextureColor(Texture2D texture, VrmLib.ImageTexture imageTexture, bool isLinear)
+        {
+            var srcColor = texture.GetPixel(0, 0);
+
+            var dstTexture = new Texture2D(2, 2, TextureFormat.ARGB32, mipChain: false, linear: isLinear);
+            dstTexture.LoadImage(imageTexture.Image.Bytes.ToArray());
+            var dstColor = dstTexture.GetPixel(0, 0);
+
+            Debug.LogFormat("src:{0}, dst{1}", srcColor, dstColor);
+            EqualColor(srcColor, dstColor);
+        }
+
+        [UnityTest]
+        public IEnumerator MToonTextureToGltf_BaseColorTexture()
+        {
+            var assets = ToUnity(_vrmPath);
+            var srcMaterial = assets.Map.Materials.First();
+            var key = srcMaterial.Key;
+            var srcSrgbTexture = CreateMonoTexture(0.5f, 0.5f, false);
+
+            srcMaterial.Value.SetTexture(VrmLib.MToon.Utils.PropMainTex, srcSrgbTexture);
+
+            var model = ToVrmModel(assets.Root);
+            var dstMaterial = model.Materials.First(x => x.Name == key.Name) as VrmLib.MToonMaterial;
+
+            var imageTexture = dstMaterial.Definition.Color.LitMultiplyTexture.Texture as VrmLib.ImageTexture;
+            EqualTextureColor(srcSrgbTexture, imageTexture, false);
+
+            yield return null;
+        }
+
+        [UnityTest]
+        public IEnumerator MToonTextureToGltf_OutlineWidthTexture()
+        {
+            var assets = ToUnity(_vrmPath);
+            var srcMaterial = assets.Map.Materials.First();
+            var key = srcMaterial.Key;
+            var srcLinearTexture = CreateMonoTexture(0.5f, 0.5f, true);
+
+            srcMaterial.Value.SetTexture(VrmLib.MToon.Utils.PropOutlineWidthTexture, srcLinearTexture);
+
+            var model = ToVrmModel(assets.Root);
+            var dstMaterial = model.Materials.First(x => x.Name == key.Name) as VrmLib.MToonMaterial;
+
+            var imageTexture = dstMaterial.Definition.Outline.OutlineWidthMultiplyTexture.Texture as VrmLib.ImageTexture;
+            EqualTextureColor(srcLinearTexture, imageTexture, true);
+
+            yield return null;
+        }
+
+        [UnityTest]
+        public IEnumerator GetOrCreateTextureTest()
+        {
+            var converter = new RuntimeVrmConverter();
+            var material = new UnityEngine.Material(Shader.Find("VRM/MToon"));
+            var srcLinearTexture = CreateMonoTexture(0.5f, 1.0f, true);
+            var srcSRGBTexture = CreateMonoTexture(0.5f, 1.0f, false);
+
+            {
+                material.SetTexture(VrmLib.MToon.Utils.PropOutlineWidthTexture, srcSRGBTexture);
+                var textureInfo = converter.GetOrCreateTexture(material, srcSRGBTexture, VrmLib.Texture.ColorSpaceTypes.Srgb, VrmLib.Texture.TextureTypes.Default);
+                var imageTexture = textureInfo.Texture as VrmLib.ImageTexture;
+                EqualTextureColor(srcSRGBTexture, imageTexture, false);
+            }
+
+            {
+                material.SetTexture(VrmLib.MToon.Utils.PropOutlineWidthTexture, srcLinearTexture);
+                var textureInfo = converter.GetOrCreateTexture(material, srcLinearTexture, VrmLib.Texture.ColorSpaceTypes.Linear, VrmLib.Texture.TextureTypes.Default);
+                var imageTexture = textureInfo.Texture as VrmLib.ImageTexture;
+                EqualTextureColor(srcLinearTexture, imageTexture, true);
+            }
+
+            yield return null;
+        }
+
+        [UnityTest]
+        public IEnumerator MToonTextureToUnity()
+        {
+            yield return null;
+        }
+        #endregion
+    }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/Tests.PlayMode/MaterialTests.cs.meta b/Assets/VRM10/Tests.PlayMode/MaterialTests.cs.meta
new file mode 100644
index 000000000..cbfcf0c1d
--- /dev/null
+++ b/Assets/VRM10/Tests.PlayMode/MaterialTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d3d4677022a540242809a2df0bc17c84
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/Tests.PlayMode/VRM10.Tests.PlayMode.asmdef b/Assets/VRM10/Tests.PlayMode/VRM10.Tests.PlayMode.asmdef
new file mode 100644
index 000000000..337fbe31c
--- /dev/null
+++ b/Assets/VRM10/Tests.PlayMode/VRM10.Tests.PlayMode.asmdef
@@ -0,0 +1,19 @@
+{
+    "name": "VRM10.Tests.PlayMode",
+    "references": [
+        "VrmLib",
+        "VRM10"
+    ],
+    "optionalUnityReferences": [
+        "TestAssemblies"
+    ],
+    "includePlatforms": [],
+    "excludePlatforms": [],
+    "allowUnsafeCode": false,
+    "overrideReferences": true,
+    "precompiledReferences": [],
+    "autoReferenced": false,
+    "defineConstraints": [
+        "UNITY_INCLUDE_TESTS"
+    ]
+}
\ No newline at end of file
diff --git a/Assets/VRM10/Tests.PlayMode/VRM10.Tests.PlayMode.asmdef.meta b/Assets/VRM10/Tests.PlayMode/VRM10.Tests.PlayMode.asmdef.meta
new file mode 100644
index 000000000..8a879822a
--- /dev/null
+++ b/Assets/VRM10/Tests.PlayMode/VRM10.Tests.PlayMode.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 0b089d14d258c9a4099ab210918e19b9
+AssemblyDefinitionImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/Tests.meta b/Assets/VRM10/Tests.meta
new file mode 100644
index 000000000..b65ccd843
--- /dev/null
+++ b/Assets/VRM10/Tests.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 34fff503c3487b140a29c3da6538d6f6
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/Tests/Resources.meta b/Assets/VRM10/Tests/Resources.meta
new file mode 100644
index 000000000..9dc585a90
--- /dev/null
+++ b/Assets/VRM10/Tests/Resources.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 8bde762829a19de49a51317236684982
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/Tests/Resources/TestMToon.mat b/Assets/VRM10/Tests/Resources/TestMToon.mat
new file mode 100644
index 000000000..fa3b36734
--- /dev/null
+++ b/Assets/VRM10/Tests/Resources/TestMToon.mat
@@ -0,0 +1,131 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!21 &2100000
+Material:
+  serializedVersion: 6
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_Name: TestMToon
+  m_Shader: {fileID: 4800000, guid: 1a97144e4ad27a04aafd70f7b915cedb, type: 3}
+  m_ShaderKeywords: 
+  m_LightmapFlags: 4
+  m_EnableInstancingVariants: 0
+  m_DoubleSidedGI: 0
+  m_CustomRenderQueue: -1
+  stringTagMap:
+    RenderType: Opaque
+  disabledShaderPasses: []
+  m_SavedProperties:
+    serializedVersion: 3
+    m_TexEnvs:
+    - _BumpMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _DetailAlbedoMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _DetailMask:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _DetailNormalMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _EmissionMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _MainTex:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _MetallicGlossMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _OcclusionMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _OutlineWidthTexture:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _ParallaxMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _ReceiveShadowTexture:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _RimTexture:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _ShadeTexture:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _ShadingGradeTexture:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _SphereAdd:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _UvAnimMaskTexture:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    m_Floats:
+    - _BlendMode: 0
+    - _BumpScale: 1
+    - _CullMode: 2
+    - _Cutoff: 0.5
+    - _DebugMode: 0
+    - _DetailNormalMapScale: 1
+    - _DstBlend: 0
+    - _GlossMapScale: 1
+    - _Glossiness: 0.5
+    - _GlossyReflections: 1
+    - _IndirectLightIntensity: 0.1
+    - _LightColorAttenuation: 0
+    - _MToonVersion: 32
+    - _Metallic: 0
+    - _Mode: 0
+    - _OcclusionStrength: 1
+    - _OutlineColorMode: 0
+    - _OutlineCullMode: 1
+    - _OutlineLightingMix: 1
+    - _OutlineScaledMaxDistance: 1
+    - _OutlineWidth: 0.5
+    - _OutlineWidthMode: 0
+    - _Parallax: 0.02
+    - _ReceiveShadowRate: 1
+    - _RimFresnelPower: 1
+    - _RimLift: 0
+    - _RimLightingMix: 0
+    - _ShadeShift: 0
+    - _ShadeToony: 0.9
+    - _ShadingGradeRate: 1
+    - _SmoothnessTextureChannel: 0
+    - _SpecularHighlights: 1
+    - _SrcBlend: 1
+    - _UVSec: 0
+    - _UvAnimRotation: 0
+    - _UvAnimScrollX: 0
+    - _UvAnimScrollY: 0
+    - _ZWrite: 1
+    m_Colors:
+    - _Color: {r: 1, g: 1, b: 1, a: 1}
+    - _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
+    - _OutlineColor: {r: 0, g: 0, b: 0, a: 1}
+    - _RimColor: {r: 0, g: 0, b: 0, a: 1}
+    - _ShadeColor: {r: 0.96999997, g: 0.81, b: 0.86, a: 1}
diff --git a/Assets/VRM10/Tests/Resources/TestMToon.mat.meta b/Assets/VRM10/Tests/Resources/TestMToon.mat.meta
new file mode 100644
index 000000000..97ca6f87f
--- /dev/null
+++ b/Assets/VRM10/Tests/Resources/TestMToon.mat.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 93bec9f866ef2fa4186c45f5b3868941
+NativeFormatImporter:
+  externalObjects: {}
+  mainObjectFileID: 0
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/Tests/Resources/TestStandard.mat b/Assets/VRM10/Tests/Resources/TestStandard.mat
new file mode 100644
index 000000000..09becda2b
--- /dev/null
+++ b/Assets/VRM10/Tests/Resources/TestStandard.mat
@@ -0,0 +1,77 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!21 &2100000
+Material:
+  serializedVersion: 6
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_Name: TestStandard
+  m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0}
+  m_ShaderKeywords: 
+  m_LightmapFlags: 4
+  m_EnableInstancingVariants: 0
+  m_DoubleSidedGI: 0
+  m_CustomRenderQueue: -1
+  stringTagMap: {}
+  disabledShaderPasses: []
+  m_SavedProperties:
+    serializedVersion: 3
+    m_TexEnvs:
+    - _BumpMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _DetailAlbedoMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _DetailMask:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _DetailNormalMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _EmissionMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _MainTex:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _MetallicGlossMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _OcclusionMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _ParallaxMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    m_Floats:
+    - _BumpScale: 1
+    - _Cutoff: 0.5
+    - _DetailNormalMapScale: 1
+    - _DstBlend: 0
+    - _GlossMapScale: 1
+    - _Glossiness: 0.5
+    - _GlossyReflections: 1
+    - _Metallic: 0
+    - _Mode: 0
+    - _OcclusionStrength: 1
+    - _Parallax: 0.02
+    - _SmoothnessTextureChannel: 0
+    - _SpecularHighlights: 1
+    - _SrcBlend: 1
+    - _UVSec: 0
+    - _ZWrite: 1
+    m_Colors:
+    - _Color: {r: 1, g: 1, b: 1, a: 1}
+    - _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
diff --git a/Assets/VRM10/Tests/Resources/TestStandard.mat.meta b/Assets/VRM10/Tests/Resources/TestStandard.mat.meta
new file mode 100644
index 000000000..939c50031
--- /dev/null
+++ b/Assets/VRM10/Tests/Resources/TestStandard.mat.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: cc57fbf5564e47d4b841435ced7e680d
+NativeFormatImporter:
+  externalObjects: {}
+  mainObjectFileID: 0
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/Tests/Resources/TestUniUnlit.mat b/Assets/VRM10/Tests/Resources/TestUniUnlit.mat
new file mode 100644
index 000000000..4f9469585
--- /dev/null
+++ b/Assets/VRM10/Tests/Resources/TestUniUnlit.mat
@@ -0,0 +1,81 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!21 &2100000
+Material:
+  serializedVersion: 6
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_Name: TestUniUnlit
+  m_Shader: {fileID: 4800000, guid: 8c17b56f4bf084c47872edcb95237e4a, type: 3}
+  m_ShaderKeywords: _ALPHATEST_ON
+  m_LightmapFlags: 4
+  m_EnableInstancingVariants: 0
+  m_DoubleSidedGI: 0
+  m_CustomRenderQueue: 2450
+  stringTagMap:
+    RenderType: TransparentCutout
+  disabledShaderPasses: []
+  m_SavedProperties:
+    serializedVersion: 3
+    m_TexEnvs:
+    - _BumpMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _DetailAlbedoMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _DetailMask:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _DetailNormalMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _EmissionMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _MainTex:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 0, y: 0}
+        m_Offset: {x: 0, y: 0}
+    - _MetallicGlossMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _OcclusionMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _ParallaxMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    m_Floats:
+    - _BlendMode: 1
+    - _BumpScale: 1
+    - _CullMode: 0
+    - _Cutoff: 0.3
+    - _DetailNormalMapScale: 1
+    - _DstBlend: 0
+    - _GlossMapScale: 1
+    - _Glossiness: 0.5
+    - _GlossyReflections: 1
+    - _Metallic: 0
+    - _Mode: 0
+    - _OcclusionStrength: 1
+    - _Parallax: 0.02
+    - _SmoothnessTextureChannel: 0
+    - _SpecularHighlights: 1
+    - _SrcBlend: 1
+    - _UVSec: 0
+    - _VColBlendMode: 0
+    - _ZWrite: 1
+    m_Colors:
+    - _Color: {r: 0.8301887, g: 0.25845498, b: 0.25845498, a: 1}
+    - _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
diff --git a/Assets/VRM10/Tests/Resources/TestUniUnlit.mat.meta b/Assets/VRM10/Tests/Resources/TestUniUnlit.mat.meta
new file mode 100644
index 000000000..00d13a418
--- /dev/null
+++ b/Assets/VRM10/Tests/Resources/TestUniUnlit.mat.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 9f4a2b6e842dd8e419d0440cbb3f272d
+NativeFormatImporter:
+  externalObjects: {}
+  mainObjectFileID: 0
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/Tests/Resources/TestUnlitColor.mat b/Assets/VRM10/Tests/Resources/TestUnlitColor.mat
new file mode 100644
index 000000000..94c0cbf4b
--- /dev/null
+++ b/Assets/VRM10/Tests/Resources/TestUnlitColor.mat
@@ -0,0 +1,80 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!21 &2100000
+Material:
+  serializedVersion: 6
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_Name: TestUnlitColor
+  m_Shader: {fileID: 10755, guid: 0000000000000000f000000000000000, type: 0}
+  m_ShaderKeywords: 
+  m_LightmapFlags: 4
+  m_EnableInstancingVariants: 0
+  m_DoubleSidedGI: 0
+  m_CustomRenderQueue: -1
+  stringTagMap: {}
+  disabledShaderPasses: []
+  m_SavedProperties:
+    serializedVersion: 3
+    m_TexEnvs:
+    - _BumpMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _DetailAlbedoMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _DetailMask:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _DetailNormalMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _EmissionMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _MainTex:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _MetallicGlossMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _OcclusionMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _ParallaxMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    m_Floats:
+    - _BlendMode: 0
+    - _BumpScale: 1
+    - _CullMode: 2
+    - _Cutoff: 0.5
+    - _DetailNormalMapScale: 1
+    - _DstBlend: 0
+    - _GlossMapScale: 1
+    - _Glossiness: 0.5
+    - _GlossyReflections: 1
+    - _Metallic: 0
+    - _Mode: 0
+    - _OcclusionStrength: 1
+    - _Parallax: 0.02
+    - _SmoothnessTextureChannel: 0
+    - _SpecularHighlights: 1
+    - _SrcBlend: 1
+    - _UVSec: 0
+    - _VColBlendMode: 0
+    - _ZWrite: 1
+    m_Colors:
+    - _Color: {r: 1, g: 1, b: 1, a: 1}
+    - _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
diff --git a/Assets/VRM10/Tests/Resources/TestUnlitColor.mat.meta b/Assets/VRM10/Tests/Resources/TestUnlitColor.mat.meta
new file mode 100644
index 000000000..0cb79d341
--- /dev/null
+++ b/Assets/VRM10/Tests/Resources/TestUnlitColor.mat.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: f7f4d0051179dea4abb0a5cc1e06868f
+NativeFormatImporter:
+  externalObjects: {}
+  mainObjectFileID: 0
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/Tests/Resources/TestUnlitCutout.mat b/Assets/VRM10/Tests/Resources/TestUnlitCutout.mat
new file mode 100644
index 000000000..b4e7b5296
--- /dev/null
+++ b/Assets/VRM10/Tests/Resources/TestUnlitCutout.mat
@@ -0,0 +1,80 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!21 &2100000
+Material:
+  serializedVersion: 6
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_Name: TestUnlitCutout
+  m_Shader: {fileID: 10755, guid: 0000000000000000f000000000000000, type: 0}
+  m_ShaderKeywords: 
+  m_LightmapFlags: 4
+  m_EnableInstancingVariants: 0
+  m_DoubleSidedGI: 0
+  m_CustomRenderQueue: -1
+  stringTagMap: {}
+  disabledShaderPasses: []
+  m_SavedProperties:
+    serializedVersion: 3
+    m_TexEnvs:
+    - _BumpMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _DetailAlbedoMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _DetailMask:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _DetailNormalMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _EmissionMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _MainTex:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _MetallicGlossMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _OcclusionMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _ParallaxMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    m_Floats:
+    - _BlendMode: 0
+    - _BumpScale: 1
+    - _CullMode: 2
+    - _Cutoff: 0.5
+    - _DetailNormalMapScale: 1
+    - _DstBlend: 0
+    - _GlossMapScale: 1
+    - _Glossiness: 0.5
+    - _GlossyReflections: 1
+    - _Metallic: 0
+    - _Mode: 0
+    - _OcclusionStrength: 1
+    - _Parallax: 0.02
+    - _SmoothnessTextureChannel: 0
+    - _SpecularHighlights: 1
+    - _SrcBlend: 1
+    - _UVSec: 0
+    - _VColBlendMode: 0
+    - _ZWrite: 1
+    m_Colors:
+    - _Color: {r: 1, g: 1, b: 1, a: 1}
+    - _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
diff --git a/Assets/VRM10/Tests/Resources/TestUnlitCutout.mat.meta b/Assets/VRM10/Tests/Resources/TestUnlitCutout.mat.meta
new file mode 100644
index 000000000..48bd52a47
--- /dev/null
+++ b/Assets/VRM10/Tests/Resources/TestUnlitCutout.mat.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 4fb31f9c7e0666b4bbfe0d6e3e8e147b
+NativeFormatImporter:
+  externalObjects: {}
+  mainObjectFileID: 0
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/Tests/Resources/TestUnlitTexture.mat b/Assets/VRM10/Tests/Resources/TestUnlitTexture.mat
new file mode 100644
index 000000000..575b6cf64
--- /dev/null
+++ b/Assets/VRM10/Tests/Resources/TestUnlitTexture.mat
@@ -0,0 +1,80 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!21 &2100000
+Material:
+  serializedVersion: 6
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_Name: TestUnlitTexture
+  m_Shader: {fileID: 10755, guid: 0000000000000000f000000000000000, type: 0}
+  m_ShaderKeywords: 
+  m_LightmapFlags: 4
+  m_EnableInstancingVariants: 0
+  m_DoubleSidedGI: 0
+  m_CustomRenderQueue: -1
+  stringTagMap: {}
+  disabledShaderPasses: []
+  m_SavedProperties:
+    serializedVersion: 3
+    m_TexEnvs:
+    - _BumpMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _DetailAlbedoMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _DetailMask:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _DetailNormalMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _EmissionMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _MainTex:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _MetallicGlossMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _OcclusionMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _ParallaxMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    m_Floats:
+    - _BlendMode: 0
+    - _BumpScale: 1
+    - _CullMode: 2
+    - _Cutoff: 0.5
+    - _DetailNormalMapScale: 1
+    - _DstBlend: 0
+    - _GlossMapScale: 1
+    - _Glossiness: 0.5
+    - _GlossyReflections: 1
+    - _Metallic: 0
+    - _Mode: 0
+    - _OcclusionStrength: 1
+    - _Parallax: 0.02
+    - _SmoothnessTextureChannel: 0
+    - _SpecularHighlights: 1
+    - _SrcBlend: 1
+    - _UVSec: 0
+    - _VColBlendMode: 0
+    - _ZWrite: 1
+    m_Colors:
+    - _Color: {r: 1, g: 1, b: 1, a: 1}
+    - _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
diff --git a/Assets/VRM10/Tests/Resources/TestUnlitTexture.mat.meta b/Assets/VRM10/Tests/Resources/TestUnlitTexture.mat.meta
new file mode 100644
index 000000000..c078c996f
--- /dev/null
+++ b/Assets/VRM10/Tests/Resources/TestUnlitTexture.mat.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 618c113d7622d0e4f9a08d1180ea4c18
+NativeFormatImporter:
+  externalObjects: {}
+  mainObjectFileID: 0
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/Tests/Resources/TestUnlitTransparent.mat b/Assets/VRM10/Tests/Resources/TestUnlitTransparent.mat
new file mode 100644
index 000000000..bca08f422
--- /dev/null
+++ b/Assets/VRM10/Tests/Resources/TestUnlitTransparent.mat
@@ -0,0 +1,80 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!21 &2100000
+Material:
+  serializedVersion: 6
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_Name: TestUnlitTransparent
+  m_Shader: {fileID: 10755, guid: 0000000000000000f000000000000000, type: 0}
+  m_ShaderKeywords: 
+  m_LightmapFlags: 4
+  m_EnableInstancingVariants: 0
+  m_DoubleSidedGI: 0
+  m_CustomRenderQueue: -1
+  stringTagMap: {}
+  disabledShaderPasses: []
+  m_SavedProperties:
+    serializedVersion: 3
+    m_TexEnvs:
+    - _BumpMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _DetailAlbedoMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _DetailMask:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _DetailNormalMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _EmissionMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _MainTex:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _MetallicGlossMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _OcclusionMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _ParallaxMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    m_Floats:
+    - _BlendMode: 0
+    - _BumpScale: 1
+    - _CullMode: 2
+    - _Cutoff: 0.5
+    - _DetailNormalMapScale: 1
+    - _DstBlend: 0
+    - _GlossMapScale: 1
+    - _Glossiness: 0.5
+    - _GlossyReflections: 1
+    - _Metallic: 0
+    - _Mode: 0
+    - _OcclusionStrength: 1
+    - _Parallax: 0.02
+    - _SmoothnessTextureChannel: 0
+    - _SpecularHighlights: 1
+    - _SrcBlend: 1
+    - _UVSec: 0
+    - _VColBlendMode: 0
+    - _ZWrite: 1
+    m_Colors:
+    - _Color: {r: 1, g: 1, b: 1, a: 1}
+    - _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
diff --git a/Assets/VRM10/Tests/Resources/TestUnlitTransparent.mat.meta b/Assets/VRM10/Tests/Resources/TestUnlitTransparent.mat.meta
new file mode 100644
index 000000000..d8d0201c5
--- /dev/null
+++ b/Assets/VRM10/Tests/Resources/TestUnlitTransparent.mat.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: ae4a2672b95a4b1469e195db2e3967ab
+NativeFormatImporter:
+  externalObjects: {}
+  mainObjectFileID: 0
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/Tests/SerializationTests.cs b/Assets/VRM10/Tests/SerializationTests.cs
new file mode 100644
index 000000000..15dc56374
--- /dev/null
+++ b/Assets/VRM10/Tests/SerializationTests.cs
@@ -0,0 +1,267 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using NUnit.Framework;
+using UniGLTF;
+using UniJSON;
+using UnityEditor;
+using UnityEngine;
+using VrmLib.Diff;
+using static UnityEditor.ShaderUtil;
+
+namespace UniVRM10
+{
+    public class SerializationTests
+    {
+        static string Serialize(T value, Action seri)
+        {
+            var f = new JsonFormatter();
+            seri(f, value);
+            var b = f.GetStoreBytes();
+            return Encoding.UTF8.GetString(b.Array, b.Offset, b.Count);
+        }
+
+        [Test]
+        public void MaterialTest()
+        {
+            var q = "\"";
+
+            {
+                var data = new UniGLTF.glTFMaterial
+                {
+                    name = "Some",
+                };
+
+                var json = Serialize(data, UniGLTF.GltfSerializer.Serialize_gltf_materials_ITEM);
+                Assert.AreEqual($"{{{q}name{q}:{q}Some{q},{q}pbrMetallicRoughness{q}:{{{q}baseColorFactor{q}:[1,1,1,1],{q}metallicFactor{q}:1,{q}roughnessFactor{q}:1}},{q}doubleSided{q}:false}}", json);
+            }
+
+            {
+                var data = new UniGLTF.glTF();
+                data.textures.Add(new UniGLTF.glTFTexture
+                {
+
+                });
+
+                var json = Serialize(data, UniGLTF.GltfSerializer.Serialize);
+                // Assert.Equal($"{{ {q}name{q}: {q}Some{q} }}", json);
+            }
+
+            {
+                var data = new UniGLTF.glTFMaterial
+                {
+                    name = "Alicia_body",
+                    pbrMetallicRoughness = new UniGLTF.glTFPbrMetallicRoughness
+                    {
+                        // BaseColorFactor = new[] { 1, 1, 1, 1 },
+                        // BaseColorTexture= { }, 
+                        metallicFactor = 0,
+                        roughnessFactor = 0.9f
+                    },
+                    alphaMode = "OPAQUE",
+                    alphaCutoff = 0.5f,
+                    extensions = new UniGLTF.glTFExtensionExport().Add(
+                        UniGLTF.glTF_KHR_materials_unlit.ExtensionName,
+                        new ArraySegment(UniGLTF.glTF_KHR_materials_unlit.Raw))
+                };
+
+                var json = Serialize(data, UniGLTF.GltfSerializer.Serialize_gltf_materials_ITEM);
+                // Assert.Equal($"{{ {q}name{q}: {q}Some{q} }}", json);
+            }
+        }
+
+        static (UniGLTF.glTFMaterial, bool) ToProtobufMaterial(VrmLib.Material vrmlibMaterial, List textures)
+        {
+            if (vrmlibMaterial is VrmLib.MToonMaterial mtoon)
+            {
+                // MToon
+                var protobufMaterial = UniVRM10.MToonAdapter.MToonToGltf(mtoon, textures);
+                return (protobufMaterial, true);
+            }
+            else if (vrmlibMaterial is VrmLib.UnlitMaterial unlit)
+            {
+                // Unlit
+                var protobufMaterial = UniVRM10.MaterialAdapter.UnlitToGltf(unlit, textures);
+                return (protobufMaterial, true);
+            }
+            else if (vrmlibMaterial is VrmLib.PBRMaterial pbr)
+            {
+                // PBR
+                var protobufMaterial = UniVRM10.MaterialAdapter.PBRToGltf(pbr, textures);
+                return (protobufMaterial, false);
+            }
+            else
+            {
+                throw new NotImplementedException();
+            }
+        }
+
+        static void CompareUnityMaterial(Material lhs, Material rhs)
+        {
+            Assert.AreEqual(lhs.name, rhs.name);
+            Assert.AreEqual(lhs.shader, rhs.shader);
+            var sb = new StringBuilder();
+            for (int i = 0; i < ShaderUtil.GetPropertyCount(lhs.shader); ++i)
+            {
+                var prop = ShaderUtil.GetPropertyName(lhs.shader, i);
+                if (s_ignoreProps.Contains(prop))
+                {
+                    continue;
+                }
+
+                switch (ShaderUtil.GetPropertyType(lhs.shader, i))
+                {
+                    case ShaderPropertyType.Color:
+                    case ShaderPropertyType.Vector:
+                        {
+                            var l = lhs.GetVector(prop);
+                            var r = rhs.GetVector(prop);
+                            if (l != r)
+                            {
+                                sb.AppendLine($"{prop} {l}!={r}");
+                            }
+                        }
+                        break;
+
+                    case ShaderPropertyType.Float:
+                    case ShaderPropertyType.Range:
+                        {
+                            var l = lhs.GetFloat(prop);
+                            var r = rhs.GetFloat(prop);
+                            if (l != r)
+                            {
+                                sb.AppendLine($"{prop} {l}!={r}");
+                            }
+                        }
+                        break;
+
+                    case ShaderPropertyType.TexEnv:
+                        {
+                            var l = lhs.GetTextureOffset(prop);
+                            var r = rhs.GetTextureOffset(prop);
+                            if (l != r)
+                            {
+                                sb.AppendLine($"{prop} {l}!={r}");
+                            }
+                        }
+                        break;
+
+                    default:
+                        throw new NotImplementedException(prop);
+                }
+            }
+            if (sb.Length > 0)
+            {
+                Debug.LogWarning(sb.ToString());
+            }
+            Assert.AreEqual(0, sb.Length);
+        }
+
+        static string[] s_ignoreKeys = new string[]
+        {
+            "(MToonMaterial).Definition.MetaDefinition.VersionNumber",
+        };
+
+        static string[] s_ignoreProps = new string[]
+        {
+            "_ReceiveShadowRate",
+            "_ShadingGradeRate",
+            "_MToonVersion",
+            "_Glossiness", // Gloss is burned into the texture and changed to the default value (1.0)
+        };
+
+        /// Unity material を export => import して元の material と一致するか
+        [Test]
+        [TestCase("TestMToon", typeof(VrmLib.MToonMaterial))]
+        [TestCase("TestUniUnlit", typeof(VrmLib.UnlitMaterial))]
+        [TestCase("TestStandard", typeof(VrmLib.PBRMaterial))]
+        [TestCase("TestUnlitColor", typeof(VrmLib.UnlitMaterial), false)]
+        [TestCase("TestUnlitTexture", typeof(VrmLib.UnlitMaterial), false)]
+        [TestCase("TestUnlitTransparent", typeof(VrmLib.UnlitMaterial), false)]
+        [TestCase("TestUnlitCutout", typeof(VrmLib.UnlitMaterial), false)]
+        public void UnityMaterialTest(string materialName, Type vrmLibMaterialType, bool sameShader = true)
+        {
+            // asset (cerate copy for avoid modify asset)
+            var src = new Material(Resources.Load(materialName));
+
+            // asset => vrmlib
+            var converter = new UniVRM10.RuntimeVrmConverter();
+            var vrmLibMaterial = converter.Export10(src, (a, b, c, d) => null);
+            Assert.AreEqual(vrmLibMaterialType, vrmLibMaterial.GetType());
+
+            // vrmlib => gltf
+            var textures = new List();
+            var (gltfMaterial, hasKhrUnlit) = ToProtobufMaterial(vrmLibMaterial, textures);
+            if (gltfMaterial.extensions != null)
+            {
+                gltfMaterial.extensions = gltfMaterial.extensions.Deserialize();
+            }
+            Assert.AreEqual(hasKhrUnlit, glTF_KHR_materials_unlit.IsEnable(gltfMaterial));
+
+            // gltf => json
+            var jsonMaterial = Serialize(gltfMaterial, UniGLTF.GltfSerializer.Serialize_gltf_materials_ITEM);
+
+            // gltf <= json
+            var deserialized = UniGLTF.GltfDeserializer.Deserialize_gltf_materials_LIST(jsonMaterial.ParseAsJson());
+
+            // vrmlib <= gltf
+            var loaded = deserialized.FromGltf(textures);
+            var context = ModelDiffContext.Create();
+            ModelDiffExtensions.MaterialEquals(context, vrmLibMaterial, loaded);
+            var diff = context.List
+            .Where(x => !s_ignoreKeys.Contains(x.Context))
+            .ToArray();
+            if (diff.Length > 0)
+            {
+                Debug.LogWarning(string.Join("\n", diff.Select(x => $"{x.Context}: {x.Message}")));
+            }
+            Assert.AreEqual(0, diff.Length);
+
+            // <= vrmlib
+            var map = new Dictionary();
+            var dst = UniVRM10.RuntimeUnityMaterialBuilder.CreateMaterialAsset(loaded, hasVertexColor: false, map);
+            dst.name = src.name;
+
+            if (sameShader)
+            {
+                CompareUnityMaterial(src, dst);
+            }
+        }
+
+        [Test]
+        public void ExpressionTest()
+        {
+            var q = "\"";
+
+            {
+                var data = new UniGLTF.Extensions.VRMC_vrm.Expression();
+                data.IgnoreBlink = true;
+
+                var json = Serialize(data, UniGLTF.Extensions.VRMC_vrm.GltfSerializer.Serialize_Expressions_ITEM);
+                Assert.AreEqual($"{{{q}preset{q}:{q}custom{q},{q}ignoreBlink{q}:true}}", json);
+            }
+
+            {
+                var expression = new VrmLib.Expression(VrmLib.ExpressionPreset.Blink, "blink", true)
+                {
+                    IgnoreBlink = true,
+                    IgnoreLookAt = true,
+                    IgnoreMouth = true,
+                };
+
+                // export
+                var gltf = UniVRM10.ExpressionAdapter.ToGltf(expression, new List(), new List());
+                Assert.AreEqual(true, gltf.IgnoreBlink);
+                Assert.AreEqual(true, gltf.IgnoreLookAt);
+                Assert.AreEqual(true, gltf.IgnoreMouth);
+
+                // import
+                var imported = UniVRM10.ExpressionAdapter.FromGltf(gltf, new List(), new List());
+                Assert.AreEqual(true, imported.IgnoreBlink);
+                Assert.AreEqual(true, imported.IgnoreLookAt);
+                Assert.AreEqual(true, imported.IgnoreMouth);
+            }
+        }
+    }
+}
diff --git a/Assets/VRM10/Tests/SerializationTests.cs.meta b/Assets/VRM10/Tests/SerializationTests.cs.meta
new file mode 100644
index 000000000..685a3987e
--- /dev/null
+++ b/Assets/VRM10/Tests/SerializationTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 4b12f97cab9de774ca44ae4061361529
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/Tests/TextureTests.cs b/Assets/VRM10/Tests/TextureTests.cs
new file mode 100644
index 000000000..6dd794175
--- /dev/null
+++ b/Assets/VRM10/Tests/TextureTests.cs
@@ -0,0 +1,171 @@
+using NUnit.Framework;
+using System.IO;
+using UnityEngine;
+using UnityEngine.Assertions;
+using Assert = NUnit.Framework.Assert;
+
+namespace UniVRM10
+{
+    public class TextureTests
+    {
+        [Test]
+        public void TextureExportTest()
+        {
+            //// Dummy texture
+            //var tex0 = new Texture2D(128, 128)
+            //{
+            //    wrapMode = TextureWrapMode.Clamp,
+            //    filterMode = FilterMode.Trilinear,
+            //};
+            //var textureManager = new TextureExportManager(new Texture[] {tex0});
+
+            //var material = new Material(Shader.Find("Standard"));
+            //material.mainTexture = tex0;
+
+            //var materialExporter = new MaterialExporter();
+            //materialExporter.ExportMaterial(material, textureManager);
+
+            //var convTex0 = textureManager.GetExportTexture(0);
+            //var sampler = TextureSamplerUtil.Export(convTex0);
+
+            //Assert.AreEqual(glWrap.CLAMP_TO_EDGE, sampler.wrapS);
+            //Assert.AreEqual(glWrap.CLAMP_TO_EDGE, sampler.wrapT);
+            //Assert.AreEqual(glFilter.LINEAR_MIPMAP_LINEAR, sampler.minFilter);
+            //Assert.AreEqual(glFilter.LINEAR_MIPMAP_LINEAR, sampler.magFilter);
+        }
+    }
+
+    public class MetallicRoughnessConverterTests
+    {
+        const float epsilon = 0.005f;
+        private static void EqualColor(Color color1, Color color2)
+        {
+            Assert.AreEqual(color1.r, color2.r, epsilon);
+            Assert.AreEqual(color1.g, color2.g, epsilon);
+            Assert.AreEqual(color1.b, color2.b, epsilon);
+            Assert.AreEqual(color1.a, color2.a, epsilon);
+        }
+
+        public static Texture2D CreateMonoTexture(Color color, bool isLinear)
+        {
+            var texture = new Texture2D(64, 64, TextureFormat.RGBA32, false, isLinear)
+            {
+                wrapMode = TextureWrapMode.Clamp,
+                filterMode = FilterMode.Trilinear,
+            };
+
+            Fill(texture, color);
+            return texture;
+        }
+
+        public static void Fill(Texture2D texture, Color color)
+        {
+            for (int y = 0; y < texture.height; ++y)
+            {
+                for (int x = 0; x < texture.width; ++x)
+                {
+                    texture.SetPixel(x, y, color);
+                }
+            }
+            texture.Apply();
+        }
+
+        public static Color GetColor(Texture2D texture)
+        {
+            return texture.GetPixel(0, 0);
+        }
+
+        [Test]
+        public void ExportingColorTest()
+        {
+
+            {
+                var smoothness = 1.0f;
+                var src = CreateMonoTexture(new UnityEngine.Color(1.0f, 1.0f, 1.0f, 1.0f), true);
+                var material = UniVRM10.TextureConvertMaterial.GetMetallicRoughnessUnityToGltf(smoothness);
+                var dst = UnityTextureUtil.CopyTexture(src, RenderTextureReadWrite.Linear, material);
+                // r <- 0   : (Unused)
+                // g <- 0   : ((1 - src.a(as float) * smoothness) ^ 2)(as uint8)
+                // b <- 255 : Same metallic (src.r)
+                // a <- 255 : (Unused)
+                EqualColor(GetColor(dst), new Color(0, 0, 1.0f, 1.0f));
+            }
+
+            {
+                var smoothness = 0.5f;
+                var src = CreateMonoTexture(new UnityEngine.Color(1.0f, 1.0f, 1.0f, 1.0f), true);
+                var material = UniVRM10.TextureConvertMaterial.GetMetallicRoughnessUnityToGltf(smoothness);
+                var dst = UnityTextureUtil.CopyTexture(src, RenderTextureReadWrite.Linear, material);
+                // r <- 0   : (Unused)
+                // g <- 63  : ((1 - src.a(as float) * smoothness) ^ 2)(as uint8)
+                // b <- 255 : Same metallic (src.r)
+                // a <- 255 : (Unused)
+                EqualColor(GetColor(dst), new Color(0, 0.25f, 1.0f, 1.0f));
+            }
+
+            {
+                var smoothness = 0.0f;
+                var src = CreateMonoTexture(new UnityEngine.Color(1.0f, 1.0f, 1.0f, 1.0f), true);
+                var material = UniVRM10.TextureConvertMaterial.GetMetallicRoughnessUnityToGltf(smoothness);
+                var dst = UnityTextureUtil.CopyTexture(src, RenderTextureReadWrite.Linear, material);
+                // r <- 0   : (Unused)
+                // g <- 255 : ((1 - src.a(as float) * smoothness) ^ 2)(as uint8)
+                // b <- 255 : Same metallic (src.r)
+                // a <- 255 : (Unused)
+                EqualColor(GetColor(dst), new Color(0, 1.0f, 1.0f, 1.0f));
+            }
+        }
+
+        [Test]
+        public void ImportingColorTest()
+        {
+            {
+                var roughnessFactor = 1.0f;
+                var src = CreateMonoTexture(new UnityEngine.Color(1.0f, 1.0f, 1.0f, 1.0f), true);
+                var material = UniVRM10.TextureConvertMaterial.GetMetallicRoughnessGltfToUnity(roughnessFactor);
+                var dst = UnityTextureUtil.CopyTexture(src, RenderTextureReadWrite.Linear, material);
+                // r <- 255 : Same metallic (src.r)
+                // g <- 0   : (Unused)
+                // b <- 0   : (Unused)
+                // a <- 0   : ((1 - sqrt(src.g(as float) * roughnessFactor)))(as uint8)
+                EqualColor(GetColor(dst), new Color(1.0f, 0, 0, 0));
+            }
+
+            {
+                var roughnessFactor = 1.0f;
+                var src = CreateMonoTexture(new UnityEngine.Color(1.0f, 0.25f, 1.0f, 1.0f), true);
+                var material = UniVRM10.TextureConvertMaterial.GetMetallicRoughnessGltfToUnity(roughnessFactor);
+                var dst = UnityTextureUtil.CopyTexture(src, RenderTextureReadWrite.Linear, material);
+                // r <- 255 : Same metallic (src.r)
+                // g <- 0   : (Unused)
+                // b <- 0   : (Unused)
+                // a <- 128 : ((1 - sqrt(src.g(as float) * roughnessFactor)))(as uint8)
+                EqualColor(GetColor(dst), new Color(1.0f, 0, 0, 0.5f));
+            }
+
+            {
+                var roughnessFactor = 0.5f;
+                var src = CreateMonoTexture(new UnityEngine.Color(1.0f, 1.0f, 1.0f, 1.0f), true);
+                var material = UniVRM10.TextureConvertMaterial.GetMetallicRoughnessGltfToUnity(roughnessFactor);
+                var dst = UnityTextureUtil.CopyTexture(src, RenderTextureReadWrite.Linear, material);
+                // r <- 255 : Same metallic (src.r)
+                // g <- 0   : (Unused)
+                // b <- 0   : (Unused)
+                // a <- 74 : ((1 - sqrt(src.g(as float) * roughnessFactor)))(as uint8)
+                EqualColor(GetColor(dst), new Color(1.0f, 0, 0, 0.29289f));
+            }
+
+            {
+                var roughnessFactor = 0.0f;
+                var src = CreateMonoTexture(new UnityEngine.Color(1.0f, 1.0f, 1.0f, 1.0f), true);
+                var material = UniVRM10.TextureConvertMaterial.GetMetallicRoughnessGltfToUnity(roughnessFactor);
+                var dst = UnityTextureUtil.CopyTexture(src, RenderTextureReadWrite.Linear, material);
+                // r <- 255 : Same metallic (src.r)
+                // g <- 0   : (Unused)
+                // b <- 0   : (Unused)
+                // a <- 255 : ((1 - sqrt(src.g(as float) * roughnessFactor)))(as uint8)
+                EqualColor(GetColor(dst), new Color(1.0f, 0, 0, 1.0f));
+            }
+        }
+    }
+}
diff --git a/Assets/VRM10/Tests/TextureTests.cs.meta b/Assets/VRM10/Tests/TextureTests.cs.meta
new file mode 100644
index 000000000..1fc373ed0
--- /dev/null
+++ b/Assets/VRM10/Tests/TextureTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 782661967221c594196e50532a9eef88
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/Tests/VRM10.Tests.asmdef b/Assets/VRM10/Tests/VRM10.Tests.asmdef
new file mode 100644
index 000000000..911a1bedb
--- /dev/null
+++ b/Assets/VRM10/Tests/VRM10.Tests.asmdef
@@ -0,0 +1,23 @@
+{
+    "name": "VRM10.Tests",
+    "references": [
+        "VrmLib",
+        "ShaderProperty.Runtime",
+        "VRM10",
+        "UniGLTF"
+    ],
+    "optionalUnityReferences": [
+        "TestAssemblies"
+    ],
+    "includePlatforms": [
+        "Editor"
+    ],
+    "excludePlatforms": [],
+    "allowUnsafeCode": false,
+    "overrideReferences": true,
+    "precompiledReferences": [],
+    "autoReferenced": true,
+    "defineConstraints": [
+        "UNITY_INCLUDE_TESTS"
+    ]
+}
\ No newline at end of file
diff --git a/Assets/VRM10/Tests/VRM10.Tests.asmdef.meta b/Assets/VRM10/Tests/VRM10.Tests.asmdef.meta
new file mode 100644
index 000000000..f1c979f86
--- /dev/null
+++ b/Assets/VRM10/Tests/VRM10.Tests.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 095fd7f56eebd62498fdadd5a60ed11b
+AssemblyDefinitionImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib.meta b/Assets/VRM10/vrmlib.meta
new file mode 100644
index 000000000..be3114e95
--- /dev/null
+++ b/Assets/VRM10/vrmlib.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: b19bf01847aebbb4c919173b7da4dc23
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/README.md b/Assets/VRM10/vrmlib/README.md
new file mode 100644
index 000000000..7087d712d
--- /dev/null
+++ b/Assets/VRM10/vrmlib/README.md
@@ -0,0 +1,3 @@
+# VrmLib
+
+A 3D model utility for VRM.
diff --git a/Assets/VRM10/vrmlib/README.md.meta b/Assets/VRM10/vrmlib/README.md.meta
new file mode 100644
index 000000000..92385aaf4
--- /dev/null
+++ b/Assets/VRM10/vrmlib/README.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: ada41fcec802ec7418eb2d39939fc9f5
+TextScriptImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime.meta b/Assets/VRM10/vrmlib/Runtime.meta
new file mode 100644
index 000000000..6d0185ab2
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 1cc5482841b076d469e32bef81ef4056
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Animation.cs b/Assets/VRM10/vrmlib/Runtime/Animation.cs
new file mode 100644
index 000000000..45ca00550
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Animation.cs
@@ -0,0 +1,325 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+
+
+namespace VrmLib
+{
+    /// 
+    /// ひと塊のアニメーション。
+    /// UnityのAnimationClip, Gltfのanimationに相当する
+    /// 
+    public class Animation : GltfId
+    {
+        public string Name;
+
+        public readonly Dictionary NodeMap = new Dictionary();
+
+        TimeSpan m_lastTime;
+        public TimeSpan Duration => m_lastTime;
+
+        int m_channels = 0;
+
+        public Animation(string name)
+        {
+            Name = name;
+        }
+
+        public override string ToString()
+        {
+            var sb = new System.Text.StringBuilder();
+            sb.Append(Name);
+            sb.Append($" {m_channels}channels {m_lastTime}sec");
+            return sb.ToString();
+        }
+
+        public NodeAnimation GetOrCreateNodeAnimation(Node node)
+        {
+            NodeAnimation nodeAnimation;
+            if (!NodeMap.TryGetValue(node, out nodeAnimation))
+            {
+                nodeAnimation = new NodeAnimation();
+                NodeMap.Add(node, nodeAnimation);
+            }
+            return nodeAnimation;
+        }
+
+        public void AddCurve(Node node, NodeAnimation nodeAnimation)
+        {
+            NodeMap.Add(node, nodeAnimation);
+            if (nodeAnimation.Duration > m_lastTime)
+            {
+                m_lastTime = nodeAnimation.Duration;
+            }
+        }
+
+        public void UpdateChannelsAndLastTime()
+        {
+            var lastTime = 0.0f;
+            m_channels = 0;
+            foreach (var animation in NodeMap.Values)
+            {
+                foreach (var curve in animation.Curves.Values)
+                {
+                    lastTime = Math.Max(lastTime, curve.LastTime);
+                    ++m_channels;
+                }
+            }
+
+            var duration = TimeSpan.FromSeconds(lastTime);
+            if (duration > m_lastTime)
+            {
+                m_lastTime = duration;
+            }
+        }
+
+        public void SetTime(TimeSpan elapsed)
+        {
+            // repeat
+            while (m_lastTime > TimeSpan.Zero && elapsed > m_lastTime)
+            {
+                elapsed -= m_lastTime;
+            }
+
+            foreach (var (node, animation) in NodeMap)
+            {
+                foreach (var (target, curve) in animation.Curves)
+                {
+                    switch (target)
+                    {
+                        case AnimationPathType.Translation:
+                            node.LocalTranslationWithoutUpdate = curve.GetVector3(elapsed);
+                            break;
+
+                        case AnimationPathType.Rotation:
+                            node.LocalRotationWithoutUpdate = curve.GetQuaternion(elapsed);
+                            break;
+
+                        case AnimationPathType.Scale:
+                            node.LocalScalingWithoutUpdate = curve.GetVector3(elapsed);
+                            break;
+
+                        case AnimationPathType.Weights:
+                            // TODO: morph target
+                            break;
+
+                        default:
+                            throw new NotImplementedException();
+                    }
+                }
+            }
+        }
+
+        /// 
+        /// ノードの参照するカーブとフレーム位置を指し示す
+        /// 
+        struct KeyFrameReference
+        {
+            public Node Node => KV.Key;
+            public NodeAnimation NodeAnimation => KV.Value;
+
+            public CurveSampler Rotation => NodeAnimation.Curves[AnimationPathType.Rotation];
+
+            public readonly KeyValuePair KV;
+            public readonly int Index;
+
+            public float Seconds
+            {
+                get
+                {
+                    var span = SpanLike.Wrap(Rotation.In.Bytes);
+                    if (Index < span.Length)
+                    {
+                        return span[Index];
+                    }
+                    return float.NaN;
+                }
+            }
+
+            public int Count => Rotation.In.Count;
+
+            public KeyFrameReference(KeyValuePair kv, int index)
+            {
+                KV = kv;
+                Index = index;
+            }
+        }
+
+        /// 
+        /// 同じ時間のキーフレームをまとめて列挙する
+        /// 
+        IEnumerable<(TimeSpan, IReadOnlyList)> KeyFramesGroupBySeconds()
+        {
+            Dictionary, int> curves = this.NodeMap.ToDictionary(kv => kv, kv => 0);
+
+            /// すべてのキーフレームを消費するまでループする
+            var list = new List();
+            while (curves.Any())
+            {
+                list.Clear();
+
+                var min = float.PositiveInfinity;
+                foreach (var (curve, index) in curves)
+                {
+                    var keyframe = new KeyFrameReference(curve, index);
+                    var seconds = keyframe.Seconds;
+                    if (seconds < min)
+                    {
+                        // 各カーブの先頭のキーフレームのうち時間が最小のものを得る
+                        min = seconds;
+                        list.Clear();
+                        list.Add(keyframe);
+                    }
+                    else if (seconds == min)
+                    {
+                        // 同じ時間の場合はリストに詰める
+                        list.Add(keyframe);
+                    }
+                }
+
+                // 最小時間のキーフレームを列挙する
+                yield return (TimeSpan.FromSeconds(min), list);
+
+                // 最小時間として列挙したキーフレームを消費する
+                foreach (var keyframe in list)
+                {
+                    if (keyframe.Index + 1 < keyframe.Count)
+                    {
+                        // next
+                        curves[keyframe.KV]++;
+                    }
+                    else
+                    {
+                        // remove
+                        curves.Remove(keyframe.KV);
+                    }
+                }
+            }
+        }
+
+        Node m_root;
+        Node Root
+        {
+            get
+            {
+                if (m_root == null)
+                {
+                    var keys = NodeMap.Keys;
+                    foreach (var key in keys)
+                    {
+                        if (!key.Ancestors().Intersect(keys).Any())
+                        {
+                            m_root = key;
+                            break;
+                        }
+                    }
+                }
+                return m_root;
+            }
+        }
+
+        /// 
+        /// モーションの基本姿勢を basePose ベースに再計算する
+        ///
+        /// basePose は Humanoid.CopyNodes が必用 !
+        /// 
+        public Animation RebaseAnimation(Humanoid basePose)
+        {
+            var map = NodeMap.ToDictionary(kv => kv.Key, kv => new List());
+            var hipsPositions = new List();
+
+            foreach (var (seconds, keyframes) in KeyFramesGroupBySeconds())
+            {
+                // モーション適用
+                SetTime(seconds);
+                Root.CalcWorldMatrix();
+
+                foreach (var keyframe in keyframes)
+                {
+                    if (!keyframe.Node.HumanoidBone.HasValue
+                    || keyframe.Node.HumanoidBone.Value == HumanoidBones.unknown)
+                    {
+                        continue;
+                    }
+
+                    // ローカル回転を算出する
+                    var t = basePose[keyframe.Node].Rotation;
+                    var w = keyframe.Node.Rotation;
+                    var w_from_t = w * Quaternion.Inverse(t);
+
+                    // parent
+                    var key = keyframe.Node.HumanoidBone.Value;
+                    var curve = map[keyframe.Node];
+                    if (key != HumanoidBones.hips)
+                    {
+                        if (basePose[key].Parent == null)
+                        {
+                            throw new Exception();
+                        }
+                        var parent_t = basePose[key].Parent.Rotation;
+                        var parent_w = keyframe.Node.Parent.Rotation;
+                        var parent_w_from_t = parent_w * Quaternion.Inverse(parent_t);
+
+                        var r = Quaternion.Inverse(parent_w_from_t) * w_from_t;
+                        curve.Add(r);
+                    }
+                    else
+                    {
+                        // hips
+                        curve.Add(w_from_t);
+                        hipsPositions.Add(keyframe.Node.Translation);
+                    }
+                }
+            }
+
+            var dst = new Animation(Name + ".tpose");
+            foreach (var kv in map)
+            {
+                if (!kv.Value.Any())
+                {
+                    continue;
+                }
+
+                var bone = kv.Key.HumanoidBone.Value;
+
+                var inCurve = NodeMap[kv.Key].Curves[AnimationPathType.Rotation].In;
+                if (inCurve.Count != kv.Value.Count)
+                {
+                    throw new Exception();
+                }
+
+                var nodeAnimation = new NodeAnimation();
+                nodeAnimation.Curves.Add(AnimationPathType.Rotation, new CurveSampler
+                {
+                    In = inCurve,
+                    Out = BufferAccessor.Create(kv.Value.ToArray()),
+                });
+                if (bone == HumanoidBones.hips)
+                {
+                    nodeAnimation.Curves.Add(AnimationPathType.Translation, new CurveSampler
+                    {
+                        In = inCurve,
+                        Out = BufferAccessor.Create(hipsPositions.ToArray()),
+                    });
+                }
+                dst.AddCurve(kv.Key, nodeAnimation);
+            }
+            return dst;
+        }
+
+        /// 
+        /// 指定された数のフレームを先頭から取り除く
+        /// 
+        public void SkipFrame(int skipFrames)
+        {
+            foreach (var kv in NodeMap)
+            {
+                foreach (var curve in kv.Value.Curves)
+                {
+                    curve.Value.SkipFrame(skipFrames);
+                }
+            }
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/Animation.cs.meta b/Assets/VRM10/vrmlib/Runtime/Animation.cs.meta
new file mode 100644
index 000000000..47dbe14ab
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Animation.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 013e4c2b6af3dc540b17099174655fe0
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/ArrayPin.cs b/Assets/VRM10/vrmlib/Runtime/ArrayPin.cs
new file mode 100644
index 000000000..83e14397f
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ArrayPin.cs
@@ -0,0 +1,118 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace VrmLib
+{
+    public static class ArrayPin
+    {
+        public static ArrayPin Create(ArraySegment src) where T : struct
+        {
+            return new ArrayPin(src);
+        }
+        public static ArrayPin Create(T[] src) where T : struct
+        {
+            return Create(new ArraySegment(src));
+        }
+    }
+
+    public class ArrayPin : IDisposable
+        where T : struct
+    {
+        GCHandle m_pinnedArray;
+
+        ArraySegment m_src;
+
+        public int Length
+        {
+            get
+            {
+                return m_src.Count * Marshal.SizeOf(typeof(T));
+            }
+        }
+
+        public ArrayPin(ArraySegment src)
+        {
+            m_src = src;
+            m_pinnedArray = GCHandle.Alloc(src.Array, GCHandleType.Pinned);
+        }
+
+        public IntPtr Ptr
+        {
+            get
+            {
+                var ptr = m_pinnedArray.AddrOfPinnedObject();
+                return new IntPtr(ptr.ToInt64() + m_src.Offset);
+            }
+        }
+
+        #region IDisposable Support
+        private bool disposedValue = false; // 重複する呼び出しを検出するには
+
+        protected virtual void Dispose(bool disposing)
+        {
+            if (!disposedValue)
+            {
+                if (disposing)
+                {
+                    // TODO: マネージ状態を破棄します (マネージ オブジェクト)。
+                }
+
+                // TODO: アンマネージ リソース (アンマネージ オブジェクト) を解放し、下のファイナライザーをオーバーライドします。
+                // TODO: 大きなフィールドを null に設定します。
+                if (m_pinnedArray.IsAllocated)
+                {
+                    m_pinnedArray.Free();
+                }
+
+                disposedValue = true;
+            }
+        }
+
+        // TODO: 上の Dispose(bool disposing) にアンマネージ リソースを解放するコードが含まれる場合にのみ、ファイナライザーをオーバーライドします。
+        // ~Pin() {
+        //   // このコードを変更しないでください。クリーンアップ コードを上の Dispose(bool disposing) に記述します。
+        //   Dispose(false);
+        // }
+
+        // このコードは、破棄可能なパターンを正しく実装できるように追加されました。
+        public void Dispose()
+        {
+            // このコードを変更しないでください。クリーンアップ コードを上の Dispose(bool disposing) に記述します。
+            Dispose(true);
+            // TODO: 上のファイナライザーがオーバーライドされる場合は、次の行のコメントを解除してください。
+            // GC.SuppressFinalize(this);
+        }
+        #endregion
+    }
+
+    public static class ArrayExtensions
+    {
+        public static int FromBytes(this ArraySegment src, T[] dst) where T : struct
+        {
+            var dstSize = dst.Length * Marshal.SizeOf(typeof(T));
+            if (src.Count > dstSize)
+            {
+                throw new ArgumentOutOfRangeException();
+            }
+            using (var pin = ArrayPin.Create(dst))
+            {
+                Marshal.Copy(src.Array, src.Offset, pin.Ptr, src.Count);
+            }
+            return src.Count;
+        }
+
+        public static int ToBytes(this T[] src, ArraySegment dst) where T : struct
+        {
+            var srcSize = src.Length * Marshal.SizeOf(typeof(T));
+            if (srcSize > dst.Count)
+            {
+                throw new ArgumentOutOfRangeException();
+            }
+            using (var pin = ArrayPin.Create(src))
+            {
+                Marshal.Copy(pin.Ptr, dst.Array, dst.Offset, srcSize);
+            }
+            return srcSize;
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/ArrayPin.cs.meta b/Assets/VRM10/vrmlib/Runtime/ArrayPin.cs.meta
new file mode 100644
index 000000000..ba3648e26
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ArrayPin.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: c431387eb7732aa4fb43d54385c8e0dc
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/BufferAccessor.cs b/Assets/VRM10/vrmlib/Runtime/BufferAccessor.cs
new file mode 100644
index 000000000..52a369592
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/BufferAccessor.cs
@@ -0,0 +1,464 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Runtime.InteropServices;
+
+namespace VrmLib
+{
+    public enum AccessorVectorType
+    {
+        SCALAR,
+        VEC2,
+        VEC3,
+        VEC4,
+        MAT2,
+        MAT3,
+        MAT4,
+    }
+
+    public static class GltfAccessorTypeExtensions
+    {
+        public static int TypeCount(this AccessorVectorType type)
+        {
+            switch (type)
+            {
+                case AccessorVectorType.SCALAR:
+                    return 1;
+                case AccessorVectorType.VEC2:
+                    return 2;
+                case AccessorVectorType.VEC3:
+                    return 3;
+                case AccessorVectorType.VEC4:
+                case AccessorVectorType.MAT2:
+                    return 4;
+                case AccessorVectorType.MAT3:
+                    return 9;
+                case AccessorVectorType.MAT4:
+                    return 16;
+                default:
+                    throw new NotImplementedException();
+            }
+        }
+    }
+
+    public enum AccessorValueType : int
+    {
+        BYTE = 5120, // signed ?
+        UNSIGNED_BYTE = 5121,
+
+        SHORT = 5122,
+        UNSIGNED_SHORT = 5123,
+
+        //INT = 5124,
+        UNSIGNED_INT = 5125,
+
+        FLOAT = 5126,
+    }
+
+    public static class GltfComponentTypeExtensions
+    {
+        public static int ByteSize(this AccessorValueType t)
+        {
+            switch (t)
+            {
+                case AccessorValueType.BYTE: return 1;
+                case AccessorValueType.UNSIGNED_BYTE: return 4;
+                case AccessorValueType.SHORT: return 2;
+                case AccessorValueType.UNSIGNED_SHORT: return 2;
+                case AccessorValueType.UNSIGNED_INT: return 4;
+                case AccessorValueType.FLOAT: return 4;
+                default: throw new ArgumentException();
+            }
+        }
+    }
+
+
+    public class BufferAccessor
+    {
+        public ArraySegment Bytes;
+
+        public AccessorValueType ComponentType;
+
+        public AccessorVectorType AccessorType;
+
+        public int Stride => ComponentType.ByteSize() * AccessorType.TypeCount();
+
+        public int Count;
+
+        public int ByteLength => Stride * Count;
+
+        public override string ToString()
+        {
+            return $"{Stride}stride x{Count}";
+        }
+
+        public SpanLike GetSpan(bool checkStride = true) where T : struct
+        {
+            if (checkStride && Marshal.SizeOf(typeof(T)) != Stride)
+            {
+                throw new Exception("different sizeof(T) with stride");
+            }
+            return SpanLike.Wrap(Bytes);
+        }
+
+        public void Assign(T[] values) where T : struct
+        {
+            if (Marshal.SizeOf(typeof(T)) != Stride)
+            {
+                throw new Exception("invalid element size");
+            }
+            var array = new byte[Stride * values.Length];
+            Bytes = new ArraySegment(array);
+            values.ToBytes(Bytes);
+            Count = values.Length;
+        }
+
+        public void Assign(SpanLike values) where T : struct
+        {
+            if (Marshal.SizeOf(typeof(T)) != Stride)
+            {
+                throw new Exception("invalid element size");
+            }
+            Bytes = values.Bytes;
+            Count = values.Length;
+        }
+
+        // for index buffer
+        public void AssignAsShort(SpanLike values)
+        {
+            if (AccessorType != AccessorVectorType.SCALAR)
+            {
+                throw new NotImplementedException();
+            }
+            ComponentType = AccessorValueType.UNSIGNED_SHORT;
+
+            Bytes = new ArraySegment(new byte[Stride * values.Length]);
+            var span = GetSpan();
+            Count = values.Length;
+            for (int i = 0; i < values.Length; ++i)
+            {
+                span[i] = (ushort)values[i];
+            }
+        }
+
+        // Index用
+        public int[] GetAsIntArray()
+        {
+            if (AccessorType != AccessorVectorType.SCALAR)
+            {
+                throw new InvalidOperationException("not scalar");
+            }
+            switch (ComponentType)
+            {
+                case AccessorValueType.UNSIGNED_SHORT:
+                    {
+                        var span = SpanLike.Wrap(Bytes);
+                        var array = new int[span.Length];
+                        for (int i = 0; i < span.Length; ++i)
+                        {
+                            array[i] = span[i];
+                        }
+                        return array;
+                    }
+
+                case AccessorValueType.UNSIGNED_INT:
+                    return SpanLike.Wrap(Bytes).ToArray();
+
+                default:
+                    throw new NotImplementedException();
+            }
+        }
+
+        public List GetAsIntList()
+        {
+            if (AccessorType != AccessorVectorType.SCALAR)
+            {
+                throw new InvalidOperationException("not scalar");
+            }
+            switch (ComponentType)
+            {
+                case AccessorValueType.UNSIGNED_SHORT:
+                    {
+                        var span = SpanLike.Wrap(Bytes);
+                        var array = new List(Count);
+                        if (span.Length != Count)
+                        {
+                            for (int i = 0; i < Count; ++i)
+                            {
+                                array.Add(span[i]);
+                            }
+                        }
+                        else
+                        {
+                            // Spanが動かない?WorkAround
+                            var bytes = Bytes.ToArray();
+                            var offset = 0;
+                            for (int i = 0; i < Count; ++i)
+                            {
+                                array.Add(BitConverter.ToUInt16(bytes, offset));
+                                offset += 2;
+                            }
+                        }
+                        return array;
+                    }
+
+                case AccessorValueType.UNSIGNED_INT:
+                    return SpanLike.Wrap(Bytes).ToArray().ToList();
+
+                default:
+                    throw new NotImplementedException();
+            }
+        }
+
+        // Joints用
+        public UShort4[] GetAsUShort4()
+        {
+            if (AccessorType != AccessorVectorType.VEC4)
+            {
+                throw new InvalidOperationException("not vec4");
+            }
+            switch (ComponentType)
+            {
+                case AccessorValueType.UNSIGNED_SHORT:
+                    return SpanLike.Wrap(Bytes).ToArray();
+
+                case AccessorValueType.UNSIGNED_BYTE:
+                    {
+                        var array = new UShort4[Count];
+                        var span = SpanLike.Wrap(Bytes);
+                        for (int i = 0; i < span.Length; ++i)
+                        {
+                            array[i].X = span[i].X;
+                            array[i].Y = span[i].Y;
+                            array[i].Z = span[i].Z;
+                            array[i].W = span[i].W;
+                        }
+                        return array;
+                    }
+
+                default:
+                    throw new NotImplementedException();
+            }
+        }
+
+        // Weigt用
+        public Vector4[] GetAsVector4()
+        {
+            if (AccessorType != AccessorVectorType.VEC4)
+            {
+                throw new InvalidOperationException("not vec4");
+            }
+            switch (ComponentType)
+            {
+                case AccessorValueType.FLOAT:
+                    return SpanLike.Wrap(Bytes).ToArray();
+
+                default:
+                    throw new NotImplementedException();
+            }
+        }
+
+        public void Resize(int count)
+        {
+            if (count < Count)
+            {
+                throw new Exception();
+            }
+            ToByteLength(Stride * count);
+
+            Count = count;
+        }
+
+        void ToByteLength(int byteLength)
+        {
+            var newBytes = new byte[byteLength];
+            Buffer.BlockCopy(Bytes.Array, Bytes.Offset, newBytes, 0, Bytes.Count);
+            Bytes = new ArraySegment(newBytes);
+        }
+
+        public void Extend(int count)
+        {
+            var oldLength = Bytes.Count;
+            ToByteLength(oldLength + Stride * count);
+            Count += count;
+        }
+
+        //
+        // ArraySegment を新規に確保して置き換える
+        //
+        public void Append(BufferAccessor a, int offset = -1)
+        {
+            if (AccessorType != a.AccessorType)
+            {
+                System.Console.WriteLine(AccessorType.ToString() + "!=" + a.AccessorType.ToString());
+                throw new Exception("different AccessorType");
+            }
+
+            // UNSIGNED_SHORT <-> UNSIGNED_INT の変換を許容して処理を続行
+            // 統合メッシュのprimitiveのIndexBufferが65,535(ushort.MaxValue)を超える場合や、変換前にindexBuffer.ComponetTypeがushortとuint混在する場合など
+            if (ComponentType != a.ComponentType)
+            {
+                switch (a.ComponentType)
+                {
+                    //ushort to uint
+                    case AccessorValueType.UNSIGNED_SHORT:
+                        {
+                            var src = SpanLike.Wrap(a.Bytes).Slice(0, a.Count);
+                            var bytes = new byte[src.Length * 4];
+                            var dst = SpanLike.Wrap(new ArraySegment(bytes));
+                            for (int i = 0; i < src.Length; ++i)
+                            {
+                                dst[i] = (uint)src[i];
+                            }
+                            var accessor = new BufferAccessor(new ArraySegment(bytes), AccessorValueType.UNSIGNED_INT, AccessorVectorType.SCALAR, a.Count);
+                            a = accessor;
+
+                            break;
+                        }
+
+                    //uint to ushort (おそらく通ることはない)
+                    case AccessorValueType.UNSIGNED_INT:
+                        {
+                            var src = SpanLike.Wrap(a.Bytes).Slice(0, a.Count);
+                            var bytes = new byte[src.Length * 2];
+                            var dst = SpanLike.Wrap(new ArraySegment(bytes));
+                            for (int i = 0; i < src.Length; ++i)
+                            {
+                                dst[i] = (ushort)src[i];
+                            }
+                            var accessor = new BufferAccessor(new ArraySegment(bytes), ComponentType, AccessorVectorType.SCALAR, a.Count);
+                            a = accessor;
+                            break;
+                        }
+
+                    default:
+                        throw new Exception("Cannot Convert ComponentType");
+
+                }
+            }
+
+            // 連結した新しいバッファを確保
+            var oldLength = Bytes.Count;
+            ToByteLength(oldLength + a.Bytes.Count);
+            // 後ろにコピー
+            Buffer.BlockCopy(a.Bytes.Array, a.Bytes.Offset, Bytes.Array, Bytes.Offset + oldLength, a.Bytes.Count);
+            Count += a.Count;
+
+            if (offset > 0)
+            {
+                // 後半にoffsetを足す
+                switch (ComponentType)
+                {
+                    case AccessorValueType.UNSIGNED_SHORT:
+                        {
+                            var span = SpanLike.Wrap(Bytes.Slice(oldLength));
+                            var ushortOffset = (ushort)offset;
+                            for (int i = 0; i < span.Length; ++i)
+                            {
+                                span[i] += ushortOffset;
+                            }
+                        }
+                        break;
+
+                    case AccessorValueType.UNSIGNED_INT:
+                        {
+                            var span = SpanLike.Wrap(Bytes.Slice(oldLength));
+                            var uintOffset = (uint)offset;
+                            for (int i = 0; i < span.Length; ++i)
+                            {
+                                span[i] += uintOffset;
+                            }
+                        }
+                        break;
+
+                    default:
+                        throw new NotImplementedException();
+                }
+            }
+        }
+
+        public BufferAccessor Skip(int skipFrames)
+        {
+            skipFrames = Math.Min(Count, skipFrames);
+            if (skipFrames == 0)
+            {
+                return this;
+            }
+
+            return new BufferAccessor(Bytes.Slice(Stride * skipFrames), ComponentType, AccessorType, Count - skipFrames);
+        }
+
+        public BufferAccessor CloneWithOffset(int offsetCount)
+        {
+            var offsetSize = Stride * offsetCount;
+            var buffer = new byte[offsetSize + Bytes.Count];
+
+            Buffer.BlockCopy(Bytes.Array, Bytes.Offset, buffer, offsetSize, Bytes.Count);
+
+            return new BufferAccessor(new ArraySegment(buffer), ComponentType, AccessorType, Count + offsetCount);
+        }
+
+        public BufferAccessor(ArraySegment bytes, AccessorValueType componentType, AccessorVectorType accessorType, int count)
+        {
+            Bytes = bytes;
+            ComponentType = componentType;
+            AccessorType = accessorType;
+            Count = count;
+        }
+
+        public static BufferAccessor Create(T[] list) where T : struct
+        {
+            var t = typeof(T);
+            var bytes = new byte[list.Length * Marshal.SizeOf(t)];
+            var span = SpanLike.Wrap(new ArraySegment(bytes));
+            for (int i = 0; i < list.Length; ++i)
+            {
+                span[i] = list[i];
+            }
+            AccessorValueType componentType = default(AccessorValueType);
+            AccessorVectorType accessorType = default(AccessorVectorType);
+            if (t == typeof(Vector2))
+            {
+                componentType = AccessorValueType.FLOAT;
+                accessorType = AccessorVectorType.VEC2;
+            }
+            else if (t == typeof(Vector3))
+            {
+                componentType = AccessorValueType.FLOAT;
+                accessorType = AccessorVectorType.VEC3;
+            }
+            else if (t == typeof(Vector4))
+            {
+                componentType = AccessorValueType.FLOAT;
+                accessorType = AccessorVectorType.VEC4;
+            }
+            else if (t == typeof(Quaternion))
+            {
+                componentType = AccessorValueType.FLOAT;
+                accessorType = AccessorVectorType.VEC4;
+            }
+            else if (t == typeof(SkinJoints))
+            {
+                componentType = AccessorValueType.UNSIGNED_SHORT;
+                accessorType = AccessorVectorType.VEC4;
+            }
+            else if (t == typeof(int))
+            {
+                componentType = AccessorValueType.UNSIGNED_INT;
+                accessorType = AccessorVectorType.SCALAR;
+            }
+            else
+            {
+                throw new NotImplementedException();
+            }
+            return new BufferAccessor(
+                new ArraySegment(bytes), componentType, accessorType, list.Length);
+        }
+
+        public void AddTo(Dictionary dict, string key)
+        {
+            dict.Add(key, this);
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/BufferAccessor.cs.meta b/Assets/VRM10/vrmlib/Runtime/BufferAccessor.cs.meta
new file mode 100644
index 000000000..2162bb5a6
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/BufferAccessor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 27cdb60e5e9634743875e0655372e682
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Bvh.meta b/Assets/VRM10/vrmlib/Runtime/Bvh.meta
new file mode 100644
index 000000000..0a55d43bc
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Bvh.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: ac2c48555929afa47903182006ab82d2
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Bvh/Bvh.cs b/Assets/VRM10/vrmlib/Runtime/Bvh/Bvh.cs
new file mode 100644
index 000000000..c140b33fb
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Bvh/Bvh.cs
@@ -0,0 +1,360 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Numerics;
+
+namespace VrmLib.Bvh
+{
+    public class BvhException : Exception
+    {
+        public BvhException(string msg) : base(msg) { }
+    }
+
+    public enum Channel
+    {
+        Xposition,
+        Yposition,
+        Zposition,
+        Xrotation,
+        Yrotation,
+        Zrotation,
+    }
+    public static class ChannelExtensions
+    {
+        public static string ToProperty(this Channel ch)
+        {
+            switch (ch)
+            {
+                case Channel.Xposition: return "localPosition.x";
+                case Channel.Yposition: return "localPosition.y";
+                case Channel.Zposition: return "localPosition.z";
+                case Channel.Xrotation: return "localEulerAnglesBaked.x";
+                case Channel.Yrotation: return "localEulerAnglesBaked.y";
+                case Channel.Zrotation: return "localEulerAnglesBaked.z";
+            }
+
+            throw new BvhException("no property for " + ch);
+        }
+
+        public static bool IsLocation(this Channel ch)
+        {
+            switch (ch)
+            {
+                case Channel.Xposition:
+                case Channel.Yposition:
+                case Channel.Zposition: return true;
+                case Channel.Xrotation:
+                case Channel.Yrotation:
+                case Channel.Zrotation: return false;
+            }
+
+            throw new BvhException("no property for " + ch);
+        }
+    }
+
+    public class EndSite : BvhNode
+    {
+        public EndSite() : base("")
+        {
+        }
+
+        public override void Parse(StringReader r)
+        {
+            r.ReadLine(); // offset
+        }
+    }
+
+    public class ChannelCurve
+    {
+        public float[] Keys
+        {
+            get;
+            private set;
+        }
+
+        public ChannelCurve(int frameCount)
+        {
+            Keys = new float[frameCount];
+        }
+
+        public void SetKey(int frame, float value)
+        {
+            Keys[frame] = value;
+        }
+    }
+
+    public class Bvh
+    {
+        public BvhNode Root
+        {
+            get;
+            private set;
+        }
+
+        public TimeSpan FrameTime
+        {
+            get;
+            private set;
+        }
+
+        public ChannelCurve[] Channels
+        {
+            get;
+            private set;
+        }
+
+        int m_frames;
+        public int FrameCount
+        {
+            get { return m_frames; }
+        }
+
+        public struct PathWithProperty
+        {
+            public string Path;
+            public string Property;
+            public bool IsLocation;
+        }
+
+        public bool TryGetPathWithPropertyFromChannel(ChannelCurve channel, out PathWithProperty pathWithProp)
+        {
+            var index = Channels.ToList().IndexOf(channel);
+            if (index == -1)
+            {
+                pathWithProp = default(PathWithProperty);
+                return false;
+            }
+
+            foreach (var node in Root.Traverse())
+            {
+                for (int i = 0; i < node.Channels.Length; ++i, --index)
+                {
+                    if (index == 0)
+                    {
+                        pathWithProp = new PathWithProperty
+                        {
+                            Path = GetPath(node),
+                            Property = node.Channels[i].ToProperty(),
+                            IsLocation = node.Channels[i].IsLocation(),
+                        };
+                        return true;
+                    }
+                }
+            }
+
+            throw new BvhException("channel is not found");
+        }
+
+        public string GetPath(BvhNode node)
+        {
+            var list = new List() { node.Name };
+
+            var current = node;
+            while (current != null)
+            {
+                current = GetParent(current);
+                if (current != null)
+                {
+                    list.Insert(0, current.Name);
+                }
+            }
+
+            return String.Join("/", list.ToArray());
+        }
+
+        BvhNode GetParent(BvhNode node)
+        {
+            foreach (var x in Root.Traverse())
+            {
+                if (x.Children.Contains(node))
+                {
+                    return x;
+                }
+            }
+
+            return null;
+        }
+
+        public ChannelCurve GetChannel(BvhNode target, Channel channel)
+        {
+            var index = 0;
+            foreach (var node in Root.Traverse())
+            {
+                for (int i = 0; i < node.Channels.Length; ++i, ++index)
+                {
+                    if (node == target && node.Channels[i] == channel)
+                    {
+                        return Channels[index];
+                    }
+                }
+            }
+
+            throw new BvhException("channel is not found");
+        }
+
+        public override string ToString()
+        {
+            return string.Format("{0}nodes, {1}channels, {2}frames, {3:0.00}seconds"
+                , Root.Traverse().Count()
+                , Channels.Length
+                , m_frames
+                , m_frames * FrameTime.TotalSeconds);
+        }
+
+        public Bvh(BvhNode root, int frames, float seconds)
+        {
+            Root = root;
+            FrameTime = TimeSpan.FromSeconds(seconds);
+            m_frames = frames;
+            var channelCount = Root.Traverse()
+                .Where(x => x.Channels != null)
+                .Select(x => x.Channels.Length)
+                .Sum();
+            Channels = Enumerable.Range(0, channelCount)
+                .Select(x => new ChannelCurve(frames))
+                .ToArray()
+                ;
+        }
+
+        public void ParseFrame(int frame, string line)
+        {
+            var splitted = line.Trim().Split().Where(x => !string.IsNullOrEmpty(x)).ToArray();
+            if (splitted.Length != Channels.Length)
+            {
+                throw new BvhException("frame key count is not match channel count");
+            }
+            for (int i = 0; i < Channels.Length; ++i)
+            {
+                Channels[i].SetKey(frame, float.Parse(splitted[i]));
+            }
+        }
+    }
+
+    public static class BvhParser
+    {
+        static BvhNode ParseNode(StringReader r, int level = 0)
+        {
+            var firstline = r.ReadLine().Trim();
+            var splitted = firstline.Split();
+            if (splitted.Length != 2)
+            {
+                if (splitted.Length == 1)
+                {
+                    if (splitted[0] == "}")
+                    {
+                        return null;
+                    }
+                }
+                throw new BvhException(String.Format("splitted to {0}({1})", splitted.Length, firstline));
+            }
+
+            BvhNode node = null;
+            if (splitted[0] == "ROOT")
+            {
+                if (level != 0)
+                {
+                    throw new BvhException("nested ROOT");
+                }
+                node = new BvhNode(splitted[1]);
+            }
+            else if (splitted[0] == "JOINT")
+            {
+                if (level == 0)
+                {
+                    throw new BvhException("should ROOT, but JOINT");
+                }
+                node = new BvhNode(splitted[1]);
+            }
+            else if (splitted[0] == "End")
+            {
+                if (level == 0)
+                {
+                    throw new BvhException("End in level 0");
+                }
+                node = new EndSite();
+            }
+            else
+            {
+                throw new BvhException("unknown type: " + splitted[0]);
+            }
+
+            if (r.ReadLine().Trim() != "{")
+            {
+                throw new BvhException("'{' is not found");
+            }
+
+            node.Parse(r);
+
+            // child nodes
+            while (true)
+            {
+                var child = ParseNode(r, level + 1);
+                if (child == null)
+                {
+                    break;
+                }
+
+                if (!(child is EndSite))
+                {
+                    node.AddChid(child);
+                }
+            }
+
+            return node;
+        }
+
+        public static Bvh FromPath(string path)
+        {
+            return Parse(File.ReadAllText(path));
+        }
+
+        public static Bvh Parse(string src)
+        {
+            using (var r = new StringReader(src))
+            {
+                if (r.ReadLine() != "HIERARCHY")
+                {
+                    throw new BvhException("not start with HIERARCHY");
+                }
+
+                var root = ParseNode(r);
+                if (root == null)
+                {
+                    return null;
+                }
+
+                var frames = 0;
+                var frameTime = 0.0f;
+                if (r.ReadLine() == "MOTION")
+                {
+                    var frameSplitted = r.ReadLine().Split(':');
+                    if (frameSplitted[0] != "Frames")
+                    {
+                        throw new BvhException("Frames is not found");
+                    }
+                    frames = int.Parse(frameSplitted[1]);
+
+                    var frameTimeSplitted = r.ReadLine().Split(':');
+                    if (frameTimeSplitted[0] != "Frame Time")
+                    {
+                        throw new BvhException("Frame Time is not found");
+                    }
+                    frameTime = float.Parse(frameTimeSplitted[1]);
+                }
+
+                var bvh = new Bvh(root, frames, frameTime);
+
+                for (int i = 0; i < frames; ++i)
+                {
+                    var line = r.ReadLine();
+                    bvh.ParseFrame(i, line);
+                }
+
+                bvh.Root.UpdatePosition(Vector3.Zero);
+
+                return bvh;
+            }
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/Bvh/Bvh.cs.meta b/Assets/VRM10/vrmlib/Runtime/Bvh/Bvh.cs.meta
new file mode 100644
index 000000000..3451a2a10
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Bvh/Bvh.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3876c0b36fb1fc54e9f7a5fc412967ec
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Bvh/BvhAnimationClip.cs b/Assets/VRM10/vrmlib/Runtime/Bvh/BvhAnimationClip.cs
new file mode 100644
index 000000000..1a836dd0a
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Bvh/BvhAnimationClip.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+
+namespace VrmLib.Bvh
+{
+    public static class BvhAnimation
+    {
+        class CurveSet
+        {
+            BvhNode Node = default;
+            Func EulerToRotation = default;
+            public CurveSet(BvhNode node)
+            {
+                Node = node;
+            }
+
+            public ChannelCurve PositionX = default;
+            public ChannelCurve PositionY = default;
+            public ChannelCurve PositionZ = default;
+            public Vector3 GetPosition(int i)
+            {
+                return new Vector3(
+                    PositionX.Keys[i],
+                    PositionY.Keys[i],
+                    PositionZ.Keys[i]);
+            }
+
+            public ChannelCurve RotationX = default;
+            public ChannelCurve RotationY = default;
+            public ChannelCurve RotationZ = default;
+            public Quaternion GetRotation(int i)
+            {
+                if (EulerToRotation == null)
+                {
+                    EulerToRotation = Node.GetEulerToRotation();
+                }
+                return EulerToRotation(
+                    RotationX.Keys[i],
+                    RotationY.Keys[i],
+                    RotationZ.Keys[i]
+                    );
+            }
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/Bvh/BvhAnimationClip.cs.meta b/Assets/VRM10/vrmlib/Runtime/Bvh/BvhAnimationClip.cs.meta
new file mode 100644
index 000000000..417828c56
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Bvh/BvhAnimationClip.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 556644864ce505b43b0f967425a25c0c
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Bvh/BvhExtensions.cs b/Assets/VRM10/vrmlib/Runtime/Bvh/BvhExtensions.cs
new file mode 100644
index 000000000..13b5b0eee
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Bvh/BvhExtensions.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Linq;
+using System.Numerics;
+
+namespace VrmLib.Bvh
+{
+    public static class BvhExtensions
+    {
+        public static Func GetEulerToRotation(this BvhNode bvh)
+        {
+            var order = bvh.Channels.Where(x => x == Channel.Xrotation || x == Channel.Yrotation || x == Channel.Zrotation).ToArray();
+
+            return (x, y, z) =>
+            {
+                var xRot = Quaternion.CreateFromYawPitchRoll(x, 0, 0);
+                var yRot = Quaternion.CreateFromYawPitchRoll(0, y, 0);
+                var zRot = Quaternion.CreateFromYawPitchRoll(0, 0, z);
+
+                var r = Quaternion.Identity;
+                foreach (var ch in order)
+                {
+                    switch (ch)
+                    {
+                        case Channel.Xrotation: r = r * xRot; break;
+                        case Channel.Yrotation: r = r * yRot; break;
+                        case Channel.Zrotation: r = r * zRot; break;
+                        default: throw new BvhException("no rotation");
+                    }
+                }
+                return r;
+            };
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/Bvh/BvhExtensions.cs.meta b/Assets/VRM10/vrmlib/Runtime/Bvh/BvhExtensions.cs.meta
new file mode 100644
index 000000000..59cc066a2
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Bvh/BvhExtensions.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: be2bfe50d0adaf24185001d94f5ff8e0
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Bvh/BvhNode.cs b/Assets/VRM10/vrmlib/Runtime/Bvh/BvhNode.cs
new file mode 100644
index 000000000..6564d2a08
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Bvh/BvhNode.cs
@@ -0,0 +1,124 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Numerics;
+
+namespace VrmLib.Bvh
+{
+    public class BvhNode
+    {
+        public String Name
+        {
+            get;
+            set;
+        }
+
+        public override string ToString()
+        {
+            return $"{Name}";
+        }
+
+        public HumanoidBones Bone
+        {
+            get;
+            set;
+        }
+
+        // world position
+        public Vector3 SkeletonLocalPosition
+        {
+            get;
+            private set;
+        }
+
+        public void UpdatePosition(Vector3 parentPosition)
+        {
+            SkeletonLocalPosition = parentPosition + Offset;
+
+            foreach (var child in Children)
+            {
+                child.UpdatePosition(SkeletonLocalPosition);
+            }
+        }
+
+        public Vector3 Offset;
+
+        public Channel[] Channels
+        {
+            get;
+            private set;
+        }
+
+        List m_children = new List();
+
+
+        public IReadOnlyList Children => m_children;
+
+
+        public void AddChid(BvhNode child)
+        {
+            child.Parent = this;
+            m_children.Add(child);
+        }
+
+
+        public BvhNode Parent
+        {
+            get;
+            private set;
+        }
+
+        public BvhNode(string name)
+        {
+            Name = name;
+        }
+
+        public virtual void Parse(StringReader r)
+        {
+            Offset = ParseOffset(r.ReadLine());
+
+            Channels = ParseChannel(r.ReadLine());
+        }
+
+        static Vector3 ParseOffset(string line)
+        {
+            var splited = line.Trim().Split();
+            if (splited[0] != "OFFSET")
+            {
+                throw new BvhException("OFFSET is not found");
+            }
+
+            var offset = splited.Skip(1).Where(x => !string.IsNullOrEmpty(x)).Select(x => float.Parse(x)).ToArray();
+            return new Vector3(offset[0], offset[1], offset[2]);
+        }
+
+        static Channel[] ParseChannel(string line)
+        {
+            var splited = line.Trim().Split();
+            if (splited[0] != "CHANNELS")
+            {
+                throw new BvhException("CHANNELS is not found");
+            }
+            var count = int.Parse(splited[1]);
+            if (count + 2 != splited.Length)
+            {
+                throw new BvhException("channel count is not match with splited count");
+            }
+            return splited.Skip(2).Select(x => (Channel)Enum.Parse(typeof(Channel), x)).ToArray();
+        }
+
+        public IEnumerable Traverse()
+        {
+            yield return this;
+
+            foreach (var child in Children)
+            {
+                foreach (var descentant in child.Traverse())
+                {
+                    yield return descentant;
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Runtime/Bvh/BvhNode.cs.meta b/Assets/VRM10/vrmlib/Runtime/Bvh/BvhNode.cs.meta
new file mode 100644
index 000000000..db53f700d
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Bvh/BvhNode.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ac87eb7c7e9da9c458c0601587ed0bc9
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Coordinates.cs b/Assets/VRM10/vrmlib/Runtime/Coordinates.cs
new file mode 100644
index 000000000..519cb1e02
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Coordinates.cs
@@ -0,0 +1,46 @@
+namespace VrmLib
+{
+    public enum GeometryCoordinates
+    {
+        Unknown,
+
+        /// OpenGL standard
+        XYZ_RightUpBack_RH,
+
+        /// D3D standard(Unity)
+        XYZ_RightUpForward_LH,
+    }
+
+    public enum TextureOrigin
+    {
+        Unknown,
+
+        // GLTF
+        LeftTop,
+
+        // Unity
+        LeftBottom,
+    }
+
+    public struct Coordinates
+    {
+        public GeometryCoordinates Geometry;
+        public TextureOrigin Texture;
+
+        public static Coordinates Gltf => new Coordinates
+        {
+            Geometry = GeometryCoordinates.XYZ_RightUpBack_RH,
+            Texture = TextureOrigin.LeftTop,
+        };
+
+        public bool IsGltf => this.Equals(Gltf);
+
+        public static Coordinates Unity => new Coordinates
+        {
+            Geometry = GeometryCoordinates.XYZ_RightUpForward_LH,
+            Texture = TextureOrigin.LeftBottom,
+        };
+
+        public bool IsUnity => this.Equals(Unity);
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/Coordinates.cs.meta b/Assets/VRM10/vrmlib/Runtime/Coordinates.cs.meta
new file mode 100644
index 000000000..851eb4f7b
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Coordinates.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 77774615c3e654941bfbbe546cba08b5
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/EnumUtil.cs b/Assets/VRM10/vrmlib/Runtime/EnumUtil.cs
new file mode 100644
index 000000000..93647a0f8
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/EnumUtil.cs
@@ -0,0 +1,39 @@
+using System;
+
+namespace VrmLib
+{
+    public static class EnumUtil
+    {
+        public static T Parse(string src, bool ignoreCase = true) where T : struct
+        {
+            if (string.IsNullOrEmpty(src))
+            {
+                return default(T);
+            }
+
+            return (T)Enum.Parse(typeof(T), src, ignoreCase);
+        }
+
+        public static T TryParseOrDefault(string src, T defaultValue = default(T)) where T : struct
+        {
+            try
+            {
+                return (T)Enum.Parse(typeof(T), src, true);
+            }
+            catch (Exception)
+            {
+                return defaultValue;
+            }
+        }
+
+        public static T Cast(object src, bool ignoreCase = true) where T : struct
+        {
+            if (src is null)
+            {
+                throw new ArgumentNullException();
+            }
+
+            return (T)Enum.Parse(typeof(T), src.ToString(), ignoreCase);
+        }
+    }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Runtime/EnumUtil.cs.meta b/Assets/VRM10/vrmlib/Runtime/EnumUtil.cs.meta
new file mode 100644
index 000000000..28808c47e
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/EnumUtil.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 035afec0107099641b5bc89b84a51733
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/ExportArgs.cs b/Assets/VRM10/vrmlib/Runtime/ExportArgs.cs
new file mode 100644
index 000000000..ecd2a27ae
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ExportArgs.cs
@@ -0,0 +1,104 @@
+using System;
+
+namespace VrmLib
+{
+    [Serializable]
+    public struct ExportArgs
+    {
+        /// 
+        /// VRM拡張をエクスポートするか
+        ///
+        /// struct で初期値をdefault以外にするために
+        /// nullableなpropertyを使っている
+        /// 
+        bool? m_vrm;
+        public bool vrm
+        {
+            get
+            {
+                if (!m_vrm.HasValue)
+                {
+                    m_vrm = true;
+                }
+                return m_vrm.Value;
+            }
+            set
+            {
+                m_vrm = value;
+            }
+        }
+
+        /// 
+        /// 頂点バッファにsparse機能を使うか
+        ///
+        /// struct で初期値をdefault以外にするために
+        /// nullableなpropertyを使っている
+        /// 
+        bool? m_sparse;
+
+        public bool sparse
+        {
+            get
+            {
+                if (!m_sparse.HasValue)
+                {
+                    m_sparse = true;
+                }
+                return m_sparse.Value;
+            }
+            set
+            {
+                m_sparse = value;
+            }
+        }
+
+        /// 
+        /// エクスポート時にmorphTargetから法線を削除するか
+        ///
+        /// struct で初期値をdefault以外にするために
+        /// nullableなpropertyを使っている
+        /// 
+        bool? m_remove_morph_normal;
+
+        public bool removeMorphNormal
+        {
+            get
+            {
+                if (!m_remove_morph_normal.HasValue)
+                {
+                    // TODO: Importerの修正が取り込まれたらtrueにする
+                    m_remove_morph_normal = false;
+                }
+                return m_remove_morph_normal.Value;
+            }
+            set
+            {
+                m_remove_morph_normal = value;
+            }
+        }
+
+        /// 
+        /// エクスポート時にtangentを削除するか
+        ///
+        /// struct で初期値をdefault以外にするために
+        /// nullableなpropertyを使っている
+        /// 
+        bool? m_remove_tangent;
+
+        public bool removeTangent
+        {
+            get
+            {
+                if (!m_remove_tangent.HasValue)
+                {
+                    m_remove_tangent = true;
+                }
+                return m_remove_tangent.Value;
+            }
+            set
+            {
+                m_remove_tangent = value;
+            }
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/ExportArgs.cs.meta b/Assets/VRM10/vrmlib/Runtime/ExportArgs.cs.meta
new file mode 100644
index 000000000..3923b5125
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ExportArgs.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f4cd67059531eda4e9aa740ba07e8204
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Extensions.meta b/Assets/VRM10/vrmlib/Runtime/Extensions.meta
new file mode 100644
index 000000000..04cd2e335
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Extensions.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: cfb8b86e925ee9d46bdb4409d91e96b4
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Extensions/ListExtensions.cs b/Assets/VRM10/vrmlib/Runtime/Extensions/ListExtensions.cs
new file mode 100644
index 000000000..fe23d84d7
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Extensions/ListExtensions.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+
+namespace VrmLib
+{
+    public static class ListExtensions
+    {
+        public static int IndexOfThrow(this List list, T target)
+        {
+            var index = list.IndexOf(target);
+            if (index == -1)
+            {
+                throw new KeyNotFoundException();
+            }
+            return index;
+        }
+
+        public static int? IndexOfNullable(this List list, T target)
+        {
+            var index = list.IndexOf(target);
+            if (index == -1)
+            {
+                return default;
+            }
+            return index;
+        }
+     }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Runtime/Extensions/ListExtensions.cs.meta b/Assets/VRM10/vrmlib/Runtime/Extensions/ListExtensions.cs.meta
new file mode 100644
index 000000000..7e9faf8b7
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Extensions/ListExtensions.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: cb448ffdf669ef74c84625b12fa691de
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/GltfId.cs b/Assets/VRM10/vrmlib/Runtime/GltfId.cs
new file mode 100644
index 000000000..163d776ed
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/GltfId.cs
@@ -0,0 +1,22 @@
+namespace VrmLib
+{
+    /// 
+    /// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/schema/glTFid.schema.json
+    ///
+    /// ImportとExportでなるべく順番を維持するべく導入。下記のベースクラスとした
+    ///
+    /// * Image, Texture, Material
+    /// * Animation
+    /// * Node, Skin, Mesh
+    ///
+    /// 
+    public class GltfId
+    {
+        public int? GltfIndex;
+
+        /// 
+        /// 未指定は後ろに送る
+        /// 
+        public int SortOrder => GltfIndex.GetValueOrDefault(int.MaxValue);
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/GltfId.cs.meta b/Assets/VRM10/vrmlib/Runtime/GltfId.cs.meta
new file mode 100644
index 000000000..1fb899220
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/GltfId.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 94bf75bb38754154592dec46de49410b
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Humanoid.meta b/Assets/VRM10/vrmlib/Runtime/Humanoid.meta
new file mode 100644
index 000000000..a04e05a34
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Humanoid.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 0d05c6ea4a073fc49b7f5b47373def99
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Humanoid/Humanoid.cs b/Assets/VRM10/vrmlib/Runtime/Humanoid/Humanoid.cs
new file mode 100644
index 000000000..434b48854
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Humanoid/Humanoid.cs
@@ -0,0 +1,510 @@
+using System;
+using System.Linq;
+using System.Collections;
+using System.Collections.Generic;
+using System.Numerics;
+
+
+/// 
+/// * 胴体: hips, spine, chest, upperChest, neck, head => 6
+/// * 腕: shoulder, upperArm, lowerArm, hand => 4 x 2
+/// * 脚: upperLeg, lowerLeg, foot, toes => 4 x 2
+///     = 22
+/// * 指: proximal, inetermediate, distal => 3 x 5 x 2 = 30
+/// 
+namespace VrmLib
+{
+    public struct HumanoidHead
+    {
+        // required
+        public Node Head;
+        public Node Jaw;
+        public Node LeftEye;
+        public Node RightEye;
+
+        public bool HasRequiredBones => Head != null;
+    };
+
+    public struct HumanoidArm
+    {
+        public Node Shoulder;
+        // required
+        public Node Upper;
+        // required
+        public Node Lower;
+        // required
+        public Node Hand;
+
+        public bool HasRequiredBones => Upper != null && Lower != null && Hand != null;
+
+        public Vector3 Direction
+        {
+            get
+            {
+                if (Shoulder != null)
+                {
+                    return Vector3.Normalize(Hand.Translation - Shoulder.Translation);
+                }
+                else
+                {
+                    return Vector3.Normalize(Hand.Translation - Upper.Translation);
+                }
+            }
+        }
+
+        public void DirectTo(Vector3 dir)
+        {
+            if (Shoulder != null)
+            {
+                Shoulder.RotateFromTo(Upper.Translation - Shoulder.Translation, dir);
+            }
+            Upper.RotateFromTo(Lower.Translation - Upper.Translation, dir);
+            Lower.RotateFromTo(Hand.Translation - Lower.Translation, dir);
+        }
+    };
+
+    public struct HumanoidLeg
+    {
+        public Node Upper;
+        public Node Lower;
+        public Node Foot;
+        public Node Toe;
+    };
+
+    struct HumanoidFinger
+    {
+        public Node Proximal;
+        public Node Intermediate;
+        public Node Distal;
+    }
+
+    struct HumanoidFingers
+    {
+        public HumanoidFinger Thumb;
+        public HumanoidFinger Index;
+        public HumanoidFinger Middle;
+        public HumanoidFinger Ring;
+        public HumanoidFinger Little;
+    };
+
+    /// 
+    /// ヒューマノイドの姿勢を制御する
+    /// 
+    public class Humanoid : IDictionary
+    {
+        public Node Hips;
+        public Node Spine;
+        public Node Chest;
+        public Node UpperChest;
+        public Node Neck;
+
+        HumanoidHead Head;
+
+        HumanoidArm LeftArm;
+        HumanoidFingers LeftFingers;
+
+        HumanoidArm RightArm;
+        HumanoidFingers RightFingers;
+
+        HumanoidLeg LeftLeg;
+        HumanoidLeg RightLeg;
+
+        public bool HasRequiredBones
+        {
+            get
+            {
+                if (Hips == null) return false;
+                if (Spine == null) return false;
+                if (!Head.HasRequiredBones) return false;
+                if (!LeftArm.HasRequiredBones) return false;
+                if (!RightArm.HasRequiredBones) return false;
+
+                // TODO
+                return true;
+            }
+        }
+
+        ICollection IDictionary.Keys => throw new System.NotImplementedException();
+
+        ICollection IDictionary.Values => throw new System.NotImplementedException();
+
+        int ICollection>.Count => this.Select(_ => _).Count();
+
+        bool ICollection>.IsReadOnly => throw new System.NotImplementedException();
+
+        Node IDictionary.this[HumanoidBones key] { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); }
+
+        public Humanoid()
+        {
+        }
+
+        public Humanoid(Node root)
+        {
+            Assign(root);
+        }
+
+        public void Assign(Node root)
+        {
+            foreach (var node in root.Traverse())
+            {
+                if (node.HumanoidBone.HasValue)
+                {
+                    Add(node.HumanoidBone.Value, node);
+                }
+            }
+        }
+
+        void CopyTraverse(Node src, Node dst)
+        {
+            dst.HumanoidBone = src.HumanoidBone;
+            dst.LocalScaling = src.LocalScaling;
+            dst.LocalRotation = src.LocalRotation;
+            dst.LocalTranslation = src.LocalTranslation;
+            foreach (var child in src.Children)
+            {
+                var dstChild = new Node(child.Name /*+ ".copy"*/);
+                dst.Add(dstChild);
+                CopyTraverse(child, dstChild);
+            }
+        }
+
+        public Humanoid CopyNodes()
+        {
+            // ヒエラルキーのコピーを作成する
+            var hips = new Node(Hips.Name /*+ ".copy"*/);
+            CopyTraverse(Hips, hips);
+
+            var humanoid = new Humanoid(hips);
+            return humanoid;
+        }
+
+        // Y軸180度回転
+        public (bool, string) Y180()
+        {
+            var sb = new System.Text.StringBuilder();
+            Hips.LocalRotation = Quaternion.CreateFromYawPitchRoll(MathFWrap.PI, 0, 0);
+            Hips.CalcWorldMatrix();
+            return (true, sb.ToString());
+        }
+
+        /// 
+        /// 上半身のTPose
+        /// 
+        public (bool, string) MakeTPose()
+        {
+            var sb = new System.Text.StringBuilder();
+            bool modified = false;
+
+            // hipsのforward を -Z に向ける
+            // hipsのforward は (left.leg - right.leg) cross (0, 1, 0)
+            var left = Vector3.Normalize(LeftLeg.Upper.Translation - RightLeg.Upper.Translation);
+            var forward = Vector3.Cross(left, Vector3.UnitY);
+            if (Vector3.Dot(forward, -Vector3.UnitZ) < 1.0f - 0.1f)
+            {
+                Hips.RotateFromTo(forward, -Vector3.UnitZ);
+                modified = true;
+            }
+
+            if (Vector3.Dot(LeftArm.Direction, -Vector3.UnitX) < 1.0f - 0.1f)
+            {
+                LeftArm.DirectTo(-Vector3.UnitX);
+                sb.Append("(fix left arm)");
+                modified = true;
+            }
+            if (Vector3.Dot(RightArm.Direction, Vector3.UnitX) < 1.0f - 0.1f)
+            {
+                RightArm.DirectTo(Vector3.UnitX);
+                sb.Append("(fix right arm)");
+                modified = true;
+            }
+
+            Hips.CalcWorldMatrix();
+            return (modified, sb.ToString());
+        }
+
+        public void RetargetTo(Humanoid srcTPose, Humanoid dst)
+        {
+            foreach (var kv in this)
+            {
+                var tposeNode = srcTPose[kv.Key];
+                if (dst.TryGetValue(kv.Key, out Node node))
+                {
+                    // var t = tposeNode.LocalRotation;
+                    // var t = Quaternion.Identity;
+                    // node.LocalRotationWithoutUpdate = Quaternion.Inverse(t) * kv.Value.LocalRotation;
+                    // node.LocalRotationWithoutUpdate = kv.Value.LocalRotation * Quaternion.Inverse(t);
+
+                    var t = tposeNode.Rotation;
+                    // node.Rotation = Quaternion.Inverse(t) * kv.Value.Rotation;
+                    node.Rotation = kv.Value.Rotation * Quaternion.Inverse(t);
+                    // node.LocalRotationWithoutUpdate = kv.Value.LocalRotation * Quaternion.Inverse(t);
+                }
+                else
+                {
+                    Console.WriteLine($"{kv.Key} not found");
+                }
+            }
+            dst.Hips.CalcWorldMatrix();
+        }
+
+        #region interface
+        public void Add(KeyValuePair item)
+        {
+            Add(item.Key, item.Value);
+        }
+
+        public void Add(HumanoidBones key, Node node)
+        {
+            if (key == HumanoidBones.unknown)
+            {
+                throw new ArgumentException();
+            }
+
+            node.HumanoidBone = key;
+
+            switch (node.HumanoidBone.Value)
+            {
+                case HumanoidBones.hips: Hips = node; break;
+                case HumanoidBones.spine: Spine = node; break;
+                case HumanoidBones.chest: Chest = node; break;
+                case HumanoidBones.upperChest: UpperChest = node; break;
+                case HumanoidBones.neck: Neck = node; break;
+                case HumanoidBones.head: Head.Head = node; break;
+                case HumanoidBones.jaw: Head.Jaw = node; break;
+                case HumanoidBones.leftEye: Head.LeftEye = node; break;
+                case HumanoidBones.rightEye: Head.RightEye = node; break;
+
+                case HumanoidBones.leftShoulder: LeftArm.Shoulder = node; break;
+                case HumanoidBones.leftUpperArm: LeftArm.Upper = node; break;
+                case HumanoidBones.leftLowerArm: LeftArm.Lower = node; break;
+                case HumanoidBones.leftHand: LeftArm.Hand = node; break;
+
+                case HumanoidBones.rightShoulder: RightArm.Shoulder = node; break;
+                case HumanoidBones.rightUpperArm: RightArm.Upper = node; break;
+                case HumanoidBones.rightLowerArm: RightArm.Lower = node; break;
+                case HumanoidBones.rightHand: RightArm.Hand = node; break;
+
+                case HumanoidBones.leftUpperLeg: LeftLeg.Upper = node; break;
+                case HumanoidBones.leftLowerLeg: LeftLeg.Lower = node; break;
+                case HumanoidBones.leftFoot: LeftLeg.Foot = node; break;
+                case HumanoidBones.leftToes: LeftLeg.Toe = node; break;
+
+                case HumanoidBones.rightUpperLeg: RightLeg.Upper = node; break;
+                case HumanoidBones.rightLowerLeg: RightLeg.Lower = node; break;
+                case HumanoidBones.rightFoot: RightLeg.Foot = node; break;
+                case HumanoidBones.rightToes: RightLeg.Toe = node; break;
+
+                case HumanoidBones.leftThumbProximal: LeftFingers.Thumb.Proximal = node; break;
+                case HumanoidBones.leftThumbIntermediate: LeftFingers.Thumb.Intermediate = node; break;
+                case HumanoidBones.leftThumbDistal: LeftFingers.Thumb.Distal = node; break;
+                case HumanoidBones.leftIndexProximal: LeftFingers.Index.Proximal = node; break;
+                case HumanoidBones.leftIndexIntermediate: LeftFingers.Index.Intermediate = node; break;
+                case HumanoidBones.leftIndexDistal: LeftFingers.Index.Distal = node; break;
+                case HumanoidBones.leftMiddleProximal: LeftFingers.Middle.Proximal = node; break;
+                case HumanoidBones.leftMiddleIntermediate: LeftFingers.Middle.Intermediate = node; break;
+                case HumanoidBones.leftMiddleDistal: LeftFingers.Middle.Distal = node; break;
+                case HumanoidBones.leftRingProximal: LeftFingers.Ring.Proximal = node; break;
+                case HumanoidBones.leftRingIntermediate: LeftFingers.Ring.Intermediate = node; break;
+                case HumanoidBones.leftRingDistal: LeftFingers.Ring.Distal = node; break;
+                case HumanoidBones.leftLittleProximal: LeftFingers.Little.Proximal = node; break;
+                case HumanoidBones.leftLittleIntermediate: LeftFingers.Little.Intermediate = node; break;
+                case HumanoidBones.leftLittleDistal: LeftFingers.Little.Distal = node; break;
+
+                case HumanoidBones.rightThumbProximal: RightFingers.Thumb.Proximal = node; break;
+                case HumanoidBones.rightThumbIntermediate: RightFingers.Thumb.Intermediate = node; break;
+                case HumanoidBones.rightThumbDistal: RightFingers.Thumb.Distal = node; break;
+                case HumanoidBones.rightIndexProximal: RightFingers.Index.Proximal = node; break;
+                case HumanoidBones.rightIndexIntermediate: RightFingers.Index.Intermediate = node; break;
+                case HumanoidBones.rightIndexDistal: RightFingers.Index.Distal = node; break;
+                case HumanoidBones.rightMiddleProximal: RightFingers.Middle.Proximal = node; break;
+                case HumanoidBones.rightMiddleIntermediate: RightFingers.Middle.Intermediate = node; break;
+                case HumanoidBones.rightMiddleDistal: RightFingers.Middle.Distal = node; break;
+                case HumanoidBones.rightRingProximal: RightFingers.Ring.Proximal = node; break;
+                case HumanoidBones.rightRingIntermediate: RightFingers.Ring.Intermediate = node; break;
+                case HumanoidBones.rightRingDistal: RightFingers.Ring.Distal = node; break;
+                case HumanoidBones.rightLittleProximal: RightFingers.Little.Proximal = node; break;
+                case HumanoidBones.rightLittleIntermediate: RightFingers.Little.Intermediate = node; break;
+                case HumanoidBones.rightLittleDistal: RightFingers.Little.Distal = node; break;
+
+                default: throw new NotImplementedException();
+            }
+        }
+
+        public bool ContainsKey(HumanoidBones key)
+        {
+            return TryGetValue(key, out Node _);
+        }
+
+        public bool Remove(HumanoidBones key)
+        {
+            if (!ContainsKey(key))
+            {
+                return false;
+            }
+            Add(key, null);
+            return true;
+        }
+
+        public Node this[HumanoidBones key]
+        {
+            get
+            {
+                if (TryGetValue(key, out Node node))
+                {
+                    return node;
+                }
+                else
+                {
+                    throw new KeyNotFoundException();
+                }
+            }
+        }
+
+        public Node this[Node node]
+        {
+            get
+            {
+                if (node.HumanoidBone.HasValue && node.HumanoidBone.Value != HumanoidBones.unknown)
+                {
+                    return this[node.HumanoidBone.Value];
+                }
+                else
+                {
+                    // とりあえず
+                    return Hips.Traverse().First(x => x.Name == node.Name);
+                }
+            }
+        }
+
+        public bool TryGetValue(HumanoidBones key, out Node value)
+        {
+            switch (key)
+            {
+                case HumanoidBones.hips: value = Hips; return true;
+                case HumanoidBones.spine: value = Spine; return true;
+                case HumanoidBones.chest: value = Chest; return true;
+                case HumanoidBones.upperChest: value = UpperChest; return true;
+                case HumanoidBones.neck: value = Neck; return true;
+                case HumanoidBones.head: value = Head.Head; return true;
+                case HumanoidBones.jaw: value = Head.Jaw; return true;
+                case HumanoidBones.leftEye: value = Head.LeftEye; return true;
+                case HumanoidBones.rightEye: value = Head.RightEye; return true;
+
+                case HumanoidBones.leftShoulder: value = LeftArm.Shoulder; return true;
+                case HumanoidBones.leftUpperArm: value = LeftArm.Upper; return true;
+                case HumanoidBones.leftLowerArm: value = LeftArm.Lower; return true;
+                case HumanoidBones.leftHand: value = LeftArm.Hand; return true;
+
+                case HumanoidBones.rightShoulder: value = RightArm.Shoulder; return true;
+                case HumanoidBones.rightUpperArm: value = RightArm.Upper; return true;
+                case HumanoidBones.rightLowerArm: value = RightArm.Lower; return true;
+                case HumanoidBones.rightHand: value = RightArm.Hand; return true;
+
+                case HumanoidBones.leftUpperLeg: value = LeftLeg.Upper; return true;
+                case HumanoidBones.leftLowerLeg: value = LeftLeg.Lower; return true;
+                case HumanoidBones.leftFoot: value = LeftLeg.Foot; return true;
+                case HumanoidBones.leftToes: value = LeftLeg.Toe; return true;
+
+                case HumanoidBones.rightUpperLeg: value = RightLeg.Upper; return true;
+                case HumanoidBones.rightLowerLeg: value = RightLeg.Lower; return true;
+                case HumanoidBones.rightFoot: value = RightLeg.Foot; return true;
+                case HumanoidBones.rightToes: value = RightLeg.Toe; return true;
+
+                case HumanoidBones.leftThumbProximal: value = LeftFingers.Thumb.Proximal; return true;
+                case HumanoidBones.leftThumbIntermediate: value = LeftFingers.Thumb.Intermediate; return true;
+                case HumanoidBones.leftThumbDistal: value = LeftFingers.Thumb.Distal; return true;
+                case HumanoidBones.leftIndexProximal: value = LeftFingers.Index.Proximal; return true;
+                case HumanoidBones.leftIndexIntermediate: value = LeftFingers.Index.Intermediate; return true;
+                case HumanoidBones.leftIndexDistal: value = LeftFingers.Index.Distal; return true;
+                case HumanoidBones.leftMiddleProximal: value = LeftFingers.Middle.Proximal; return true;
+                case HumanoidBones.leftMiddleIntermediate: value = LeftFingers.Middle.Intermediate; return true;
+                case HumanoidBones.leftMiddleDistal: value = LeftFingers.Middle.Distal; return true;
+                case HumanoidBones.leftRingProximal: value = LeftFingers.Ring.Proximal; return true;
+                case HumanoidBones.leftRingIntermediate: value = LeftFingers.Ring.Intermediate; return true;
+                case HumanoidBones.leftRingDistal: value = LeftFingers.Ring.Distal; return true;
+                case HumanoidBones.leftLittleProximal: value = LeftFingers.Little.Proximal; return true;
+                case HumanoidBones.leftLittleIntermediate: value = LeftFingers.Little.Intermediate; return true;
+                case HumanoidBones.leftLittleDistal: value = LeftFingers.Little.Distal; return true;
+
+                case HumanoidBones.rightThumbProximal: value = LeftFingers.Thumb.Proximal; return true;
+                case HumanoidBones.rightThumbIntermediate: value = LeftFingers.Thumb.Intermediate; return true;
+                case HumanoidBones.rightThumbDistal: value = LeftFingers.Thumb.Distal; return true;
+                case HumanoidBones.rightIndexProximal: value = LeftFingers.Index.Proximal; return true;
+                case HumanoidBones.rightIndexIntermediate: value = LeftFingers.Index.Intermediate; return true;
+                case HumanoidBones.rightIndexDistal: value = LeftFingers.Index.Distal; return true;
+                case HumanoidBones.rightMiddleProximal: value = LeftFingers.Middle.Proximal; return true;
+                case HumanoidBones.rightMiddleIntermediate: value = LeftFingers.Middle.Intermediate; return true;
+                case HumanoidBones.rightMiddleDistal: value = LeftFingers.Middle.Distal; return true;
+                case HumanoidBones.rightRingProximal: value = LeftFingers.Ring.Proximal; return true;
+                case HumanoidBones.rightRingIntermediate: value = LeftFingers.Ring.Intermediate; return true;
+                case HumanoidBones.rightRingDistal: value = LeftFingers.Ring.Distal; return true;
+                case HumanoidBones.rightLittleProximal: value = LeftFingers.Little.Proximal; return true;
+                case HumanoidBones.rightLittleIntermediate: value = LeftFingers.Little.Intermediate; return true;
+                case HumanoidBones.rightLittleDistal: value = LeftFingers.Little.Distal; return true;
+            }
+
+            value = null;
+            return false;
+        }
+
+        public void Clear()
+        {
+            foreach (HumanoidBones key in Enum.GetValues(typeof(HumanoidBones)))
+            {
+                Add(key, null);
+            }
+        }
+
+        public IEnumerator> GetEnumerator()
+        {
+            if (Hips != null) yield return new KeyValuePair(HumanoidBones.hips, Hips);
+            if (Spine != null) yield return new KeyValuePair(HumanoidBones.spine, Spine);
+            if (Chest != null) yield return new KeyValuePair(HumanoidBones.chest, Chest);
+            if (UpperChest != null) yield return new KeyValuePair(HumanoidBones.upperChest, UpperChest);
+            if (Neck != null) yield return new KeyValuePair(HumanoidBones.neck, Neck);
+            if (Head.Head != null) yield return new KeyValuePair(HumanoidBones.head, Head.Head);
+
+            if (LeftArm.Shoulder != null) yield return new KeyValuePair(HumanoidBones.leftShoulder, LeftArm.Shoulder);
+            if (LeftArm.Upper != null) yield return new KeyValuePair(HumanoidBones.leftUpperArm, LeftArm.Upper);
+            if (LeftArm.Lower != null) yield return new KeyValuePair(HumanoidBones.leftLowerArm, LeftArm.Lower);
+            if (LeftArm.Hand != null) yield return new KeyValuePair(HumanoidBones.leftHand, LeftArm.Hand);
+
+            if (RightArm.Shoulder != null) yield return new KeyValuePair(HumanoidBones.rightShoulder, RightArm.Shoulder);
+            if (RightArm.Upper != null) yield return new KeyValuePair(HumanoidBones.rightUpperArm, RightArm.Upper);
+            if (RightArm.Lower != null) yield return new KeyValuePair(HumanoidBones.rightLowerArm, RightArm.Lower);
+            if (RightArm.Hand != null) yield return new KeyValuePair(HumanoidBones.rightHand, RightArm.Hand);
+
+            if (LeftLeg.Upper != null) yield return new KeyValuePair(HumanoidBones.leftUpperLeg, LeftLeg.Upper);
+            if (LeftLeg.Lower != null) yield return new KeyValuePair(HumanoidBones.leftLowerLeg, LeftLeg.Lower);
+            if (LeftLeg.Foot != null) yield return new KeyValuePair(HumanoidBones.leftFoot, LeftLeg.Foot);
+
+            if (RightLeg.Upper != null) yield return new KeyValuePair(HumanoidBones.rightUpperLeg, RightLeg.Upper);
+            if (RightLeg.Lower != null) yield return new KeyValuePair(HumanoidBones.rightLowerLeg, RightLeg.Lower);
+            if (RightLeg.Foot != null) yield return new KeyValuePair(HumanoidBones.rightFoot, RightLeg.Foot);
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+
+        #region NotImplement
+        bool ICollection>.Contains(KeyValuePair item)
+        {
+            throw new System.NotImplementedException();
+        }
+
+        void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex)
+        {
+            throw new System.NotImplementedException();
+        }
+
+        bool ICollection>.Remove(KeyValuePair item)
+        {
+            throw new System.NotImplementedException();
+        }
+        #endregion
+
+        #endregion
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/Humanoid/Humanoid.cs.meta b/Assets/VRM10/vrmlib/Runtime/Humanoid/Humanoid.cs.meta
new file mode 100644
index 000000000..e85ea96b6
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Humanoid/Humanoid.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 95f837b865335a34fb1bcd7d057fbabc
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Humanoid/HumanoidBones.cs b/Assets/VRM10/vrmlib/Runtime/Humanoid/HumanoidBones.cs
new file mode 100644
index 000000000..3d14a9214
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Humanoid/HumanoidBones.cs
@@ -0,0 +1,105 @@
+using System;
+
+namespace VrmLib
+{
+    /// 
+    /// Required for skeleton.
+    /// 15 bones.
+    /// 
+    public class BoneRequiredAttribute : Attribute
+    {
+    }
+
+    /// 
+    /// hips -> spine -> (chest) -> (heck) -> head: Y+
+    /// 
+    public enum HumanoidBones
+    {
+        unknown,
+
+        [BoneRequired]
+        hips,
+
+        #region leg
+        [BoneRequired]
+        leftUpperLeg,
+        [BoneRequired]
+        rightUpperLeg,
+        [BoneRequired]
+        leftLowerLeg,
+        [BoneRequired]
+        rightLowerLeg,
+        [BoneRequired]
+        leftFoot,
+        [BoneRequired]
+        rightFoot,
+        #endregion
+
+        #region spine
+        [BoneRequired]
+        spine,
+        chest,
+        neck,
+        [BoneRequired]
+        head,
+        #endregion
+
+        #region arm
+        leftShoulder,
+        rightShoulder,
+        [BoneRequired]
+        leftUpperArm,
+        [BoneRequired]
+        rightUpperArm,
+        [BoneRequired]
+        leftLowerArm,
+        [BoneRequired]
+        rightLowerArm,
+        [BoneRequired]
+        leftHand,
+        [BoneRequired]
+        rightHand,
+        #endregion
+
+        leftToes,
+        rightToes,
+        leftEye,
+        rightEye,
+        jaw,
+
+        #region fingers
+        leftThumbProximal,
+        leftThumbIntermediate,
+        leftThumbDistal,
+        leftIndexProximal,
+        leftIndexIntermediate,
+        leftIndexDistal,
+        leftMiddleProximal,
+        leftMiddleIntermediate,
+        leftMiddleDistal,
+        leftRingProximal,
+        leftRingIntermediate,
+        leftRingDistal,
+        leftLittleProximal,
+        leftLittleIntermediate,
+        leftLittleDistal,
+        rightThumbProximal,
+        rightThumbIntermediate,
+        rightThumbDistal,
+        rightIndexProximal,
+        rightIndexIntermediate,
+        rightIndexDistal,
+        rightMiddleProximal,
+        rightMiddleIntermediate,
+        rightMiddleDistal,
+        rightRingProximal,
+        rightRingIntermediate,
+        rightRingDistal,
+        rightLittleProximal,
+        rightLittleIntermediate,
+        rightLittleDistal,
+        #endregion
+
+        upperChest,
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/Humanoid/HumanoidBones.cs.meta b/Assets/VRM10/vrmlib/Runtime/Humanoid/HumanoidBones.cs.meta
new file mode 100644
index 000000000..ff41d2822
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Humanoid/HumanoidBones.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: dd11adf32300d3e4d8a51e126b88ee8d
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Humanoid/SkeletonEstimator.cs b/Assets/VRM10/vrmlib/Runtime/Humanoid/SkeletonEstimator.cs
new file mode 100644
index 000000000..ada561d8f
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Humanoid/SkeletonEstimator.cs
@@ -0,0 +1,434 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+
+namespace VrmLib
+{
+    public static class SkeletonEstimator
+    {
+        static Node GetRoot(IReadOnlyList bones)
+        {
+            var hips = bones.Where(x => x.Parent == null).ToArray();
+            if (hips.Length != 1)
+            {
+                throw new System.Exception("Require unique root");
+            }
+            return hips[0];
+        }
+
+        static Node SelectBone(Func pred, Node parent)
+        {
+            var bones = parent.Children;
+            if (bones == null || bones.Count == 0) throw new Exception("no bones");
+            foreach (var bone in bones)
+            {
+                if (pred(bone, parent))
+                {
+                    return bone;
+                }
+            }
+
+            throw new Exception("not found");
+        }
+
+        static void GetSpineAndHips(Node hips, out Node spine, out Node leg_L, out Node leg_R)
+        {
+            if (hips.Children.Count != 3) throw new System.Exception("Hips require 3 children");
+            spine = SelectBone((l, r) => l.CenterOfDescendant().Y > r.SkeletonLocalPosition.Y, hips);
+            var s = spine;
+            try
+            {
+                leg_L = SelectBone((l, r) => !l.Equals(s) && l.CenterOfDescendant().X < r.SkeletonLocalPosition.X, hips);
+                leg_R = SelectBone((l, r) => !l.Equals(s) && l.CenterOfDescendant().X > r.SkeletonLocalPosition.X, hips);
+            }
+            catch (Exception)
+            {
+                // Z軸で左右を代用
+                leg_L = SelectBone((l, r) => !l.Equals(s) && l.CenterOfDescendant().Z < r.SkeletonLocalPosition.Z, hips);
+                leg_R = SelectBone((l, r) => !l.Equals(s) && l.CenterOfDescendant().Z > r.SkeletonLocalPosition.Z, hips);
+            }
+        }
+
+        static void GetNeckAndArms(Node chest, out Node neck, out Node arm_L, out Node arm_R, Func isLeft)
+        {
+            if (chest.Children.Count != 3) throw new System.Exception("Chest require 3 children");
+            neck = SelectBone((l, r) => l.CenterOfDescendant().Y > r.SkeletonLocalPosition.Y, chest);
+            var n = neck;
+            arm_L = SelectBone((l, r) => !l.Equals(n) && isLeft(l.CenterOfDescendant(), r.SkeletonLocalPosition), chest);
+            arm_R = SelectBone((l, r) => !l.Equals(n) && !isLeft(l.CenterOfDescendant(), r.SkeletonLocalPosition), chest);
+        }
+
+        struct Arm
+        {
+            public Node Shoulder;
+            public Node UpperArm;
+            public Node LowerArm;
+            public Node Hand;
+        }
+
+        static Arm GetArm(Node shoulder)
+        {
+            var bones = shoulder.Traverse().ToArray();
+            switch (bones.Length)
+            {
+                case 0:
+                case 1:
+                case 2:
+                    throw new NotImplementedException();
+
+                case 3:
+                    return new Arm
+                    {
+                        UpperArm = bones[0],
+                        LowerArm = bones[1],
+                        Hand = bones[2],
+                    };
+
+                default:
+                    return new Arm
+                    {
+                        Shoulder = bones[0],
+                        UpperArm = bones[1],
+                        LowerArm = bones[2],
+                        Hand = bones[3],
+                    };
+            }
+        }
+
+        struct Leg
+        {
+            public Node UpperLeg;
+            public Node LowerLeg;
+            public Node Foot;
+            public Node Toes;
+        }
+
+        static Leg GetLeg(Node leg)
+        {
+            var bones = leg.Traverse().Where(x => string.IsNullOrEmpty(x.Name) || !x.Name.ToLower().Contains("buttock")).ToArray();
+            switch (bones.Length)
+            {
+                case 0:
+                case 1:
+                case 2:
+                    throw new NotImplementedException();
+
+                case 3:
+                    return new Leg
+                    {
+                        UpperLeg = bones[0],
+                        LowerLeg = bones[1],
+                        Foot = bones[2],
+                    };
+
+                default:
+                    return new Leg
+                    {
+                        UpperLeg = bones[bones.Length - 4],
+                        LowerLeg = bones[bones.Length - 3],
+                        Foot = bones[bones.Length - 2],
+                        Toes = bones[bones.Length - 1],
+                    };
+            }
+        }
+
+        static public Dictionary DetectByName(Node root, Dictionary map)
+        {
+            var dictionary = new Dictionary();
+            foreach (var bone in root.Traverse())
+            {
+                if (map.TryGetValue(bone.Name, out HumanoidBones humanbone))
+                {
+                    if (humanbone != HumanoidBones.unknown)
+                    {
+                        dictionary.Add(humanbone, bone);
+                    }
+                }
+                else if (Enum.TryParse(bone.Name, true, out HumanoidBones result))
+                {
+                    humanbone = (HumanoidBones)result;
+                    dictionary.Add(humanbone, bone);
+                }
+                else
+                {
+                    // throw new NotImplementedException();
+                }
+            }
+            return dictionary;
+        }
+
+        static public Dictionary DetectByPosition(Node root)
+        {
+            var hips = root.Traverse().First(x =>
+            {
+                // 3分岐以上で
+                //
+                // 子孫が以下の構成持ちうるもの
+                //
+                // spine, head, (upper, lower, hand) x 2
+                // (upper, lower, foot)
+                // (upper, lower, foot)
+                return x.Children.Where(y => y.Traverse().Count() >= 3).Count() >= 3;
+            });
+
+            Node spine, hip_L, hip_R;
+            GetSpineAndHips(hips, out spine, out hip_L, out hip_R);
+            if (hip_L.Equals(hip_R))
+            {
+                throw new Exception();
+            }
+            var legLeft = GetLeg(hip_L);
+            var legRight = GetLeg(hip_R);
+
+            var spineToChest = new List();
+            foreach (var x in spine.Traverse())
+            {
+                spineToChest.Add(x);
+                if (x.Children.Count == 3) break;
+            }
+
+            Func isLeft = default(Func);
+            if (legLeft.UpperLeg.SkeletonLocalPosition.Z == legRight.UpperLeg.SkeletonLocalPosition.Z)
+            {
+                isLeft = (l, r) => l.X < r.X;
+            }
+            else
+            {
+                isLeft = (l, r) => l.Z < r.Z;
+            }
+
+            Node neck, shoulder_L, shoulder_R;
+            GetNeckAndArms(spineToChest.Last(), out neck, out shoulder_L, out shoulder_R, isLeft);
+            var armLeft = GetArm(shoulder_L);
+            var armRight = GetArm(shoulder_R);
+
+            var neckToHead = neck.Traverse().ToArray();
+
+            //
+            //  set result
+            //
+            var skeleton = new Dictionary();
+            Action AddBoneToSkeleton = (b, t) =>
+            {
+                if (t != null)
+                {
+                    t.HumanoidBone = b;
+                    skeleton[b] = t;
+                }
+            };
+
+            AddBoneToSkeleton(HumanoidBones.hips, hips);
+
+            switch (spineToChest.Count)
+            {
+                case 0:
+                    throw new Exception();
+
+                case 1:
+                    AddBoneToSkeleton(HumanoidBones.spine, spineToChest[0]);
+                    break;
+
+                case 2:
+                    AddBoneToSkeleton(HumanoidBones.spine, spineToChest[0]);
+                    AddBoneToSkeleton(HumanoidBones.chest, spineToChest[1]);
+                    break;
+
+                case 3:
+                    AddBoneToSkeleton(HumanoidBones.spine, spineToChest[0]);
+                    AddBoneToSkeleton(HumanoidBones.chest, spineToChest[1]);
+                    AddBoneToSkeleton(HumanoidBones.upperChest, spineToChest[2]);
+                    break;
+
+                default:
+                    AddBoneToSkeleton(HumanoidBones.spine, spineToChest[0]);
+                    AddBoneToSkeleton(HumanoidBones.chest, spineToChest[1]);
+                    AddBoneToSkeleton(HumanoidBones.upperChest, spineToChest.Last());
+                    break;
+            }
+
+            switch (neckToHead.Length)
+            {
+                case 0:
+                    throw new Exception();
+
+                case 1:
+                    AddBoneToSkeleton(HumanoidBones.head, neckToHead[0]);
+                    break;
+
+                case 2:
+                    AddBoneToSkeleton(HumanoidBones.neck, neckToHead[0]);
+                    AddBoneToSkeleton(HumanoidBones.head, neckToHead[1]);
+                    break;
+
+                default:
+                    AddBoneToSkeleton(HumanoidBones.neck, neckToHead[0]);
+                    AddBoneToSkeleton(HumanoidBones.head, neckToHead.Where(x => x.Parent.Children.Count == 1).Last());
+                    break;
+            }
+
+            AddBoneToSkeleton(HumanoidBones.leftUpperLeg, legLeft.UpperLeg);
+            AddBoneToSkeleton(HumanoidBones.leftLowerLeg, legLeft.LowerLeg);
+            AddBoneToSkeleton(HumanoidBones.leftFoot, legLeft.Foot);
+            AddBoneToSkeleton(HumanoidBones.leftToes, legLeft.Toes);
+
+            AddBoneToSkeleton(HumanoidBones.rightUpperLeg, legRight.UpperLeg);
+            AddBoneToSkeleton(HumanoidBones.rightLowerLeg, legRight.LowerLeg);
+            AddBoneToSkeleton(HumanoidBones.rightFoot, legRight.Foot);
+            AddBoneToSkeleton(HumanoidBones.rightToes, legRight.Toes);
+
+            AddBoneToSkeleton(HumanoidBones.leftShoulder, armLeft.Shoulder);
+            AddBoneToSkeleton(HumanoidBones.leftUpperArm, armLeft.UpperArm);
+            AddBoneToSkeleton(HumanoidBones.leftLowerArm, armLeft.LowerArm);
+            AddBoneToSkeleton(HumanoidBones.leftHand, armLeft.Hand);
+
+            AddBoneToSkeleton(HumanoidBones.rightShoulder, armRight.Shoulder);
+            AddBoneToSkeleton(HumanoidBones.rightUpperArm, armRight.UpperArm);
+            AddBoneToSkeleton(HumanoidBones.rightLowerArm, armRight.LowerArm);
+            AddBoneToSkeleton(HumanoidBones.rightHand, armRight.Hand);
+
+            return skeleton;
+        }
+
+        // MVN
+        static Dictionary s_nameBoneMap = new Dictionary
+        {
+            {"Spine1", HumanoidBones.chest},
+            {"Spine2", HumanoidBones.upperChest},
+            {"LeftShoulder", HumanoidBones.leftShoulder},
+            {"LeftArm", HumanoidBones.leftUpperArm},
+            {"LeftForeArm", HumanoidBones.leftLowerArm},
+            {"RightShoulder", HumanoidBones.rightShoulder},
+            {"RightArm", HumanoidBones.rightUpperArm},
+            {"RightForeArm", HumanoidBones.rightLowerArm},
+            {"LeftUpLeg", HumanoidBones.leftUpperLeg},
+            {"LeftLeg", HumanoidBones.leftLowerLeg},
+            {"LeftToeBase", HumanoidBones.leftToes},
+            {"RightUpLeg", HumanoidBones.rightUpperLeg},
+            {"RightLeg", HumanoidBones.rightLowerLeg},
+            {"RightToeBase", HumanoidBones.rightToes},
+        };
+
+        static Dictionary s_nameBoneMapLA = new Dictionary
+        {
+            { "Chest", HumanoidBones.spine},
+            { "Chest2", HumanoidBones.chest},
+            { "LeftCollar", HumanoidBones.leftShoulder},
+            { "LeftShoulder", HumanoidBones.leftUpperArm},
+            { "LeftElbow", HumanoidBones.leftLowerArm},
+            { "LeftWrist", HumanoidBones.leftHand},
+            { "RightCollar", HumanoidBones.rightShoulder},
+            { "RightShoulder", HumanoidBones.rightUpperArm},
+            { "RightElbow", HumanoidBones.rightLowerArm},
+            { "RightWrist", HumanoidBones.rightHand},
+            { "LeftHip", HumanoidBones.leftUpperLeg},
+            { "LeftKnee", HumanoidBones.leftLowerLeg},
+            { "LeftAnkle", HumanoidBones.leftFoot},
+            { "RightHip", HumanoidBones.rightUpperLeg},
+            { "RightKnee", HumanoidBones.rightLowerLeg},
+            { "RightAnkle", HumanoidBones.rightFoot},
+        };
+
+        static Dictionary s_nameBoneMapAccad = new Dictionary
+        {
+            { "root", HumanoidBones.hips},
+            { "lowerback", HumanoidBones.spine},
+            { "upperback", HumanoidBones.chest},
+            { "thorax", HumanoidBones.upperChest},
+            { "neck", HumanoidBones.neck},
+            { "head", HumanoidBones.head},
+            { "lshoulderjoint", HumanoidBones.leftShoulder},
+            { "lhumerus", HumanoidBones.leftUpperArm},
+            { "lradius", HumanoidBones.leftLowerArm},
+            { "lhand", HumanoidBones.leftHand},
+            { "rshoulderjoint", HumanoidBones.rightShoulder},
+            { "rhumerus", HumanoidBones.rightUpperArm},
+            { "rradius", HumanoidBones.rightLowerArm},
+            { "rhand", HumanoidBones.rightHand},
+            { "rfemur", HumanoidBones.rightUpperLeg},
+            { "rtibia", HumanoidBones.rightLowerLeg},
+            { "rfoot", HumanoidBones.rightFoot},
+            { "rtoes", HumanoidBones.rightToes},
+            { "lfemur", HumanoidBones.leftUpperLeg},
+            { "ltibia", HumanoidBones.leftLowerLeg},
+            { "lfoot", HumanoidBones.leftFoot},
+            { "ltoes", HumanoidBones.leftToes},
+        };
+
+        static HumanoidBones[] RequiredBones = new HumanoidBones[]
+        {
+            HumanoidBones.hips,
+            HumanoidBones.spine,
+            HumanoidBones.head,
+            HumanoidBones.leftUpperArm,
+            HumanoidBones.leftLowerArm,
+            HumanoidBones.leftHand,
+            HumanoidBones.leftUpperLeg,
+            HumanoidBones.leftLowerLeg,
+            HumanoidBones.leftFoot,
+            HumanoidBones.rightUpperArm,
+            HumanoidBones.rightLowerArm,
+            HumanoidBones.rightHand,
+            HumanoidBones.rightUpperLeg,
+            HumanoidBones.rightLowerLeg,
+            HumanoidBones.rightFoot,
+         };
+
+        static bool HasAllHumanRequiredBone(Dictionary dict)
+        {
+            foreach (var bone in RequiredBones)
+            {
+                if (!dict.ContainsKey(bone))
+                {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        static Dictionary _Detect(Node root)
+        {
+            var list = new List>();
+            foreach (var map in new[] { s_nameBoneMap, s_nameBoneMapLA, s_nameBoneMapAccad })
+            {
+                try
+                {
+                    var result = DetectByName(root, map);
+                    if (result != null)
+                    {
+                        list.Add(result);
+                    }
+                }
+                catch (Exception)
+                { }
+            }
+
+            foreach (var map in list.OrderByDescending(x => x.Count))
+            {
+                if (HasAllHumanRequiredBone(map))
+                {
+                    return map;
+                }
+            }
+
+            return DetectByPosition(root);
+        }
+
+        static public Dictionary Detect(Node root)
+        {
+            try
+            {
+                var dict = _Detect(root);
+                foreach (var kv in dict)
+                {
+                    kv.Value.HumanoidBone = kv.Key;
+                }
+                return dict;
+            }
+            catch
+            {
+                return null;
+            }
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/Humanoid/SkeletonEstimator.cs.meta b/Assets/VRM10/vrmlib/Runtime/Humanoid/SkeletonEstimator.cs.meta
new file mode 100644
index 000000000..a37f8ed97
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Humanoid/SkeletonEstimator.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: eba33276386fae64fa0a02ab6408c295
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Humanoid/SkeletonMeshUtility.cs b/Assets/VRM10/vrmlib/Runtime/Humanoid/SkeletonMeshUtility.cs
new file mode 100644
index 000000000..80dabe170
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Humanoid/SkeletonMeshUtility.cs
@@ -0,0 +1,270 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+
+namespace VrmLib
+{
+    #pragma warning disable 0649
+    struct BoneWeight
+    {
+        public int boneIndex0;
+        public int boneIndex1;
+        public int boneIndex2;
+        public int boneIndex3;
+
+        public float weight0;
+        public float weight1;
+        public float weight2;
+        public float weight3;
+    }
+    #pragma warning restore
+
+    class MeshBuilder
+    {
+        public static BoneHeadTail[] Bones = new BoneHeadTail[]
+        {
+            new BoneHeadTail(HumanoidBones.hips, HumanoidBones.spine, 0.1f, 0.06f),
+            new BoneHeadTail(HumanoidBones.spine, HumanoidBones.chest),
+            new BoneHeadTail(HumanoidBones.chest, HumanoidBones.neck, 0.1f, 0.06f),
+            new BoneHeadTail(HumanoidBones.neck, HumanoidBones.head, 0.03f, 0.03f),
+            new BoneHeadTail(HumanoidBones.head, new Vector3(0, 0.1f, 0), 0.1f, 0.1f),
+
+            new BoneHeadTail(HumanoidBones.leftShoulder, HumanoidBones.leftUpperArm),
+            new BoneHeadTail(HumanoidBones.leftUpperArm, HumanoidBones.leftLowerArm),
+            new BoneHeadTail(HumanoidBones.leftLowerArm, HumanoidBones.leftHand),
+            new BoneHeadTail(HumanoidBones.leftHand, new Vector3(-0.1f, 0, 0)),
+
+            new BoneHeadTail(HumanoidBones.leftUpperLeg, HumanoidBones.leftLowerLeg),
+            new BoneHeadTail(HumanoidBones.leftLowerLeg, HumanoidBones.leftFoot),
+            new BoneHeadTail(HumanoidBones.leftFoot, HumanoidBones.leftToes),
+            new BoneHeadTail(HumanoidBones.leftToes, new Vector3(0, 0, 0.1f)),
+
+            new BoneHeadTail(HumanoidBones.rightShoulder, HumanoidBones.rightUpperArm),
+            new BoneHeadTail(HumanoidBones.rightUpperArm, HumanoidBones.rightLowerArm),
+            new BoneHeadTail(HumanoidBones.rightLowerArm, HumanoidBones.rightHand),
+            new BoneHeadTail(HumanoidBones.rightHand, new Vector3(0.1f, 0, 0)),
+
+            new BoneHeadTail(HumanoidBones.rightUpperLeg, HumanoidBones.rightLowerLeg),
+            new BoneHeadTail(HumanoidBones.rightLowerLeg, HumanoidBones.rightFoot),
+            new BoneHeadTail(HumanoidBones.rightFoot, HumanoidBones.rightToes),
+            new BoneHeadTail(HumanoidBones.rightToes, new Vector3(0, 0, 0.1f)),
+        };
+
+        public void Build(List bones)
+        {
+            foreach (var headTail in Bones)
+            {
+                var head = bones.FirstOrDefault(x => x.HumanoidBone == headTail.Head);
+                if (head != null)
+                {
+                    Node tail = default(Node);
+                    if (headTail.Tail != HumanoidBones.unknown)
+                    {
+                        tail = bones.FirstOrDefault(x => x.HumanoidBone == headTail.Tail);
+                    }
+
+                    if (tail != null)
+                    {
+                        AddBone(head.SkeletonLocalPosition, tail.SkeletonLocalPosition, bones.IndexOf(head), headTail.XWidth, headTail.ZWidth);
+                    }
+                    else if(headTail.TailOffset!=Vector3.Zero)
+                    {
+                        AddBone(head.SkeletonLocalPosition, head.SkeletonLocalPosition + headTail.TailOffset, bones.IndexOf(head), headTail.XWidth, headTail.ZWidth);
+                    }
+                }
+                else
+                {
+                    Console.Error.WriteLine($"{headTail.Head} not found");
+                }
+            }
+        }
+
+        List m_positioins = new List();
+        List m_indices = new List();
+        List m_boneWeights = new List();
+
+        void AddBone(Vector3 head, Vector3 tail, int boneIndex, float xWidth, float zWidth)
+        {
+            var yaxis = Vector3.Normalize(tail - head);
+            Vector3 xaxis;
+            Vector3 zaxis;
+            if (Vector3.Dot(yaxis, Vector3.UnitZ) >= 1.0f - float.Epsilon)
+            {
+                // ほぼZ軸
+                xaxis = Vector3.UnitX;
+                zaxis = -Vector3.UnitY;
+            }
+            else
+            {
+                xaxis = Vector3.Normalize(Vector3.Cross(yaxis, Vector3.UnitZ));
+                zaxis = Vector3.UnitZ;
+            }
+            AddBox((head + tail) * 0.5f,
+                xaxis * xWidth,
+                (tail - head) * 0.5f,
+                zaxis * zWidth,
+                boneIndex);
+        }
+
+        void AddBox(Vector3 center, Vector3 xaxis, Vector3 yaxis, Vector3 zaxis, int boneIndex)
+        {
+            AddQuad(
+                center - yaxis - xaxis - zaxis,
+                center - yaxis + xaxis - zaxis,
+                center - yaxis + xaxis + zaxis,
+                center - yaxis - xaxis + zaxis,
+                boneIndex);
+            AddQuad(
+                center + yaxis - xaxis - zaxis,
+                center + yaxis + xaxis - zaxis,
+                center + yaxis + xaxis + zaxis,
+                center + yaxis - xaxis + zaxis,
+                boneIndex, true);
+            AddQuad(
+                center - xaxis - yaxis - zaxis,
+                center - xaxis + yaxis - zaxis,
+                center - xaxis + yaxis + zaxis,
+                center - xaxis - yaxis + zaxis,
+                boneIndex, true);
+            AddQuad(
+                center + xaxis - yaxis - zaxis,
+                center + xaxis + yaxis - zaxis,
+                center + xaxis + yaxis + zaxis,
+                center + xaxis - yaxis + zaxis,
+                boneIndex);
+            AddQuad(
+                center - zaxis - xaxis - yaxis,
+                center - zaxis + xaxis - yaxis,
+                center - zaxis + xaxis + yaxis,
+                center - zaxis - xaxis + yaxis,
+                boneIndex, true);
+            AddQuad(
+                center + zaxis - xaxis - yaxis,
+                center + zaxis + xaxis - yaxis,
+                center + zaxis + xaxis + yaxis,
+                center + zaxis - xaxis + yaxis,
+                boneIndex);
+        }
+
+        void AddQuad(Vector3 v0, Vector3 v1, Vector3 v2, Vector3 v3, int boneIndex, bool reverse = false)
+        {
+            var i = m_positioins.Count;
+            if(float.IsNaN(v0.X) || float.IsNaN(v0.Y) || float.IsNaN(v0.Z))
+            {
+                throw new Exception();
+            }
+            m_positioins.Add(v0);
+
+            if(float.IsNaN(v1.X) || float.IsNaN(v1.Y) || float.IsNaN(v1.Z))
+            {
+                throw new Exception();
+            }
+            m_positioins.Add(v1);
+
+            if(float.IsNaN(v2.X) || float.IsNaN(v2.Y) || float.IsNaN(v2.Z))
+            {
+                throw new Exception();
+            }
+            m_positioins.Add(v2);
+
+            if(float.IsNaN(v3.X) || float.IsNaN(v3.Y) || float.IsNaN(v3.Z))
+            {
+                throw new Exception();
+            }
+            m_positioins.Add(v3);
+
+            var bw = new BoneWeight
+            {
+                boneIndex0 = boneIndex,
+                weight0 = 1.0f,
+            };
+            m_boneWeights.Add(bw);
+            m_boneWeights.Add(bw);
+            m_boneWeights.Add(bw);
+            m_boneWeights.Add(bw);
+
+            if (reverse)
+            {
+                m_indices.Add(i + 3);
+                m_indices.Add(i + 2);
+                m_indices.Add(i + 1);
+
+                m_indices.Add(i + 1);
+                m_indices.Add(i);
+                m_indices.Add(i + 3);
+            }
+            else
+            {
+                m_indices.Add(i);
+                m_indices.Add(i + 1);
+                m_indices.Add(i + 2);
+
+                m_indices.Add(i + 2);
+                m_indices.Add(i + 3);
+                m_indices.Add(i);
+            }
+        }
+
+        public Mesh CreateMesh()
+        {
+            if(m_positioins.Any(x => float.IsNaN(x.X) || float.IsNaN(x.Y) || float.IsNaN(x.Z)))
+            {
+                throw new Exception();
+            }
+
+            var mesh = new Mesh();
+            mesh.VertexBuffer = new VertexBuffer();
+            mesh.VertexBuffer.Add(VertexBuffer.PositionKey,
+                m_positioins.ToArray());
+
+            mesh.VertexBuffer.Add(VertexBuffer.JointKey,
+                m_boneWeights.Select(x => new SkinJoints(
+                    (ushort)x.boneIndex0,
+                    (ushort)x.boneIndex1,
+                    (ushort)x.boneIndex2,
+                    (ushort)x.boneIndex3)).ToArray());
+
+            mesh.VertexBuffer.Add(VertexBuffer.WeightKey,
+                m_boneWeights.Select(x => new Vector4(
+                    x.weight0,
+                    x.weight1,
+                    x.weight2,
+                    x.weight3
+                )).ToArray());
+
+            mesh.IndexBuffer = BufferAccessor.Create(m_indices.ToArray());
+
+            mesh.Submeshes.Add(new Submesh(0, mesh.IndexBuffer.Count, null));
+
+            return mesh;
+        }
+    }
+
+    struct BoneHeadTail
+    {
+        public HumanoidBones Head;
+        public HumanoidBones Tail;
+        public Vector3 TailOffset;
+        public float XWidth;
+        public float ZWidth;
+
+        public BoneHeadTail(HumanoidBones head, HumanoidBones tail, float xWidth = 0.05f, float zWidth = 0.05f)
+        {
+            Head = head;
+            Tail = tail;
+            TailOffset = Vector3.Zero;
+            XWidth = xWidth;
+            ZWidth = zWidth;
+        }
+
+        public BoneHeadTail(HumanoidBones head, Vector3 tailOffset, float xWidth = 0.05f, float zWidth = 0.05f)
+        {
+            Head = head;
+            Tail = HumanoidBones.unknown;
+            TailOffset = tailOffset;
+            XWidth = xWidth;
+            ZWidth = zWidth;
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/Humanoid/SkeletonMeshUtility.cs.meta b/Assets/VRM10/vrmlib/Runtime/Humanoid/SkeletonMeshUtility.cs.meta
new file mode 100644
index 000000000..82f98c366
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Humanoid/SkeletonMeshUtility.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a8f1deadd49c23a40b05e95f5d5fcfe6
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/ImportExport.meta b/Assets/VRM10/vrmlib/Runtime/ImportExport.meta
new file mode 100644
index 000000000..b5f2845b8
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ImportExport.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 9089f6791c42b144ca66b29ea0f186db
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/ImportExport/Glb.cs b/Assets/VRM10/vrmlib/Runtime/ImportExport/Glb.cs
new file mode 100644
index 000000000..7d8b1c779
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ImportExport/Glb.cs
@@ -0,0 +1,263 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace VrmLib
+{
+    /// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification
+
+    public static class GlbHeader
+    {
+        public static readonly byte[] GLB_MAGIC = new byte[] { 0x67, 0x6C, 0x54, 0x46 };  // "glTF"
+        public static readonly byte[] GLB_VERSION = new byte[] { 2, 0, 0, 0 };
+
+        public static void WriteTo(Stream s)
+        {
+            s.Write(GLB_MAGIC, 0, GLB_MAGIC.Length);
+            s.Write(GLB_VERSION, 0, GLB_VERSION.Length);
+        }
+
+        public static int TryParse(ArraySegment bytes, out int pos, out Exception message)
+        {
+            pos = 0;
+
+            if (!bytes.Slice(0, 4).SequenceEqual(GLB_MAGIC))
+            {
+                message = new FormatException("invalid magic");
+                return 0;
+            }
+            pos += 4;
+
+            if (!bytes.Slice(pos, 4).SequenceEqual(GLB_VERSION))
+            {
+                message = new FormatException("invalid magic");
+                return 0;
+            }
+            pos += 4;
+
+            var totalLength = BitConverter.ToInt32(bytes.Array, bytes.Offset + pos);
+            pos += 4;
+
+            message = null;
+            return totalLength;
+        }
+    }
+
+    public enum GlbChunkType : uint
+    {
+        JSON = 0x4E4F534A,
+        BIN = 0x004E4942,
+    }
+
+    public struct GlbChunk
+    {
+        public GlbChunkType ChunkType;
+        public ArraySegment Bytes;
+
+        public GlbChunk(GlbChunkType type, ArraySegment bytes)
+        {
+            ChunkType = type;
+            Bytes = bytes;
+        }
+
+        public static GlbChunk CreateJson(string json)
+        {
+            return CreateJson(new ArraySegment(Encoding.UTF8.GetBytes(json)));
+        }
+
+        public static GlbChunk CreateJson(ArraySegment bytes)
+        {
+            return new GlbChunk(GlbChunkType.JSON, bytes);
+        }
+
+        public static GlbChunk CreateBin(ArraySegment bytes)
+        {
+            return new GlbChunk(GlbChunkType.BIN, bytes);
+        }
+
+        byte GetPaddingByte()
+        {
+            // chunk type
+            switch (ChunkType)
+            {
+                case GlbChunkType.JSON:
+                    return 0x20;
+
+                case GlbChunkType.BIN:
+                    return 0x00;
+
+                default:
+                    throw new Exception("unknown chunk type: " + ChunkType);
+            }
+        }
+
+        public int WriteTo(Stream s)
+        {
+            // padding
+            var paddingValue = Bytes.Count % 4;
+            var padding = (paddingValue > 0) ? 4 - paddingValue : 0;
+
+            // size
+            var bytes = BitConverter.GetBytes((int)(Bytes.Count + padding));
+            s.Write(bytes, 0, bytes.Length);
+
+            // chunk type
+            switch (ChunkType)
+            {
+                case GlbChunkType.JSON:
+                    s.WriteByte((byte)'J');
+                    s.WriteByte((byte)'S');
+                    s.WriteByte((byte)'O');
+                    s.WriteByte((byte)'N');
+                    break;
+
+                case GlbChunkType.BIN:
+                    s.WriteByte((byte)'B');
+                    s.WriteByte((byte)'I');
+                    s.WriteByte((byte)'N');
+                    s.WriteByte((byte)0);
+                    break;
+
+                default:
+                    throw new Exception("unknown chunk type: " + ChunkType);
+            }
+
+            // body
+            s.Write(Bytes.Array, Bytes.Offset, Bytes.Count);
+
+            // 4byte align
+            var pad = GetPaddingByte();
+            for (int i = 0; i < padding; ++i)
+            {
+                s.WriteByte(pad);
+            }
+
+            return 4 + 4 + Bytes.Count + padding;
+        }
+    }
+
+    public struct Glb
+    {
+        public readonly GlbChunk Json;
+        public readonly GlbChunk Binary;
+
+        public Glb(GlbChunk json, GlbChunk binary)
+        {
+            if (json.ChunkType != GlbChunkType.JSON) throw new ArgumentException();
+            Json = json;
+            if (binary.ChunkType != GlbChunkType.BIN) throw new ArgumentException();
+            Binary = binary;
+        }
+
+        public byte[] ToBytes()
+        {
+            using (var s = new MemoryStream())
+            {
+                GlbHeader.WriteTo(s);
+
+                var pos = s.Position;
+                s.Position += 4; // skip total size
+
+                int size = 12;
+
+                {
+                    size += Json.WriteTo(s);
+                }
+                {
+                    size += Binary.WriteTo(s);
+                }
+
+                s.Position = pos;
+                var bytes = BitConverter.GetBytes(size);
+                s.Write(bytes, 0, bytes.Length);
+
+                return s.ToArray();
+            }
+        }
+
+        public static GlbChunkType ToChunkType(string src)
+        {
+            switch (src)
+            {
+                case "BIN":
+                    return GlbChunkType.BIN;
+
+                case "JSON":
+                    return GlbChunkType.JSON;
+
+                default:
+                    throw new FormatException("unknown chunk type: " + src);
+            }
+        }
+
+        public static Glb Parse(Byte[] bytes)
+        {
+            if (TryParse(bytes, out Glb glb, out Exception ex))
+            {
+                return glb;
+            }
+            else
+            {
+                throw ex;
+            }
+        }
+
+        public static bool TryParse(Byte[] bytes, out Glb glb, out Exception ex)
+        {
+            return TryParse(new ArraySegment(bytes), out glb, out ex);
+        }
+
+        public static bool TryParse(ArraySegment bytes, out Glb glb, out Exception ex)
+        {
+            glb = default(Glb);
+            if (bytes.Count == 0)
+            {
+                ex = new Exception("empty bytes");
+                return false;
+            }
+
+            var length = GlbHeader.TryParse(bytes, out int pos, out ex);
+            if (length == 0)
+            {
+                return false;
+            }
+            bytes = bytes.Slice(0, length);
+
+            try
+            {
+                var chunks = new List();
+                while (pos < bytes.Count)
+                {
+                    var chunkDataSize = BitConverter.ToInt32(bytes.Array, bytes.Offset + pos);
+                    pos += 4;
+
+                    //var type = (GlbChunkType)BitConverter.ToUInt32(bytes, pos);
+                    var chunkTypeBytes = bytes.Slice(pos, 4).Where(x => x != 0).ToArray();
+                    var chunkTypeStr = Encoding.ASCII.GetString(chunkTypeBytes);
+                    var type = ToChunkType(chunkTypeStr);
+                    pos += 4;
+
+                    chunks.Add(new GlbChunk
+                    {
+                        ChunkType = type,
+                        Bytes = bytes.Slice(pos, chunkDataSize)
+                    });
+
+                    pos += chunkDataSize;
+                }
+
+                glb = new Glb(chunks[0], chunks[1]);
+                return true;
+            }
+            catch (Exception _ex)
+            {
+                ex = _ex;
+                return false;
+            }
+        }
+
+
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/ImportExport/Glb.cs.meta b/Assets/VRM10/vrmlib/Runtime/ImportExport/Glb.cs.meta
new file mode 100644
index 000000000..6a6b3fe5f
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ImportExport/Glb.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e8b6e06dd9822ef41bab155e172fe857
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/ImportExport/IVrmExporter.cs b/Assets/VRM10/vrmlib/Runtime/ImportExport/IVrmExporter.cs
new file mode 100644
index 000000000..b47bb7ea9
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ImportExport/IVrmExporter.cs
@@ -0,0 +1,136 @@
+using System;
+using System.Collections.Generic;
+
+namespace VrmLib
+{
+    public interface IVrmExporter
+    {
+        byte[] ToBytes();
+
+        #region GLTF
+        void ExportAsset(Model model);
+        void Reserve(int bytesLength);
+        void ExportImageAndTextures(List images, List textures);
+        void ExportMaterialPBR(Material src, PBRMaterial pbr, List textures);
+        void ExportMaterialUnlit(Material src, UnlitMaterial unlit, List textures);
+        void ExportMaterialMToon(Material src, MToonMaterial mtoon, List textures);
+        void ExportMeshes(List groups, List materials, ExportArgs option);
+        void ExportNodes(Node root, List nodes, List groups, ExportArgs option);
+        void ExportAnimations(List animations, List nodes, ExportArgs option);
+        #endregion
+
+        #region VRM
+        void ExportVrmMeta(Vrm src, List textures);
+        void ExportVrmHumanoid(Dictionary map, List nodes);
+        void ExportVrmMaterialProperties(List materials, List textures);
+        void ExportVrmExpression(ExpressionManager expression, List meshes, List materials, List nodes);
+        void ExportVrmSpringBone(SpringBoneManager springBone, List nodes);
+        void ExportVrmFirstPersonAndLookAt(FirstPerson firstPerson, LookAt lookat, List meshes, List nodes);
+        void ExportVrmEnd();
+        #endregion
+    }
+
+    public static class IExporterExtensions
+    {
+        public static byte[] Export(this IVrmExporter exporter, Model m, ExportArgs option)
+        {
+            exporter.ExportAsset(m);
+
+            ///
+            /// 必要な容量を先に確保
+            /// (sparseは考慮してないので大きめ)
+            ///
+            {
+                var reserveBytes = 0;
+                // image
+                foreach (var image in m.Images)
+                {
+                    reserveBytes += image.Bytes.Count;
+                }
+                // mesh
+                foreach (var g in m.MeshGroups)
+                {
+                    foreach (var mesh in g.Meshes)
+                    {
+                        // 頂点バッファ
+                        reserveBytes += mesh.IndexBuffer.ByteLength;
+                        foreach (var kv in mesh.VertexBuffer)
+                        {
+                            reserveBytes += kv.Value.ByteLength;
+                        }
+                        // morph
+                        foreach (var morph in mesh.MorphTargets)
+                        {
+                            foreach (var kv in morph.VertexBuffer)
+                            {
+                                reserveBytes += kv.Value.ByteLength;
+                            }
+                        }
+                    }
+                }
+                exporter.Reserve(reserveBytes);
+            }
+
+            exporter.ExportImageAndTextures(m.Images, m.Textures);
+
+            // material
+            foreach (var src in m.Materials)
+            {
+                if (src is MToonMaterial mtoon)
+                {
+                    exporter.ExportMaterialMToon(src, mtoon, m.Textures);
+                }
+                else if (src is UnlitMaterial unlit)
+                {
+                    exporter.ExportMaterialUnlit(src, unlit, m.Textures);
+                }
+                else if (src is PBRMaterial pbr)
+                {
+                    exporter.ExportMaterialPBR(src, pbr, m.Textures);
+                }
+                else
+                {
+                    throw new NotImplementedException();
+                }
+            }
+
+            // mesh
+            exporter.ExportMeshes(m.MeshGroups, m.Materials, option);
+
+            // node
+            exporter.ExportNodes(m.Root, m.Nodes, m.MeshGroups, option);
+
+            // animation
+            exporter.ExportAnimations(m.Animations, m.Nodes, option);
+
+            if (option.vrm)
+            {
+                ExportVrm(exporter, m);
+            }
+
+            return exporter.ToBytes();
+        }
+
+        static void ExportVrm(IVrmExporter exporter, Model m)
+        {
+            if (m.Vrm == null)
+            {
+                return;
+            }
+
+            exporter.ExportVrmMeta(m.Vrm, m.Textures);
+
+            exporter.ExportVrmHumanoid(m.GetBoneMap(), m.Nodes);
+
+            exporter.ExportVrmMaterialProperties(m.Materials, m.Textures);
+
+            exporter.ExportVrmExpression(m.Vrm.ExpressionManager, m.MeshGroups, m.Materials, m.Nodes);
+
+            exporter.ExportVrmSpringBone(m.Vrm.SpringBone, m.Nodes);
+
+            exporter.ExportVrmFirstPersonAndLookAt(m.Vrm.FirstPerson, m.Vrm.LookAt, m.MeshGroups, m.Nodes);
+
+            exporter.ExportVrmEnd();
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/ImportExport/IVrmExporter.cs.meta b/Assets/VRM10/vrmlib/Runtime/ImportExport/IVrmExporter.cs.meta
new file mode 100644
index 000000000..39837ad1e
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ImportExport/IVrmExporter.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: be395fef7a307d5428ae184a77bda86e
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/ImportExport/IVrmStorage.cs b/Assets/VRM10/vrmlib/Runtime/ImportExport/IVrmStorage.cs
new file mode 100644
index 000000000..905008a27
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ImportExport/IVrmStorage.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+
+namespace VrmLib
+{
+    public interface IVrmStorage
+    {
+        ArraySegment OriginalJson { get; }
+
+        #region glTF import
+        string AssetVersion { get; }
+        string AssetMinVersion { get; }
+        string AssetGenerator { get; }
+        string AssetCopyright { get; }
+        int NodeCount { get; }
+        Node CreateNode(int index);
+        IEnumerable GetChildNodeIndices(int i);
+        int ImageCount { get; }
+        Image CreateImage(int index);
+        int TextureCount { get; }
+        Texture CreateTexture(int index, List images);
+        int MaterialCount { get; }
+        Material CreateMaterial(int index, List textures);
+        int SkinCount { get; }
+        Skin CreateSkin(int index, List nodes);
+        int MeshCount { get; }
+        MeshGroup CreateMesh(int index, List materials);
+        (int, int) GetNodeMeshSkin(int index);
+        int AnimationCount { get; }
+        Animation CreateAnimation(int index, List nodes);
+        #endregion
+
+        #region VRM
+        bool HasVrm { get; }
+        Meta CreateVrmMeta(List textures);
+        string VrmExporterVersion { get; }
+        string VrmSpecVersion { get; }
+        void LoadVrmHumanoid(List nodes);
+        ExpressionManager CreateVrmExpression(List meshGroups, List materials, List nodes);
+        SpringBoneManager CreateVrmSpringBone(List nodes);
+        FirstPerson CreateVrmFirstPerson(List nodes, List meshGroups);
+        LookAt CreateVrmLookAt();
+        #endregion
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/ImportExport/IVrmStorage.cs.meta b/Assets/VRM10/vrmlib/Runtime/ImportExport/IVrmStorage.cs.meta
new file mode 100644
index 000000000..440e16713
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ImportExport/IVrmStorage.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: dc7a9b66200d54043bef5c969aa774e7
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/ImportExport/ModelLoader.cs b/Assets/VRM10/vrmlib/Runtime/ImportExport/ModelLoader.cs
new file mode 100644
index 000000000..1f5d6f262
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ImportExport/ModelLoader.cs
@@ -0,0 +1,121 @@
+using System;
+using System.Linq;
+
+namespace VrmLib
+{
+    public static class ModelLoader
+    {
+        public static Model Load(IVrmStorage storage, string rootName, bool estimateHumanoid = false)
+        {
+            if (storage == null)
+            {
+                return null;
+            }
+
+            var model = new Model(Coordinates.Gltf)
+            {
+                AssetVersion = storage.AssetVersion,
+                AssetGenerator = storage.AssetGenerator,
+                AssetCopyright = storage.AssetCopyright,
+                AssetMinVersion = storage.AssetMinVersion,
+                OriginalJson = storage.OriginalJson,
+            };
+
+            // node
+            model.Root.Name = rootName;
+            for (var i = 0; i < storage.NodeCount; ++i)
+            {
+                var node = storage.CreateNode(i);
+                model.Nodes.Add(node);
+            }
+            for (var i = 0; i < model.Nodes.Count; ++i)
+            {
+                var parent = model.Nodes[i];
+                foreach (var j in storage.GetChildNodeIndices(i))
+                {
+                    var child = model.Nodes[j];
+                    parent.Add(child);
+                }
+            }
+            foreach (var x in model.Nodes)
+            {
+                if (x.Parent == null)
+                {
+                    model.Root.Add(x);
+                }
+            }
+
+            // image
+            model.Images.AddRange(Enumerable.Range(0, storage.ImageCount).Select(x => storage.CreateImage(x)));
+
+            // texture
+            model.Textures.AddRange(Enumerable.Range(0, storage.TextureCount).Select(x => storage.CreateTexture(x, model.Images)));
+
+            // material
+            model.Materials.AddRange(Enumerable.Range(0, storage.MaterialCount).Select(x => storage.CreateMaterial(x, model.Textures)));
+
+            // skin
+            model.Skins.AddRange(Enumerable.Range(0, storage.SkinCount).Select(x => storage.CreateSkin(x, model.Nodes)));
+
+            // mesh
+            model.MeshGroups.AddRange(Enumerable.Range(0, storage.MeshCount).Select(x => storage.CreateMesh(x, model.Materials)));
+
+            // skin
+            for (int i = 0; i < storage.NodeCount; ++i)
+            {
+                var (meshIndex, skinIndex) = storage.GetNodeMeshSkin(i);
+                if (meshIndex >= 0 && meshIndex < model.MeshGroups.Count)
+                {
+                    var node = model.Nodes[i];
+                    var mesh = model.MeshGroups[meshIndex];
+                    node.MeshGroup = mesh;
+                    if (skinIndex >= 0 && skinIndex < model.Skins.Count)
+                    {
+                        var skin = model.Skins[skinIndex];
+                        mesh.Skin = skin;
+                    }
+                }
+            }
+
+            // animation
+            model.Animations.AddRange(Enumerable.Range(0, storage.AnimationCount).Select(x => storage.CreateAnimation(x, model.Nodes)));
+
+            // VRM
+            if (!LoadVrm(model, storage) && estimateHumanoid)
+            {
+                // VRMでないときにボーン推定する
+                model.HumanoidBoneEstimate();
+            }
+
+            return model;
+        }
+
+        static bool LoadVrm(Model model, IVrmStorage storage)
+        {
+            if (!storage.HasVrm)
+            {
+                return false;
+            }
+
+            var Vrm = new Vrm(storage.CreateVrmMeta(model.Textures), storage.VrmExporterVersion, storage.VrmSpecVersion);
+            model.Vrm = Vrm;
+
+            storage.LoadVrmHumanoid(model.Nodes);
+
+            if (!model.CheckVrmHumanoid())
+            {
+                throw new Exception("CheckVrmHumanoid");
+            }
+
+            Vrm.ExpressionManager = storage.CreateVrmExpression(model.MeshGroups, model.Materials, model.Nodes);
+
+            Vrm.SpringBone = storage.CreateVrmSpringBone(model.Nodes);
+
+            Vrm.FirstPerson = storage.CreateVrmFirstPerson(model.Nodes, model.MeshGroups);
+
+            Vrm.LookAt = storage.CreateVrmLookAt();
+
+            return true;
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/ImportExport/ModelLoader.cs.meta b/Assets/VRM10/vrmlib/Runtime/ImportExport/ModelLoader.cs.meta
new file mode 100644
index 000000000..9930454b6
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ImportExport/ModelLoader.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 42576b7471f4a4e4fb25c1815a92f44b
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/IndexExtensions.cs b/Assets/VRM10/vrmlib/Runtime/IndexExtensions.cs
new file mode 100644
index 000000000..5c9d233a4
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/IndexExtensions.cs
@@ -0,0 +1,25 @@
+namespace VrmLib
+{
+    public static class IndexExtensions
+    {
+        public static bool TryGetValidIndex(this int? index, int count, out int outValue)
+        {
+            outValue = -1;
+            if (!index.HasValue)
+            {
+                return false;
+            }
+            var value = index.Value;
+            if (value < 0)
+            {
+                return false;
+            }
+            if (value >= count)
+            {
+                return false;
+            }
+            outValue = value;
+            return true;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Runtime/IndexExtensions.cs.meta b/Assets/VRM10/vrmlib/Runtime/IndexExtensions.cs.meta
new file mode 100644
index 000000000..ab1b7926b
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/IndexExtensions.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ad2e7d02ba5894344a2f58d9d2157f98
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/MToon.meta b/Assets/VRM10/vrmlib/Runtime/MToon.meta
new file mode 100644
index 000000000..cf0c676df
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/MToon.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 42b84fc3fef628142a69b9c3638867e9
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/MToon/Enums.cs b/Assets/VRM10/vrmlib/Runtime/MToon/Enums.cs
new file mode 100644
index 000000000..121ac116f
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/MToon/Enums.cs
@@ -0,0 +1,51 @@
+namespace VrmLib.MToon
+{
+    public enum DebugMode
+    {
+        None = 0,
+        Normal = 1,
+        LitShadeRate = 2,
+    }
+
+    public enum OutlineColorMode
+    {
+        FixedColor = 0,
+        MixedLighting = 1,
+    }
+
+    public enum OutlineWidthMode
+    {
+        None = 0,
+        WorldCoordinates = 1,
+        ScreenCoordinates = 2,
+    }
+
+    public enum RenderMode
+    {
+        Opaque = 0,
+        Cutout = 1,
+        Transparent = 2,
+        TransparentWithZWrite = 3,
+    }
+
+    public enum CullMode
+    {
+        Off = 0,
+        Front = 1,
+        Back = 2,
+    }
+
+    public enum RotationUnit
+    {
+        Rounds = 0,
+        Degrees = 1,
+        Radians = 2
+    }
+
+    public struct RenderQueueRequirement
+    {
+        public int DefaultValue;
+        public int MinValue;
+        public int MaxValue;
+    }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Runtime/MToon/Enums.cs.meta b/Assets/VRM10/vrmlib/Runtime/MToon/Enums.cs.meta
new file mode 100644
index 000000000..6f620cf15
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/MToon/Enums.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 65d99cc4b12aae949ab5ce97d343348f
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/MToon/EnumsEx.cs b/Assets/VRM10/vrmlib/Runtime/MToon/EnumsEx.cs
new file mode 100644
index 000000000..b7b0c2e15
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/MToon/EnumsEx.cs
@@ -0,0 +1,27 @@
+namespace VrmLib.MToon
+{
+    public enum RenderQueue
+    {
+        Background = 1000,
+        Geometry = 2000,
+        AlphaTest = 2450,
+        GeometryLast = 2500,
+        Transparent = 3000,
+        Overlay = 4000
+    }
+
+    public enum BlendMode
+    {
+        Zero = 0,
+        One = 1,
+        DstColor = 2,
+        SrcColor = 3,
+        OneMinusDstColor = 4,
+        SrcAlpha = 5,
+        OneMinusSrcColor = 6,
+        DstAlpha = 7,
+        OneMinusDstAlpha = 8,
+        SrcAlphaSaturate = 9,
+        OneMinusSrcAlpha = 10
+    }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Runtime/MToon/EnumsEx.cs.meta b/Assets/VRM10/vrmlib/Runtime/MToon/EnumsEx.cs.meta
new file mode 100644
index 000000000..8658da65c
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/MToon/EnumsEx.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0e1d9671390f90c4e99f4d840cf5a417
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/MToon/MToonDefinition.cs b/Assets/VRM10/vrmlib/Runtime/MToon/MToonDefinition.cs
new file mode 100644
index 000000000..6627620b8
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/MToon/MToonDefinition.cs
@@ -0,0 +1,111 @@
+using System.Numerics;
+using Color = VrmLib.LinearColor;
+using Texture2D = VrmLib.TextureInfo;
+
+namespace VrmLib.MToon
+{
+    public class MToonDefinition
+    {
+        public MetaDefinition Meta;
+        public RenderingDefinition Rendering;
+        public ColorDefinition Color;
+        public LightingDefinition Lighting;
+        public EmissionDefinition Emission;
+        public MatCapDefinition MatCap;
+        public RimDefinition Rim;
+        public OutlineDefinition Outline;
+        public TextureUvCoordsDefinition TextureOption;
+    }
+
+    public class MetaDefinition
+    {
+        public string Implementation;
+        public int VersionNumber;
+    }
+
+    public class RenderingDefinition
+    {
+        public RenderMode RenderMode;
+        public CullMode CullMode;
+        public int RenderQueueOffsetNumber;
+    }
+
+    public class ColorDefinition
+    {
+        public Color LitColor;
+        public Texture2D LitMultiplyTexture;
+        public Color ShadeColor;
+        public Texture2D ShadeMultiplyTexture;
+        public float CutoutThresholdValue;
+    }
+
+    public class LightingDefinition
+    {
+        public LitAndShadeMixingDefinition LitAndShadeMixing;
+        public LightingInfluenceDefinition LightingInfluence;
+        public NormalDefinition Normal;
+    }
+
+    public class LitAndShadeMixingDefinition
+    {
+        public float ShadingShiftValue;
+        public float ShadingToonyValue;
+        public float ShadowReceiveMultiplierValue;
+        public Texture2D ShadowReceiveMultiplierMultiplyTexture;
+        public float LitAndShadeMixingMultiplierValue;
+        public Texture2D LitAndShadeMixingMultiplierMultiplyTexture;
+    }
+
+    public class LightingInfluenceDefinition
+    {
+        public float LightColorAttenuationValue;
+        public float GiIntensityValue;
+    }
+
+    public class EmissionDefinition
+    {
+        public Color EmissionColor;
+        public Texture2D EmissionMultiplyTexture;
+    }
+
+    public class MatCapDefinition
+    {
+        public Texture2D AdditiveTexture;
+    }
+
+    public class RimDefinition
+    {
+        public Color RimColor;
+        public Texture2D RimMultiplyTexture;
+        public float RimLightingMixValue;
+        public float RimFresnelPowerValue;
+        public float RimLiftValue;
+    }
+
+    public class NormalDefinition
+    {
+        public Texture2D NormalTexture;
+        public float NormalScaleValue = 1.0f;
+    }
+
+    public class OutlineDefinition
+    {
+        public OutlineWidthMode OutlineWidthMode;
+        public float OutlineWidthValue;
+        public Texture2D OutlineWidthMultiplyTexture;
+        public float OutlineScaledMaxDistanceValue;
+        public OutlineColorMode OutlineColorMode;
+        public Color OutlineColor;
+        public float OutlineLightingMixValue;
+    }
+
+    public class TextureUvCoordsDefinition
+    {
+        public Vector2 MainTextureLeftBottomOriginScale = Vector2.One;
+        public Vector2 MainTextureLeftBottomOriginOffset;
+        public Texture2D UvAnimationMaskTexture;
+        public float UvAnimationScrollXSpeedValue;
+        public float UvAnimationScrollYSpeedValue;
+        public float UvAnimationRotationSpeedValue;
+    }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Runtime/MToon/MToonDefinition.cs.meta b/Assets/VRM10/vrmlib/Runtime/MToon/MToonDefinition.cs.meta
new file mode 100644
index 000000000..79c2e008d
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/MToon/MToonDefinition.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: bb3219008785c29408ca542a4352f517
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/MToon/Utils.cs b/Assets/VRM10/vrmlib/Runtime/MToon/Utils.cs
new file mode 100644
index 000000000..b4a66d415
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/MToon/Utils.cs
@@ -0,0 +1,125 @@
+using System;
+
+namespace VrmLib.MToon
+{
+    /// 
+    /// from https://github.com/Santarh/MToon/tree/master/MToon/Scripts
+    ///
+    /// * namespace MToon to VrmLib.Mtoon
+    /// * remove `using UnityEngine;`
+    /// * remove `using UnityEngine.Rendering;`
+    /// * change static class to class
+    /// * using Color = VrmLib.LinearColor;
+    /// * using Texture2D = VrmLib.TextureInfo;
+    ///
+    /// 
+    public partial class Utils
+    {
+        public const string ShaderName = "VRM/MToon";
+
+        public const string PropVersion = "_MToonVersion";
+        public const string PropDebugMode = "_DebugMode";
+        public const string PropOutlineWidthMode = "_OutlineWidthMode";
+        public const string PropOutlineColorMode = "_OutlineColorMode";
+        public const string PropBlendMode = "_BlendMode";
+        public const string PropCullMode = "_CullMode";
+        public const string PropOutlineCullMode = "_OutlineCullMode";
+        public const string PropCutoff = "_Cutoff";
+        public const string PropColor = "_Color";
+        public const string PropShadeColor = "_ShadeColor";
+        public const string PropMainTex = "_MainTex";
+        public const string PropShadeTexture = "_ShadeTexture";
+        public const string PropBumpScale = "_BumpScale";
+        public const string PropBumpMap = "_BumpMap";
+        public const string PropReceiveShadowRate = "_ReceiveShadowRate";
+        public const string PropReceiveShadowTexture = "_ReceiveShadowTexture";
+        public const string PropShadingGradeRate = "_ShadingGradeRate";
+        public const string PropShadingGradeTexture = "_ShadingGradeTexture";
+        public const string PropShadeShift = "_ShadeShift";
+        public const string PropShadeToony = "_ShadeToony";
+        public const string PropLightColorAttenuation = "_LightColorAttenuation";
+        public const string PropIndirectLightIntensity = "_IndirectLightIntensity";
+        public const string PropRimColor = "_RimColor";
+        public const string PropRimTexture = "_RimTexture";
+        public const string PropRimLightingMix = "_RimLightingMix";
+        public const string PropRimFresnelPower = "_RimFresnelPower";
+        public const string PropRimLift = "_RimLift";
+        public const string PropSphereAdd = "_SphereAdd";
+        public const string PropEmissionColor = "_EmissionColor";
+        public const string PropEmissionMap = "_EmissionMap";
+        public const string PropOutlineWidthTexture = "_OutlineWidthTexture";
+        public const string PropOutlineWidth = "_OutlineWidth";
+        public const string PropOutlineScaledMaxDistance = "_OutlineScaledMaxDistance";
+        public const string PropOutlineColor = "_OutlineColor";
+        public const string PropOutlineLightingMix = "_OutlineLightingMix";
+        public const string PropUvAnimMaskTexture = "_UvAnimMaskTexture";
+        public const string PropUvAnimScrollX = "_UvAnimScrollX";
+        public const string PropUvAnimScrollY = "_UvAnimScrollY";
+        public const string PropUvAnimRotation = "_UvAnimRotation";
+        public const string PropSrcBlend = "_SrcBlend";
+        public const string PropDstBlend = "_DstBlend";
+        public const string PropZWrite = "_ZWrite";
+        public const string PropAlphaToMask = "_AlphaToMask";
+
+        public const string KeyNormalMap = "_NORMALMAP";
+        public const string KeyAlphaTestOn = "_ALPHATEST_ON";
+        public const string KeyAlphaBlendOn = "_ALPHABLEND_ON";
+        public const string KeyAlphaPremultiplyOn = "_ALPHAPREMULTIPLY_ON";
+        public const string KeyOutlineWidthWorld = "MTOON_OUTLINE_WIDTH_WORLD";
+        public const string KeyOutlineWidthScreen = "MTOON_OUTLINE_WIDTH_SCREEN";
+        public const string KeyOutlineColorFixed = "MTOON_OUTLINE_COLOR_FIXED";
+        public const string KeyOutlineColorMixed = "MTOON_OUTLINE_COLOR_MIXED";
+        public const string KeyDebugNormal = "MTOON_DEBUG_NORMAL";
+        public const string KeyDebugLitShadeRate = "MTOON_DEBUG_LITSHADERATE";
+
+        public const string TagRenderTypeKey = "RenderType";
+        public const string TagRenderTypeValueOpaque = "Opaque";
+        public const string TagRenderTypeValueTransparentCutout = "TransparentCutout";
+        public const string TagRenderTypeValueTransparent = "Transparent";
+
+        public const int DisabledIntValue = 0;
+        public const int EnabledIntValue = 1;
+
+        public static RenderQueueRequirement GetRenderQueueRequirement(RenderMode renderMode)
+        {
+            const int shaderDefaultQueue = -1;
+            const int firstTransparentQueue = 2501;
+            const int spanOfQueue = 50;
+
+            switch (renderMode)
+            {
+                case RenderMode.Opaque:
+                    return new RenderQueueRequirement()
+                    {
+                        DefaultValue = shaderDefaultQueue,
+                        MinValue = shaderDefaultQueue,
+                        MaxValue = shaderDefaultQueue,
+                    };
+                case RenderMode.Cutout:
+                    return new RenderQueueRequirement()
+                    {
+                        DefaultValue = (int)RenderQueue.AlphaTest,
+                        MinValue = (int)RenderQueue.AlphaTest,
+                        MaxValue = (int)RenderQueue.AlphaTest,
+                    };
+                case RenderMode.Transparent:
+                    return new RenderQueueRequirement()
+                    {
+                        DefaultValue = (int)RenderQueue.Transparent,
+                        MinValue = (int)RenderQueue.Transparent - spanOfQueue + 1,
+                        MaxValue = (int)RenderQueue.Transparent,
+                    };
+                case RenderMode.TransparentWithZWrite:
+                    return new RenderQueueRequirement()
+                    {
+                        DefaultValue = firstTransparentQueue,
+                        MinValue = firstTransparentQueue,
+                        MaxValue = firstTransparentQueue + spanOfQueue - 1,
+                    };
+                default:
+                    throw new ArgumentOutOfRangeException("renderMode", renderMode, null);
+            }
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Runtime/MToon/Utils.cs.meta b/Assets/VRM10/vrmlib/Runtime/MToon/Utils.cs.meta
new file mode 100644
index 000000000..8153ed560
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/MToon/Utils.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ab8e72c10cd2ffa46aeebba6db2775b9
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/MToon/UtilsVersion.cs b/Assets/VRM10/vrmlib/Runtime/MToon/UtilsVersion.cs
new file mode 100644
index 000000000..a4cddf1fc
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/MToon/UtilsVersion.cs
@@ -0,0 +1,8 @@
+namespace VrmLib.MToon
+{
+    public partial class Utils
+    {
+        public const string Implementation = "Santarh/MToon";
+        public const int VersionNumber = 32;
+    }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Runtime/MToon/UtilsVersion.cs.meta b/Assets/VRM10/vrmlib/Runtime/MToon/UtilsVersion.cs.meta
new file mode 100644
index 000000000..16d2ecd56
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/MToon/UtilsVersion.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0f7dbc0d574925f4cb061f82618910a0
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Material.meta b/Assets/VRM10/vrmlib/Runtime/Material.meta
new file mode 100644
index 000000000..0484e4cc8
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Material.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: e1ee80125f7de8c4888e2591bce7d759
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Material/AlphaModeType.cs b/Assets/VRM10/vrmlib/Runtime/Material/AlphaModeType.cs
new file mode 100644
index 000000000..77eae82be
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Material/AlphaModeType.cs
@@ -0,0 +1,10 @@
+namespace VrmLib
+{
+    public enum AlphaModeType
+    {
+        OPAQUE,
+        MASK,
+        BLEND,
+        BLEND_ZWRITE,
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/Material/AlphaModeType.cs.meta b/Assets/VRM10/vrmlib/Runtime/Material/AlphaModeType.cs.meta
new file mode 100644
index 000000000..53fa101c3
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Material/AlphaModeType.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 4daa4a30bc14c074aa34243dd719d20e
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Material/ColorSpace.cs b/Assets/VRM10/vrmlib/Runtime/Material/ColorSpace.cs
new file mode 100644
index 000000000..bfda42f6d
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Material/ColorSpace.cs
@@ -0,0 +1,95 @@
+using System;
+using System.Numerics;
+
+namespace VrmLib
+{
+    public struct LinearColor : IEquatable
+    {
+        public Vector4 RGBA;
+
+        public float[] ToFloat4()
+        {
+            return new float[]{
+                RGBA.X,
+                RGBA.Y,
+                RGBA.Z,
+                RGBA.W,
+            };
+        }
+
+        public float[] ToFloat3()
+        {
+            return new float[]{
+                RGBA.X,
+                RGBA.Y,
+                RGBA.Z,
+            };
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (obj is LinearColor color)
+            {
+                return Equals(color);
+            }
+            else
+            {
+                return false;
+            }
+        }
+
+        public override int GetHashCode()
+        {
+            return RGBA.GetHashCode();
+        }
+
+        public static bool operator ==(LinearColor lhs, LinearColor rhs)
+        {
+            return lhs.Equals(rhs);
+        }
+
+        public static bool operator !=(LinearColor lhs, LinearColor rhs)
+        {
+            return !(lhs == rhs);
+        }
+
+        public static LinearColor FromLiner(Vector4 color)
+        {
+            return new LinearColor
+            {
+                RGBA = color
+            };
+        }
+
+        public static LinearColor FromLiner(float r, float g, float b, float a)
+        {
+            return new LinearColor
+            {
+                RGBA = new Vector4(r, g, b, a)
+            };
+        }
+
+        public static LinearColor FromLiner(float[] color)
+        {
+            return new LinearColor
+            {
+                RGBA = new Vector4(color[0], color[1], color[2], color[3])
+            };
+        }
+
+        public static LinearColor White => new LinearColor
+        {
+            RGBA = Vector4.One,
+        };
+
+        public static LinearColor Black => new LinearColor
+        {
+            RGBA = new Vector4(0, 0, 0, 1),
+        };
+
+        public bool Equals(LinearColor other)
+        {
+            return RGBA == other.RGBA;
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/Material/ColorSpace.cs.meta b/Assets/VRM10/vrmlib/Runtime/Material/ColorSpace.cs.meta
new file mode 100644
index 000000000..bc86c95bc
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Material/ColorSpace.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e00837e0bb376a0438a56ba05d5fcb6a
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Material/Image.cs b/Assets/VRM10/vrmlib/Runtime/Material/Image.cs
new file mode 100644
index 000000000..ad4355854
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Material/Image.cs
@@ -0,0 +1,118 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace VrmLib
+{
+    /// 
+    /// 画像の用途
+    /// 
+    [Flags]
+    public enum ImageUsage
+    {
+        None,
+        Color,
+        Normal,
+    }
+
+    struct PngChunk
+    {
+        public readonly string Type;
+        public readonly ArraySegment Data;
+
+        public readonly ArraySegment CRC;
+
+        public PngChunk(string type, ArraySegment data, ArraySegment crc)
+        {
+            Type = type;
+            Data = data;
+            CRC = crc;
+        }
+
+        public override string ToString()
+        {
+            return $"{Type}: {Data.Count} bytes";
+        }
+    }
+
+    static class PngUtil
+    {
+        static readonly Byte[] PNG_MAGIC = new byte[]
+        {
+            0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A
+        };
+
+        // big endian
+        public static int ToInt32BE(ArraySegment bytes)
+        {
+            var endian = bytes.Slice(0, 4).ToArray();
+            // endian.AsSpan().Reverse(); // to LE
+            // return BitConverter.ToInt32(endian);
+            return endian[3] | (8 << endian[2]) | (16 << endian[1]) | (24 << endian[0]);
+        }
+
+        public static IEnumerable ParseBytes(ArraySegment bytes)
+        {
+            if (!bytes.Slice(0, 8).SequenceEqual(PNG_MAGIC))
+            {
+                throw new FormatException("is not png");
+            }
+            bytes = bytes.Slice(8);
+
+            while (bytes.Count > 0)
+            {
+                var length = ToInt32BE(bytes);
+                bytes = bytes.Slice(4);
+
+                var type = bytes.Slice(0, 4);
+                var chunkType = Encoding.ASCII.GetString(type.Array, type.Offset, type.Count);
+                bytes = bytes.Slice(4);
+
+                var data = bytes.Slice(0, length);
+                bytes = bytes.Slice(length);
+
+                var crc = bytes.Slice(0, 4);
+                bytes = bytes.Slice(4);
+
+                yield return new PngChunk(chunkType, data, crc);
+            }
+        }
+    }
+
+    public class Image : GltfId
+    {
+        public string Name;
+        public string MimeType;
+        public ArraySegment Bytes;
+
+        public ImageUsage Usage;
+
+        public override string ToString()
+        {
+            if (MimeType == "image/png")
+            {
+                foreach (var chunk in PngUtil.ParseBytes(Bytes))
+                {
+                    if (chunk.Type == "IHDR")
+                    {
+
+                        var w = PngUtil.ToInt32BE(chunk.Data.Slice(0, 4));
+                        var h = PngUtil.ToInt32BE(chunk.Data.Slice(4, 4));
+                        return $"{Name}: {MimeType}: {w}x{h}";
+                    }
+                }
+            }
+
+            return $"{Name}: {MimeType}: {Bytes.Count} bytes";
+        }
+
+        public Image(string name, string mimeType, ImageUsage usage, ArraySegment bytes)
+        {
+            Name = name;
+            MimeType = mimeType;
+            Bytes = bytes;
+            Usage = usage;
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/Material/Image.cs.meta b/Assets/VRM10/vrmlib/Runtime/Material/Image.cs.meta
new file mode 100644
index 000000000..b33b264ad
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Material/Image.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3b80c203904677843915b1b442c18519
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Material/Material.cs b/Assets/VRM10/vrmlib/Runtime/Material/Material.cs
new file mode 100644
index 000000000..c9f50828c
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Material/Material.cs
@@ -0,0 +1,105 @@
+using System.Collections.Generic;
+using System;
+using System.Numerics;
+
+namespace VrmLib
+{
+    public class Material: GltfId
+    {
+        public string Name;
+
+        public virtual LinearColor BaseColorFactor
+        {
+            get;
+            set;
+        } = LinearColor.White;
+
+        public virtual TextureInfo BaseColorTexture
+        {
+            get;
+            set;
+        }
+
+        public virtual AlphaModeType AlphaMode
+        {
+            get;
+            set;
+        } = AlphaModeType.OPAQUE;
+
+        public virtual float AlphaCutoff
+        {
+            get;
+            set;
+        } = 0.5f;
+
+        public virtual bool DoubleSided
+        {
+            get;
+            set;
+        }
+
+        public Material(string name)
+        {
+            Name = name;
+        }
+
+        static protected bool ImageIsEquals(Image lhs, Image rhs)
+        {
+            if (lhs is null)
+            {
+                return rhs is null;
+            }
+            else
+            {
+                if (rhs is null)
+                {
+                    return false;
+                }
+            }
+
+            if (!lhs.Bytes.Equals(rhs.Bytes))
+            {
+                return false;
+            }
+
+            return true;
+        }
+
+        static protected bool TextureIsEquals(TextureInfo lhs, TextureInfo rhs)
+        {
+            if (lhs is null)
+            {
+                return rhs is null;
+            }
+            else
+            {
+                if (rhs is null)
+                {
+                    return false;
+                }
+            }
+
+            if (lhs.Offset != rhs.Offset) return false;
+            if (lhs.Scaling != rhs.Scaling) return false;
+
+            if (lhs.Texture is ImageTexture lImage && rhs.Texture is ImageTexture rImage)
+            {
+                if (!ImageIsEquals(lImage.Image, rImage.Image))
+                {
+                    return false;
+                }
+            }
+            else
+            {
+                return false;
+            }
+
+            return true;
+        }
+
+        public virtual bool CanIntegrate(Material rhs)
+        {
+            return false;
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/Material/Material.cs.meta b/Assets/VRM10/vrmlib/Runtime/Material/Material.cs.meta
new file mode 100644
index 000000000..159f6dd36
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Material/Material.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 6eb1bafca5d964e44a1de8c990d715a3
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Material/PBRMaterial.cs b/Assets/VRM10/vrmlib/Runtime/Material/PBRMaterial.cs
new file mode 100644
index 000000000..75981b310
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Material/PBRMaterial.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Numerics;
+
+namespace VrmLib
+{
+    public class PBRMaterial : Material, IEquatable
+    {
+        public PBRMaterial(string name) : base(name)
+        {
+        }
+
+        public override string ToString()
+        {
+            var sb = new System.Text.StringBuilder();
+            sb.Append($"[PBR]{Name}");
+            if (BaseColorTexture != null)
+            {
+                sb.Append(" ColorTex");
+            }
+            if (MetallicRoughnessTexture != null)
+            {
+                sb.Append("  MetallicRoughnessTex");
+            }
+            if (EmissiveTexture != null)
+            {
+                sb.Append(" EmissiveTex");
+            }
+            if (NormalTexture != null)
+            {
+                sb.Append(" NormalTex");
+            }
+            if (OcclusionTexture != null)
+            {
+                sb.Append(" OcclusionTex");
+            }
+            return sb.ToString();
+        }
+
+        public Single MetallicFactor;
+        public Single RoughnessFactor;
+        public Texture MetallicRoughnessTexture;
+
+        public Vector3 EmissiveFactor = Vector3.Zero;
+        public Texture EmissiveTexture;
+
+        public Texture NormalTexture;
+        public float NormalTextureScale = 1.0f;
+
+        public Texture OcclusionTexture;
+        public float OcclusionTextureStrength = 1.0f;
+
+        public bool Equals(PBRMaterial other)
+        {
+            if (!base.Equals(other)) return false;
+            if (MetallicFactor != other.MetallicFactor) return false;
+            if (RoughnessFactor != other.RoughnessFactor) return false;
+            if (MetallicRoughnessTexture != other.MetallicRoughnessTexture) return false;
+            if (EmissiveFactor != other.EmissiveFactor) return false;
+            if (EmissiveTexture != other.EmissiveTexture) return false;
+            if (NormalTexture != other.NormalTexture) return false;
+            if (NormalTextureScale != other.NormalTextureScale) return false;
+            if (OcclusionTexture != other.OcclusionTexture) return false;
+            if (OcclusionTextureStrength != other.OcclusionTextureStrength) return false;
+
+            return true;
+        }
+
+        public override bool CanIntegrate(Material _rhs)
+        {
+            var rhs = _rhs as PBRMaterial;
+            if (rhs == null)
+            {
+                return false;
+            }
+
+            return this.Equals(rhs);
+        }
+    }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Runtime/Material/PBRMaterial.cs.meta b/Assets/VRM10/vrmlib/Runtime/Material/PBRMaterial.cs.meta
new file mode 100644
index 000000000..a7136d535
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Material/PBRMaterial.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 2fceae9e1295b7e45a7937a949ea5e6b
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Material/Texture.cs b/Assets/VRM10/vrmlib/Runtime/Material/Texture.cs
new file mode 100644
index 000000000..a5b819a07
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Material/Texture.cs
@@ -0,0 +1,162 @@
+using System;
+using System.Numerics;
+
+namespace VrmLib
+{
+    public enum TextureMagFilterType : int
+    {
+        NEAREST = 9728,
+        LINEAR = 9729
+    }
+
+    public enum TextureMinFilterType : int
+    {
+        NEAREST = 9728,
+        LINEAR = 9729,
+
+        NEAREST_MIPMAP_NEAREST = 9984,
+        LINEAR_MIPMAP_NEAREST = 9985,
+        NEAREST_MIPMAP_LINEAR = 9986,
+        LINEAR_MIPMAP_LINEAR = 9987,
+    }
+
+    public enum TextureWrapType : int
+    {
+        REPEAT = 10497,
+        CLAMP_TO_EDGE = 33071,
+        MIRRORED_REPEAT = 33648,
+    }
+
+    public class TextureSampler
+    {
+        public TextureWrapType WrapS;
+        public TextureWrapType WrapT;
+
+        public TextureMinFilterType MinFilter;
+        public TextureMagFilterType MagFilter;
+    }
+
+    public abstract class Texture: GltfId
+    {
+        public enum TextureTypes
+        {
+            Default,
+            NormalMap,
+            MetallicRoughness,
+            Emissive,
+            Occlusion
+        };
+
+        public enum ColorSpaceTypes
+        {
+            Srgb,
+            Linear,
+        };
+
+        public string Name;
+        public TextureSampler Sampler;
+        public ColorSpaceTypes ColorSpace;
+        public TextureTypes TextureType;
+
+        protected Texture(string name, TextureSampler sampler, ColorSpaceTypes colorSpace, TextureTypes textureType)
+        {
+            if (name == null)
+            {
+                throw new ArgumentNullException("name");
+            }
+            Name = name;
+            Sampler = sampler;
+            ColorSpace = colorSpace;
+            TextureType = textureType;
+        }
+    }
+
+    public class ImageTexture : Texture, IEquatable
+    {
+        public Image Image;
+
+        public ImageTexture(string name, TextureSampler sampler, Image image, ColorSpaceTypes colorSpace, TextureTypes textureType = TextureTypes.Default) : base(name, sampler, colorSpace, textureType)
+        {
+            Image = image;
+        }
+
+        public override string ToString()
+        {
+            return $"{Name}({Image.Name}: {Image.MimeType})";
+        }
+
+        public override int GetHashCode()
+        {
+            return base.GetHashCode();
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (obj is ImageTexture rhs)
+            {
+                return Equals(rhs);
+            }
+            else
+            {
+                return false;
+            }
+        }
+
+        public bool Equals(ImageTexture other)
+        {
+            if (Name != other.Name) return false;
+            // if (Offset != other.Offset) return false;
+            // if (Scaling != other.Scaling) return false;
+            if (!Image.Bytes.Equals(other.Image.Bytes)) return false;
+            return true;
+        }
+    }
+
+    public class MetallicRoughnessImageTexture : ImageTexture
+    {
+        public float RoughnessFactor;
+
+        public MetallicRoughnessImageTexture(string name, TextureSampler sampler, Image image, float roughnessFactor, ColorSpaceTypes colorSpace, TextureTypes textureType = TextureTypes.Default)
+            : base(name, sampler, image, colorSpace, textureType)
+        {
+            Image = image;
+            RoughnessFactor = roughnessFactor;
+        }
+    }
+
+    /// 
+    /// 単色の 2x2 テクスチャ
+    /// 
+    public class SolidTexture : Texture
+    {
+        public readonly Vector4 Color;
+
+        public SolidTexture(string name, TextureSampler sampler, Vector4 color, ColorSpaceTypes colorSpace, TextureTypes textureType) : base(name, sampler, colorSpace, textureType)
+        {
+            Color = color;
+        }
+
+        public static readonly SolidTexture White = new SolidTexture("white", new TextureSampler(), Vector4.One, ColorSpaceTypes.Srgb, TextureTypes.Default);
+    }
+
+    /// 
+    /// レンダーターゲットの元になる
+    /// 
+    public class RenderTexture : Texture
+    {
+        public int Width;
+        public int Height;
+
+        public RenderTexture(string name, TextureSampler sampler, ColorSpaceTypes colorSpace, TextureTypes textureType, int width = 256, int height = 256) : base(name, sampler, colorSpace, textureType)
+        {
+            Width = width;
+            Height = height;
+        }
+
+        public void Resize(int w, int h)
+        {
+            Width = w;
+            Height = h;
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/Material/Texture.cs.meta b/Assets/VRM10/vrmlib/Runtime/Material/Texture.cs.meta
new file mode 100644
index 000000000..c0caa2976
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Material/Texture.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: c424b000dcd245749bbf5c204453a6d9
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Material/TextureInfo.cs b/Assets/VRM10/vrmlib/Runtime/Material/TextureInfo.cs
new file mode 100644
index 000000000..d58e0d507
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Material/TextureInfo.cs
@@ -0,0 +1,30 @@
+using System.Numerics;
+
+namespace VrmLib
+{
+    public class TextureInfo
+    {
+
+        public Texture Texture;
+        // uv = uv * scaling + offset
+        public Vector2 Offset;
+        public Vector2 Scaling = Vector2.One;
+
+        public float[] OffsetScaling
+        {
+            get => new float[] { Offset.X, Offset.Y, Scaling.X, Scaling.Y };
+            set
+            {
+                Offset.X = value[0];
+                Offset.Y = value[1];
+                Scaling.X = value[2];
+                Scaling.Y = value[3];
+            }
+        }
+
+        public TextureInfo(Texture texture)
+        {
+            Texture = texture;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Runtime/Material/TextureInfo.cs.meta b/Assets/VRM10/vrmlib/Runtime/Material/TextureInfo.cs.meta
new file mode 100644
index 000000000..f26320f79
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Material/TextureInfo.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 5d377e18003320f409858e0fe55e5e47
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Material/UnlitMaterial.cs b/Assets/VRM10/vrmlib/Runtime/Material/UnlitMaterial.cs
new file mode 100644
index 000000000..bc8ab0f53
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Material/UnlitMaterial.cs
@@ -0,0 +1,45 @@
+using System.Numerics;
+
+namespace VrmLib
+{
+    //
+    // https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit
+    //
+    public class UnlitMaterial : Material
+    {
+        public const string ExtensionName = "KHR_materials_unlit";
+
+        public override string ToString()
+        {
+            var sb = new System.Text.StringBuilder();
+            sb.Append($"[Unlit]{Name}");
+            if (BaseColorTexture != null)
+            {
+                sb.Append($" => {BaseColorTexture}");
+            }
+            return sb.ToString();
+        }
+
+        public UnlitMaterial(string name) : base(name)
+        {
+        }
+
+        public override bool CanIntegrate(Material _rhs)
+        {
+            var rhs = _rhs as UnlitMaterial;
+            if (rhs == null)
+            {
+                return false;
+            }
+
+            // copy
+            if (BaseColorFactor != rhs.BaseColorFactor) return false;
+            if (BaseColorTexture != rhs.BaseColorTexture) return false;
+            if (AlphaMode != rhs.AlphaMode) return false;
+            if (AlphaCutoff != rhs.AlphaCutoff) return false;
+            if (DoubleSided != rhs.DoubleSided) return false;
+
+            return true;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Runtime/Material/UnlitMaterial.cs.meta b/Assets/VRM10/vrmlib/Runtime/Material/UnlitMaterial.cs.meta
new file mode 100644
index 000000000..a39e17bec
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Material/UnlitMaterial.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3211ce9591dfcb44493a22c111f91086
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/MathFWrap.cs b/Assets/VRM10/vrmlib/Runtime/MathFWrap.cs
new file mode 100644
index 000000000..c818e01c5
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/MathFWrap.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace VrmLib
+{
+    public static class MathFWrap
+    {
+        public static readonly float PI = (float)System.Math.PI;
+
+        public static float Clamp(float src, float min, float max)
+        {
+            return Math.Max(Math.Min(src, min), max);
+        }
+        public static int Clamp(int src, int min, int max)
+        {
+            return Math.Max(Math.Min(src, min), max);
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/MathFWrap.cs.meta b/Assets/VRM10/vrmlib/Runtime/MathFWrap.cs.meta
new file mode 100644
index 000000000..28bd46ee4
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/MathFWrap.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: cc3c6cd57620b5e408c8f982fc3f320b
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Mesh.cs b/Assets/VRM10/vrmlib/Runtime/Mesh.cs
new file mode 100644
index 000000000..2f6d4f57c
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Mesh.cs
@@ -0,0 +1,281 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace VrmLib
+{
+    public enum TopologyType : int
+    {
+        Points = 0,
+        Lines = 1,
+        LineLoop = 2,
+        LineStrip = 3,
+        Triangles = 4,
+        TriangleStrip = 5,
+        TriangleFan = 6,
+    }
+
+    public struct Triangle
+    {
+        public int Vertex0;
+        public int Vertex1;
+        public int Vertex2;
+
+        public Triangle(int v0, int v1, int v2)
+        {
+            Vertex0 = v0;
+            Vertex1 = v1;
+            Vertex2 = v2;
+        }
+    }
+
+    public class Submesh
+    {
+        public int Offset;
+        public int DrawCount;
+        public Material Material;
+
+        public override string ToString()
+        {
+            return $"{Material.Name}({DrawCount})";
+        }
+
+        public Submesh(Material material) : this(0, 0, material)
+        {
+        }
+
+        public Submesh(int offset, int drawCount, Material material)
+        {
+            Offset = offset;
+            DrawCount = drawCount;
+            Material = material;
+        }
+    }
+
+    public class Mesh
+    {
+        public VertexBuffer VertexBuffer;
+
+        public BufferAccessor IndexBuffer;
+
+        /// 
+        /// indicesの最大値が65535未満(-1を避ける)ならばushort 型で、
+        /// そうでなければ int型で IndexBufferを代入する
+        /// 
+        public void AssignIndexBuffer(SpanLike indices)
+        {
+            bool isInt = false;
+            foreach (var i in indices)
+            {
+                if (i >= short.MaxValue)
+                {
+                    isInt = true;
+                    break;
+                }
+            }
+
+            if (isInt)
+            {
+                if (IndexBuffer.Stride != 4)
+                {
+                    IndexBuffer.ComponentType = AccessorValueType.UNSIGNED_INT;
+                    if (IndexBuffer.AccessorType != AccessorVectorType.SCALAR)
+                    {
+                        throw new Exception();
+                    }
+                }
+                // 変換なし
+                IndexBuffer.Assign(indices);
+            }
+            else
+            {
+                // int to ushort
+                IndexBuffer.AssignAsShort(indices);
+            }
+        }
+
+        public TopologyType Topology = TopologyType.Triangles;
+
+        public List Submeshes { private set; get; } = new List();
+
+        public int SubmeshTotalDrawCount => Submeshes.Sum(x => x.DrawCount);
+
+        public IEnumerable GetTriangles(int i)
+        {
+            var indices = IndexBuffer.GetAsIntArray();
+
+            var submesh = Submeshes[i];
+            var submeshEnd = submesh.Offset + submesh.DrawCount;
+            for (int j = submesh.Offset; j < submeshEnd; j += 3)
+            {
+                var triangle = new Triangle(
+                    indices[j],
+                    indices[j + 1],
+                    indices[j + 2]
+                );
+                yield return triangle;
+            }
+        }
+
+        public IEnumerable> Triangles
+        {
+            get
+            {
+                if (Topology != TopologyType.Triangles)
+                {
+                    throw new InvalidOperationException();
+                }
+
+                var indices = IndexBuffer.GetAsIntArray();
+
+                for (int i = 0; i < Submeshes.Count; ++i)
+                {
+                    var submesh = Submeshes[i];
+                    var submeshEnd = submesh.Offset + submesh.DrawCount;
+                    for (int j = submesh.Offset; j < submeshEnd; j += 3)
+                    {
+                        var triangle = new Triangle(
+                            indices[j],
+                            indices[j + 1],
+                            indices[j + 2]
+                        );
+                        yield return (i, triangle);
+                    }
+                }
+            }
+        }
+
+        bool GetSubmeshOverlapped() where T : struct
+        {
+            var indices = IndexBuffer.GetSpan();
+            var offset = 0;
+            var max = 0;
+            foreach (var x in Submeshes)
+            {
+                var submeshIndices = indices.Slice(offset, x.DrawCount);
+                var currentMax = 0;
+                foreach (var y in submeshIndices)
+                {
+                    if (y < max)
+                    {
+                        return true;
+                    }
+                    currentMax = Math.Max(y, currentMax);
+                }
+                offset += x.DrawCount;
+                max = currentMax;
+            }
+            return false;
+        }
+
+        public bool IsSubmeshOverlapped
+        {
+            get
+            {
+                if (Submeshes.Count <= 1)
+                {
+                    return false;
+                }
+
+                switch (IndexBuffer.ComponentType)
+                {
+                    case AccessorValueType.UNSIGNED_SHORT:
+                        return GetSubmeshOverlapped();
+
+                    case AccessorValueType.UNSIGNED_INT:
+                        return GetSubmeshOverlapped();
+
+                    default:
+                        throw new NotImplementedException();
+                }
+            }
+        }
+
+        public List MorphTargets = new List();
+
+        public override string ToString()
+        {
+            var sb = new System.Text.StringBuilder();
+            foreach (var key in VertexBuffer.Select(x => x.Key))
+            {
+                sb.Append($"[{key}]");
+            }
+            if (IndexBuffer != null)
+            {
+                switch (Topology)
+                {
+                    case TopologyType.Triangles:
+                        sb.Append($" {IndexBuffer.Count / 3} tris");
+                        break;
+
+                    default:
+                        sb.Append($" topology: {Topology}");
+                        break;
+                }
+            }
+            if (MorphTargets.Any())
+            {
+                sb.Append($", {MorphTargets.Count} morphs");
+                foreach (var kv in MorphTargets[0].VertexBuffer)
+                {
+                    sb.Append($"[{kv.Key}]");
+                }
+            }
+
+            var byteLength = VertexBuffer.ByteLength + IndexBuffer.ByteLength + MorphTargets.Sum(x => x.VertexBuffer.ByteLength);
+
+            sb.Append($": expected {byteLength / 1000 / 1000} MB");
+
+            return sb.ToString();
+        }
+
+        public Mesh(TopologyType topology = TopologyType.Triangles)
+        {
+            Topology = topology;
+        }
+
+        public void RemoveUnusedSubmesh()
+        {
+            Submeshes = Submeshes.Where(x => x.DrawCount != 0).ToList();
+        }
+    }
+
+    public class MeshGroup: GltfId
+    {
+        public readonly string Name;
+
+        public readonly List Meshes = new List();
+
+        public Skin Skin;
+
+        public override string ToString()
+        {
+            var sb = new StringBuilder();
+            sb.Append(Name);
+            if (Skin != null)
+            {
+                sb.Append("(skinned)");
+            }
+            var isFirst = true;
+            foreach (var mesh in Meshes)
+            {
+                if (isFirst)
+                {
+                    isFirst = false;
+                }
+                else
+                {
+                    sb.Append(", ");
+                }
+                sb.Append(mesh);
+            }
+            return sb.ToString();
+        }
+
+        public MeshGroup(string name)
+        {
+            Name = name;
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/Mesh.cs.meta b/Assets/VRM10/vrmlib/Runtime/Mesh.cs.meta
new file mode 100644
index 000000000..0b8248c2e
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Mesh.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: da35a7338402b7b499b9c0357144fbc8
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/MeshExtensions.cs b/Assets/VRM10/vrmlib/Runtime/MeshExtensions.cs
new file mode 100644
index 000000000..fa068237c
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/MeshExtensions.cs
@@ -0,0 +1,672 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Runtime.InteropServices;
+
+namespace VrmLib
+{
+    public static class MeshExtensions
+    {
+        // Skin.Normalize
+        public static void ApplyRotationAndScaling(this Mesh mesh, Matrix4x4 m)
+        {
+            m.Translation = Vector3.Zero;
+
+            var position = SpanLike.Wrap(mesh.VertexBuffer.Positions.Bytes);
+            var normal = SpanLike.Wrap(mesh.VertexBuffer.Normals.Bytes);
+
+            for (int i = 0; i < position.Length; ++i)
+            {
+                {
+                    var dst = Vector4.Transform(new Vector4(position[i], 1), m);
+                    position[i] = new Vector3(dst.X, dst.Y, dst.Z);
+                }
+                {
+                    var dst = Vector4.Transform(new Vector4(normal[i], 0), m);
+                    normal[i] = new Vector3(dst.X, dst.Y, dst.Z);
+                }
+            }
+        }
+
+        // joint index の調整
+        static void FixSkinJoints(SpanLike joints, SpanLike weights, int[] jointIndexMap)
+        {
+            for (int i = 0; i < joints.Length; ++i)
+            {
+                var j = joints[i];
+                var w = weights[i];
+
+                var sum = w.X + w.Y + w.Z + w.W;
+                if (sum == 0)
+                {
+                    throw new Exception("zero weight");
+                }
+                var factor = 1.0f / sum;
+
+                {
+                    var index = jointIndexMap[j.Joint0];
+                    j.Joint0 = (ushort)(index >= 0 ? index : 0);
+                    w.X *= factor;
+                }
+
+                {
+                    var index = jointIndexMap[j.Joint1];
+                    j.Joint1 = (ushort)(index >= 0 ? index : 0);
+                    w.X *= factor;
+                }
+
+                {
+                    var index = jointIndexMap[j.Joint2];
+                    j.Joint2 = (ushort)(index >= 0 ? index : 0);
+                    w.X *= factor;
+                }
+
+                {
+                    var index = jointIndexMap[j.Joint3];
+                    j.Joint3 = (ushort)(index >= 0 ? index : 0);
+                    w.X *= factor;
+                }
+
+                weights[i] = w;
+                joints[i] = j;
+            }
+        }
+
+        /// weightを付与して、matrixを適用する
+        static void FixNoSkinJoints(SpanLike joints, SpanLike weights, int jointIndex,
+            SpanLike positions, SpanLike normals, Matrix4x4 matrix)
+        {
+            for (int i = 0; i < joints.Length; ++i)
+            {
+                var j = new SkinJoints
+                {
+                    Joint0 = (ushort)jointIndex
+                };
+                joints[i] = j;
+
+                var w = new Vector4
+                {
+                    X = 1.0f,
+                };
+                weights[i] = w;
+
+                {
+                    // position
+                    var src = positions[i];
+                    var dst = Vector4.Transform(new Vector4(src, 1), matrix);
+                    positions[i] = new Vector3(dst.X, dst.Y, dst.Z);
+                }
+                {
+                    // normal
+                    var src = normals[i];
+                    var dst = Vector4.Transform(new Vector4(src, 0), matrix);
+                    normals[i] = new Vector3(dst.X, dst.Y, dst.Z);
+                }
+            }
+        }
+
+        // Meshを連結する。SingleMesh で使う
+        public static void Append(this Mesh mesh, VertexBuffer vertices, BufferAccessor indices,
+            List submeshes,
+            List targets,
+            int[] jointIndexMap,
+            int rootIndex = -1,
+            Matrix4x4 matrix = default(Matrix4x4))
+        {
+            var lastCount = mesh.VertexBuffer != null ? mesh.VertexBuffer.Count : 0;
+
+            // index buffer
+            if (mesh.IndexBuffer == null)
+            {
+                var accessor = new BufferAccessor(new ArraySegment(new byte[0]), AccessorValueType.UNSIGNED_INT, indices.AccessorType, 0);
+                mesh.IndexBuffer = accessor;
+
+                mesh.IndexBuffer.Append(indices, 0);
+            }
+            else
+            {
+                mesh.IndexBuffer.Append(indices, lastCount);
+            }
+
+            {
+                var submeshOffset = mesh.SubmeshTotalDrawCount;
+                for (int i = 0; i < submeshes.Count; ++i)
+                {
+                    var submesh = submeshes[i];
+                    mesh.Submeshes.Add(new Submesh(submeshOffset, submesh.DrawCount, submesh.Material));
+                    submeshOffset += submesh.DrawCount;
+                }
+            }
+
+            // vertex buffer
+            var vertexOffset = 0;
+            if (mesh.VertexBuffer == null)
+            {
+                mesh.VertexBuffer = vertices;
+            }
+            else
+            {
+                vertexOffset = mesh.VertexBuffer.Count;
+                mesh.VertexBuffer.Append(vertices);
+            }
+
+            if (jointIndexMap != null && mesh.VertexBuffer.Joints != null && mesh.VertexBuffer.Weights != null)
+            {
+                // JOINT index 参照の修正
+                var joints = SpanLike.Wrap(mesh.VertexBuffer.Joints.Bytes).Slice(vertexOffset);
+                var weights = SpanLike.Wrap(mesh.VertexBuffer.Weights.Bytes).Slice(vertexOffset);
+                FixSkinJoints(joints, weights, jointIndexMap);
+            }
+            else
+            {
+                var position = SpanLike.Wrap(mesh.VertexBuffer.Positions.Bytes).Slice(vertexOffset);
+                var normal = SpanLike.Wrap(mesh.VertexBuffer.Normals.Bytes).Slice(vertexOffset);
+                var joints = mesh.VertexBuffer.GetOrCreateJoints().Slice(vertexOffset);
+                var weights = mesh.VertexBuffer.GetOrCreateWeights().Slice(vertexOffset);
+                // Nodeの姿勢を反映して
+                // JOINT と WEIGHT を追加する
+                FixNoSkinJoints(joints, weights, rootIndex, position, normal, matrix);
+            }
+
+            // morph target
+            foreach (var target in targets)
+            {
+                if (string.IsNullOrEmpty(target.Name))
+                {
+                    continue;
+                }
+
+                foreach (var kv in target.VertexBuffer)
+                {
+                    if (kv.Value.Count != target.VertexBuffer.Count)
+                    {
+                        throw new Exception("different length");
+                    }
+                }
+
+                var found = mesh.MorphTargets.FirstOrDefault(x => x.Name == target.Name);
+                if (found == null)
+                {
+                    // targetの前に0を足す
+                    found = new MorphTarget(target.Name);
+                    found.VertexBuffer = target.VertexBuffer.CloneWithOffset(lastCount);
+                    mesh.MorphTargets.Add(found);
+
+                    foreach (var kv in found.VertexBuffer)
+                    {
+                        if (kv.Value.Count != mesh.VertexBuffer.Count)
+                        {
+                            throw new Exception();
+                        }
+                    }
+                }
+                else
+                {
+                    found.VertexBuffer.Resize(lastCount);
+
+                    // foundの後ろにtargetを足す
+                    found.VertexBuffer.Append(target.VertexBuffer);
+
+                    foreach (var kv in found.VertexBuffer)
+                    {
+                        if (kv.Value.Count != mesh.VertexBuffer.Count)
+                        {
+                            throw new Exception();
+                        }
+                    }
+                }
+            }
+        }
+
+        class SubmeshReorderMapper
+        {
+            public readonly List Indices = new List();
+
+            public ArraySegment IndexBytes
+            {
+                get
+                {
+                    var bytes = new byte[Indices.Count * 4];
+                    var span = SpanLike.Wrap(new ArraySegment(bytes));
+                    for (int i = 0; i < span.Length; ++i)
+                    {
+                        span[i] = Indices[i];
+                    }
+                    return new ArraySegment(bytes);
+                }
+            }
+
+            public BufferAccessor CreateIndexBuffer()
+            {
+                return new BufferAccessor(IndexBytes,
+                AccessorValueType.UNSIGNED_INT, AccessorVectorType.SCALAR, Indices.Count);
+            }
+
+            public SubmeshReorderMapper(IEnumerable> submeshTriangles)
+            {
+                foreach (var t in submeshTriangles)
+                {
+                    Indices.Add(t.Item2.Vertex0);
+                    Indices.Add(t.Item2.Vertex1);
+                    Indices.Add(t.Item2.Vertex2);
+                }
+            }
+        }
+
+        public static string IntegrateSubmeshes(this Mesh mesh)
+        {
+            var before = mesh.Submeshes.Count;
+
+            // 同じMaterialのMeshが連続するようにIndexを並べ替える
+            var sorted = new List();
+            var source = mesh.Submeshes.ToList(); // copy
+            while (source.Any())
+            {
+                var first = source[0];
+                source.RemoveAt(0);
+
+                sorted.Add(first);
+                sorted.AddRange(source.Where(x => x.Material == first.Material)); // 同じMaterialを追加
+                source.RemoveAll(x => x.Material == first.Material); // 同じMaterialを削除
+            }
+            // Submeshを並べ替えて連続するものを連結する
+            mesh.ReorderSubmeshes(sorted);
+
+            return $"({before} => {mesh.Submeshes.Count})";
+        }
+
+        struct Vertex : IComparable, IEquatable
+        {
+            public static float PositionThreshold = float.Epsilon;
+
+            public static float NormalAngleThreshold = 100.0f;
+
+            public static float UVThreshold = 0.0001f;
+
+            /// 
+            /// 他の頂点の統合先となって、残るフラグ
+            /// 
+            public bool IsMergeTarget
+            {
+                get
+                {
+                    return Index == targetIndex;
+                }
+            }
+
+            /// 
+            /// 統合先の頂点Index
+            /// 統合後も残るはtargetIndex == Index
+            /// 
+            public int targetIndex;
+
+            public int Index;
+
+            public Vector3 Position;
+
+            public Vector3? Normal;
+
+            public Vector2? UV;
+
+            public SkinJoints? Joints;
+
+            public Vector4? Weight;
+
+            public Vector4? Color;
+
+            // 必要に応じて同一判定に使う頂点パラメータを追加する
+
+            public Vertex(int index, Vector3 position, Vector3? normal, Vector2? uv, SkinJoints? joints, Vector4? weight, Vector4? color)
+            {
+                Index = index;
+                Position = position;
+                Normal = normal;
+                UV = uv;
+                Joints = joints;
+                Weight = weight;
+                Color = color;
+                targetIndex = -1;
+            }
+
+            /// 
+            /// 座標ソートのときの比較基準を記述
+            /// 必ずしもX軸基準である必要は特にない
+            ///         
+            public int CompareTo(Vertex other)
+            {
+                if (this.Position.X == other.Position.X) return 0;
+                else if (this.Position.X > other.Position.X) return 1;
+                else return -1;
+            }
+
+            public bool Equals(Vertex other)
+            {
+                if (Vector3.Distance(this.Position, other.Position) > PositionThreshold) return false;
+
+                if (this.Normal.HasValue && other.Normal.HasValue)
+                {
+                    var v = this.Normal.Value;
+                    var u = other.Normal.Value;
+                    var angle = (Math.Acos(Vector3.Dot(v, u) / (v.Length() * u.Length()))) * (180 / Math.PI);
+                    if (angle > NormalAngleThreshold) return false;
+                }
+
+                if ((this.UV.HasValue && other.UV.HasValue) && Vector2.Distance(this.UV.Value, other.UV.Value) > UVThreshold) return false;
+
+                if ((this.Joints.HasValue && other.Joints.HasValue) && !(this.Joints.Value.Equals(other.Joints.Value))) return false;
+
+                return true;
+            }
+        }
+
+#if false
+        /// 
+        /// 同一位置にある頂点を統合する
+        /// 
+        public static string MergeSameVertices(this Mesh mesh)
+        {
+            var indices = mesh.IndexBuffer.GetAsIntArray();
+
+            var positions = SpanLike.CreateVector3(mesh.VertexBuffer.Positions.Bytes);
+            var normals = mesh.VertexBuffer.Normals != null ? SpanLike.CreateVector3(mesh.VertexBuffer.Normals.Bytes) : default;
+            var UVs = mesh.VertexBuffer.TexCoords != null ? SpanLike.CreateVector2(mesh.VertexBuffer.TexCoords.Bytes) : default;
+            var joints = mesh.VertexBuffer.Joints != null ? SpanLike.CreateSkinJoints(mesh.VertexBuffer.Joints.Bytes) : default;
+            var weights = mesh.VertexBuffer.Weights != null ? SpanLike.CreateVector4(mesh.VertexBuffer.Weights.Bytes) : default;
+            var colors = mesh.VertexBuffer.Colors != null ? SpanLike.CreateVector4(mesh.VertexBuffer.Colors.Bytes) : default;
+
+            int before = positions.Length;
+
+            var verts = new Vertex[positions.Length];
+            for (int i = 0; i < positions.Length; i++)
+            {
+                var pos = positions[i];
+
+                Vector3? normal = null;
+                if (normals.Length > 0) normal = normals[i];
+
+                Vector2? uv = null;
+                if (UVs.Length > 0) uv = UVs[i];
+
+                SkinJoints? joint = null;
+                if (joints.Length > 0) joint = joints[i];
+
+                Vector4? weight = null;
+                if (weights.Length > 0) weight = weights[i];
+
+                Vector4? color = null;
+                if (colors.Length > 0) color = colors[i];
+                verts[i] = new Vertex(i, pos, normal, uv, joint, weight, color);
+            }
+
+            Array.Sort(verts);
+
+            // var vertices = new Span(verts);
+            var vertices = new ArraySegment(verts);
+
+            var stride = 200; // 総当り比較するブロック数。小さいほど計算は早いが余裕を持たせてある程度の値を確保している
+
+            for (int i = 0; i < vertices.Count; i += (stride))
+            {
+                stride = Math.Min(stride, vertices.Count - i);
+                var dividedIndices = vertices.Slice(i, stride);
+                FindMergeTargetVertices(ref dividedIndices);
+            }
+
+            // 統合する頂点の組み合わせを作成
+            var mergePairVatexDictionary = new Dictionary(); // key: remove vertex index , value : merge target vertex index
+            foreach (var v in vertices)
+            {
+                if (v.IsMergeTarget) continue;
+                if (v.targetIndex > 0)
+                {
+                    if (mergePairVatexDictionary.ContainsKey(v.Index)) continue;
+                    mergePairVatexDictionary.Add(v.Index, v.targetIndex);
+                }
+            }
+
+            // 統合されて消える頂点ID群を作成
+            var removeIndexList = new List(); // index補正用
+            var removeIndexHashSet = new HashSet();
+            foreach (var v in vertices)
+            {
+                if (v.IsMergeTarget) continue;
+                if (v.targetIndex > 0)
+                {
+                    removeIndexList.Add(v.Index);
+                    removeIndexHashSet.Add(v.Index);
+                }
+            }
+            removeIndexList.Sort();
+
+            int resizedLength = positions.Length - removeIndexHashSet.Count();
+
+            var indexOffsetArray = new int[indices.Length];
+
+            int offset = 0;
+            int currentIndex = 0;
+
+            // 統合後の頂点IDの補正値を計算
+            foreach (var index in removeIndexList)
+            {
+                for (int i = currentIndex; i <= index; i++)
+                {
+                    indexOffsetArray[i] = offset;
+                }
+                currentIndex = index + 1;
+                offset++;
+            }
+            for (int i = currentIndex; i < indices.Length; i++)
+            {
+                indexOffsetArray[i] = offset;
+            }
+
+            // merge vertex index
+            for (int i = 0; i < indices.Length; i++)
+            {
+                if (mergePairVatexDictionary.ContainsKey(indices[i]))
+                {
+                    var newIndex = mergePairVatexDictionary[indices[i]];
+                    var correctedIndex = newIndex - indexOffsetArray[newIndex];
+
+                    if (correctedIndex < 0 || correctedIndex >= resizedLength)
+                    {
+                        throw new IndexOutOfRangeException("recalculated vertex index are invalid after vertex merged");
+                    }
+                    indices[i] = correctedIndex;
+                }
+                else
+                {
+                    var correctedIndex = indices[i] - indexOffsetArray[indices[i]];
+
+                    if (correctedIndex < 0 || correctedIndex > resizedLength)
+                    {
+                        throw new IndexOutOfRangeException("recalculated vertex index are invalid after vertex merged");
+                    }
+
+                    indices[i] = correctedIndex;
+                }
+            }
+
+            if (positions.Length > ushort.MaxValue)
+            {
+                mesh.IndexBuffer.Assign(indices.AsSpan());
+            }
+            else
+            {
+                mesh.IndexBuffer.AssignAsShort(indices.AsSpan());
+            }
+
+            // 統合後、平均値を割り当てるパラメータは更新する
+            foreach (var v in vertices)
+            {
+                if (v.IsMergeTarget)
+                {
+                    if (normals != null) normals[v.Index] = v.Normal.Value;
+                    if (colors != null) colors[v.Index] = v.Color.Value;
+                }
+            }
+
+            // merge vertex 
+            var newPositions = new Vector3[resizedLength];
+            var newNormals = normals != null ? new Vector3[resizedLength] : null;
+            var newJoints = joints != null ? new SkinJoints[resizedLength] : null;
+            var newWeights = weights != null ? new Vector4[resizedLength] : null;
+            var newUVs = UVs != null ? new Vector2[resizedLength] : null;
+            var newColors = colors != null ? new Vector4[resizedLength] : null;
+
+            for (int i = 0, targetIndex = 0; i < positions.Length; i++)
+            {
+                if (removeIndexHashSet.Contains(i)) continue;
+                newPositions[targetIndex] = positions[i];
+                if (normals != null) newNormals[targetIndex] = normals[i];
+                if (joints != null) newJoints[targetIndex] = joints[i];
+                if (weights != null) newWeights[targetIndex] = weights[i];
+                if (UVs != null) newUVs[targetIndex] = UVs[i];
+                if (colors != null) newColors[targetIndex] = colors[i];
+
+                targetIndex++;
+            }
+
+            mesh.VertexBuffer.Positions.Assign(newPositions.AsSpan());
+            if (normals != null) mesh.VertexBuffer.Normals.Assign(newNormals.AsSpan());
+            if (joints != null) mesh.VertexBuffer.Joints.Assign(newJoints.AsSpan());
+            if (weights != null) mesh.VertexBuffer.Weights.Assign(newWeights.AsSpan());
+            if (UVs != null) mesh.VertexBuffer.TexCoords.Assign(newUVs.AsSpan());
+            if (colors != null) mesh.VertexBuffer.Colors.Assign(newColors.AsSpan());
+            mesh.VertexBuffer.RemoveTangent();
+            mesh.VertexBuffer.ValidateLength();
+
+            //
+            // merge morph vertex buffer
+            //
+            Span morphPositions = null;
+            Span morphNormals = null;
+            Span morphUVs = null;
+            foreach (var morph in mesh.MorphTargets)
+            {
+                morphPositions = morph.VertexBuffer.Positions != null ? morph.VertexBuffer.Positions.GetSpan() : null;
+                morphNormals = morph.VertexBuffer.Normals != null ? morph.VertexBuffer.Normals.GetSpan() : null;
+                morphUVs = morph.VertexBuffer.TexCoords != null ? morph.VertexBuffer.TexCoords.GetSpan() : null;
+
+                for (int i = 0, targetIndex = 0; i < positions.Length; i++)
+                {
+                    if (removeIndexHashSet.Contains(i)) continue;
+
+                    if (morphPositions != null) newPositions[targetIndex] = morphPositions[i];
+                    if (morphNormals != null) newNormals[targetIndex] = morphNormals[i];
+                    if (morphUVs != null) newUVs[targetIndex] = morphUVs[i];
+                    targetIndex++;
+                }
+
+                if (morphPositions != null) morph.VertexBuffer.Positions.Assign(newPositions.AsSpan());
+                if (morphNormals != null) morph.VertexBuffer.Normals.Assign(newNormals.AsSpan());
+                if (morphUVs != null) morph.VertexBuffer.Normals.Assign(newUVs.AsSpan());
+                morph.VertexBuffer.RemoveTangent();
+                morph.VertexBuffer.ValidateLength(morph.Name);
+            }
+
+            return $"({before} => {resizedLength})";
+        }
+
+        /// 
+        /// 統合できる同一とみなせる頂点を探す
+        /// 
+        private static void FindMergeTargetVertices(SpanLike vertices)
+        {            
+            var sameVertices = new HashSet();
+            for (int i = 0; i < vertices.Length; i++)
+            {
+                sameVertices.Clear();
+                var v0 = vertices[i];
+                int minIndex = v0.Index;
+                if (v0.targetIndex > 0) continue;
+                for (int j = i + 1; j < vertices.Length; j++)
+                {
+                    var v1 = vertices[j];
+
+                    if (v1.targetIndex > 0) continue;
+
+                    if (!v0.Equals(v1)) continue;
+
+                    if (!sameVertices.Contains(v0)) sameVertices.Add(v0);
+                    if (!sameVertices.Contains(v1))
+                    {
+                        sameVertices.Add(v1);
+                        if (v1.Index < minIndex) minIndex = v1.Index;
+                    }
+                }
+
+                if (sameVertices.Count() >= 2)
+                {
+                    foreach (var v in sameVertices)
+                    {
+                        v.targetIndex = minIndex;
+
+                        if (v.IsMergeTarget)
+                        {
+                            Vector3? n = Vector3.Zero;
+                            Vector4? c = Vector4.Zero;
+
+                            foreach (var vert in sameVertices)
+                            {
+                                if (vert.Normal.HasValue) n += vert.Normal;
+                                if (vert.Color.HasValue) c += vert.Color;
+
+                            }
+                            if (n.HasValue) Vector3.Normalize(n.Value);
+                            if (c.HasValue) c = c.Value / sameVertices.Count();
+
+
+                            v.Normal = n;
+                            v.Color = c;
+                        }
+                    }
+                }
+            }
+
+            return;
+        }
+#endif
+
+        static void ReorderSubmeshes(this Mesh mesh, List order)
+        {
+            if (mesh.Submeshes.Count != order.Count)
+            {
+                throw new Exception();
+            }
+            mesh.Submeshes.Clear();
+            mesh.Submeshes.AddRange(order);
+
+            // index buffer を作り直す
+            mesh.IndexBuffer = new SubmeshReorderMapper(mesh.Triangles).CreateIndexBuffer();
+
+            // submesh の offset を正規化する
+            var offset = 0;
+            for (int i = 0; i < mesh.Submeshes.Count; ++i)
+            {
+                var submesh = mesh.Submeshes[i];
+                submesh.Offset = offset;
+                offset += submesh.DrawCount;
+            }
+
+            {
+                // 連続して同じMaterialのSubMeshを連結する
+                mesh.Submeshes.Clear();
+                Material current = null;
+                foreach (var submesh in order)
+                {
+                    if (current != submesh.Material)
+                    {
+                        current = submesh.Material;
+                        mesh.Submeshes.Add(submesh);
+                    }
+                    else
+                    {
+                        mesh.Submeshes.Last().DrawCount += submesh.DrawCount;
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Runtime/MeshExtensions.cs.meta b/Assets/VRM10/vrmlib/Runtime/MeshExtensions.cs.meta
new file mode 100644
index 000000000..42772c7d8
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/MeshExtensions.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 1d56612ad117ef64fb1e374d054f449b
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/MeshFactory.cs b/Assets/VRM10/vrmlib/Runtime/MeshFactory.cs
new file mode 100644
index 000000000..546985c0b
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/MeshFactory.cs
@@ -0,0 +1,28 @@
+using System.Numerics;
+
+namespace VrmLib
+{
+    public static class MeshFactory
+    {
+        public static Mesh CreateQuadrangle()
+        {
+            var mesh = new Mesh();
+            mesh.IndexBuffer = BufferAccessor.Create(new int[] { 0, 1, 2, 2, 3, 0 });
+            mesh.VertexBuffer = new VertexBuffer();
+            mesh.VertexBuffer.Add(VertexBuffer.PositionKey, new Vector3[]{
+                    new Vector3(-1, 1, 0),
+                    new Vector3(1, 1, 0),
+                    new Vector3(1, -1, 0),
+                    new Vector3(-1, -1, 0),
+                });
+            mesh.VertexBuffer.Add(VertexBuffer.TexCoordKey, new Vector2[]{
+                    new Vector2(0, 0),
+                    new Vector2(1, 0),
+                    new Vector2(1, 1),
+                    new Vector2(0, 1),
+                });
+            mesh.Submeshes.Add(new Submesh(0, 6, new Material("SCREEN")));
+            return mesh;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Runtime/MeshFactory.cs.meta b/Assets/VRM10/vrmlib/Runtime/MeshFactory.cs.meta
new file mode 100644
index 000000000..f52f73bcf
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/MeshFactory.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d7c93f9da54dc08429261518417be9aa
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/MeshGroupExtensions.cs b/Assets/VRM10/vrmlib/Runtime/MeshGroupExtensions.cs
new file mode 100644
index 000000000..87493b2c4
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/MeshGroupExtensions.cs
@@ -0,0 +1,105 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Runtime.InteropServices;
+
+namespace VrmLib
+{
+    public static class MeshGroupExtensions
+    {
+        /// 
+        /// MorphTarget が有る Mesh と無い Mesh に分ける
+        /// 
+        /// 
+        /// 
+        public static ValueTuple SepareteByMorphTarget(this MeshGroup g)
+        {
+            if (g.Meshes.Count > 1)
+            {
+                throw new NotImplementedException("MeshGroup.Meshes.Count must 1");
+            }
+
+            var src = g.Meshes[0];
+            if (src.Topology != TopologyType.Triangles)
+            {
+                throw new InvalidOperationException("not GltfPrimitiveMode.Triangles");
+            }
+
+            var (withTriangles, withoutTriangles) = MeshSplitter.SplitTriangles(src);
+
+            MeshGroup with = default(MeshGroup);
+            if (withTriangles.Any())
+            {
+                var mesh = MeshSplitter.SeparateMesh(src, withTriangles, true);
+                with = new MeshGroup(g.Name + ".morphtarget")
+                {
+                    Skin = g.Skin,
+                };
+                with.Meshes.Add(mesh);
+            }
+
+            MeshGroup without = default(MeshGroup);
+            if (withoutTriangles.Any())
+            {
+                var mesh = MeshSplitter.SeparateMesh(src, withoutTriangles);
+                without = new MeshGroup(g.Name)
+                {
+                    Skin = g.Skin,
+                };
+                without.Meshes.Add(mesh);
+            }
+
+            return (with, without);
+        }
+
+        public static ValueTuple SepareteByHeadBone(this MeshGroup g, HashSet boneIndices)
+        {
+            if (g.Meshes.Count > 1)
+            {
+                throw new NotImplementedException("MeshGroup.Meshes.Count must 1");
+            }
+
+            var src = g.Meshes[0];
+            if (src.Topology != TopologyType.Triangles)
+            {
+                throw new InvalidOperationException("not GltfPrimitiveMode.Triangles");
+            }
+
+            var (headTriangles, bodyTriangles) = MeshSplitter.SplitTrianglesByBoneIndices(src, boneIndices);
+
+            MeshGroup head = default(MeshGroup);
+            if (headTriangles.Any())
+            {
+                var mesh = MeshSplitter.SeparateMesh(src, headTriangles, true);
+                head = new MeshGroup(g.Name + ".headMesh")
+                {
+                    Skin = g.Skin,
+                };
+                head.Meshes.Add(mesh);
+            }
+
+            MeshGroup body = default(MeshGroup);
+            if (bodyTriangles.Any())
+            {
+                var mesh = MeshSplitter.SeparateMesh(src, bodyTriangles);
+                body = new MeshGroup(g.Name)
+                {
+                    Skin = g.Skin,
+                };
+                body.Meshes.Add(mesh);
+            }
+
+            return (head, body);
+        }
+
+        public static MeshGroup Clone(this MeshGroup src)
+        {
+            throw new NotImplementedException();
+
+            // var dst = new MeshGroup(src.Name);
+
+            // return dst;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Runtime/MeshGroupExtensions.cs.meta b/Assets/VRM10/vrmlib/Runtime/MeshGroupExtensions.cs.meta
new file mode 100644
index 000000000..32d772765
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/MeshGroupExtensions.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 66fe20a63e11a3b4fb734a57c5a71839
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/MeshMorphTargetSplitter.cs b/Assets/VRM10/vrmlib/Runtime/MeshMorphTargetSplitter.cs
new file mode 100644
index 000000000..e16f05591
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/MeshMorphTargetSplitter.cs
@@ -0,0 +1,266 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+
+namespace VrmLib
+{
+    class MeshSplitter
+    {
+        public static ValueTuple<
+            List>,
+            List>> SplitTriangles(Mesh src)
+        {
+            var triangles = src.Triangles.ToArray();
+            var morphUseCount = new int[triangles.Length];
+
+            // 各モーフ
+            foreach (var morph in src.MorphTargets)
+            {
+                if (morph.VertexBuffer.Count == 0)
+                {
+                    // 無効
+                    continue;
+                }
+                // POSITIONが使っているtriangleのカウンターをアップさせる
+                var positions = SpanLike.Wrap(morph.VertexBuffer.Positions.Bytes);
+                for (int i = 0; i < triangles.Length; ++i)
+                {
+                    ref var triangle = ref triangles[i];
+                    if (positions[triangle.Item2.Vertex0] != Vector3.Zero)
+                    {
+                        ++morphUseCount[i];
+                    }
+                    if (positions[triangle.Item2.Vertex1] != Vector3.Zero)
+                    {
+                        ++morphUseCount[i];
+                    }
+                    if (positions[triangle.Item2.Vertex2] != Vector3.Zero)
+                    {
+                        ++morphUseCount[i];
+                    }
+                }
+            }
+
+            var withTriangles = new List>();
+            var withoutTriangles = new List>();
+            for (int i = 0; i < triangles.Length; ++i)
+            {
+                if (morphUseCount[i] > 0)
+                {
+                    // モーフで使われている
+                    withTriangles.Add(triangles[i]);
+                }
+                else
+                {
+                    // モーフで使われない
+                    withoutTriangles.Add(triangles[i]);
+                }
+            }
+
+            return (withTriangles, withoutTriangles);
+        }
+
+        public static ValueTuple<
+           List>,
+           List>> SplitTrianglesByBoneIndices(Mesh src, HashSet targetBoneIndices)
+        {
+            var triangles = src.Triangles.ToArray();
+            var isBodyMeshTriangle = new bool[triangles.Length];
+
+            var skinJointsSpan = SpanLike.Wrap(src.VertexBuffer.Joints.Bytes);
+            var boneWeightSpan = SpanLike.Wrap(src.VertexBuffer.Weights.Bytes);
+
+            for (int i = 0; i < triangles.Length; ++i)
+            {
+                ref var triangle = ref triangles[i];
+
+                var skinJoints0 = skinJointsSpan[triangle.Item2.Vertex0];
+                var boneWeights0 = boneWeightSpan[triangle.Item2.Vertex0];
+
+                if (boneWeights0.X > 0 && targetBoneIndices.Contains(skinJoints0.Joint0)) continue;
+                if (boneWeights0.Y > 0 && targetBoneIndices.Contains(skinJoints0.Joint1)) continue;
+                if (boneWeights0.Z > 0 && targetBoneIndices.Contains(skinJoints0.Joint2)) continue;
+                if (boneWeights0.W > 0 && targetBoneIndices.Contains(skinJoints0.Joint3)) continue;
+
+                var skinJoints1 = skinJointsSpan[triangle.Item2.Vertex1];
+                var boneWeights1 = boneWeightSpan[triangle.Item2.Vertex1];
+
+                if (boneWeights1.X > 0 && targetBoneIndices.Contains(skinJoints1.Joint0)) continue;
+                if (boneWeights1.Y > 0 && targetBoneIndices.Contains(skinJoints1.Joint1)) continue;
+                if (boneWeights1.Z > 0 && targetBoneIndices.Contains(skinJoints1.Joint2)) continue;
+                if (boneWeights1.W > 0 && targetBoneIndices.Contains(skinJoints1.Joint3)) continue;
+
+                var skinJoints2 = skinJointsSpan[triangle.Item2.Vertex2];
+                var boneWeights2 = boneWeightSpan[triangle.Item2.Vertex2];
+
+                if (boneWeights2.X > 0 && targetBoneIndices.Contains(skinJoints2.Joint0)) continue;
+                if (boneWeights2.Y > 0 && targetBoneIndices.Contains(skinJoints2.Joint1)) continue;
+                if (boneWeights2.Z > 0 && targetBoneIndices.Contains(skinJoints2.Joint2)) continue;
+                if (boneWeights2.W > 0 && targetBoneIndices.Contains(skinJoints2.Joint3)) continue;
+
+                isBodyMeshTriangle[i] = true;
+            }
+
+            var bodyTriangles = new List>();
+            var headTriangles = new List>();
+
+            for (int i = 0; i < triangles.Length; ++i)
+            {
+                if (isBodyMeshTriangle[i])
+                {
+                    // 胴体メッシュ
+                    bodyTriangles.Add(triangles[i]);
+                }
+                else
+                {
+                    // 頭メッシュ
+                    headTriangles.Add(triangles[i]);
+                }
+            }
+
+            return (headTriangles, bodyTriangles);
+        }
+
+        class VertexReorderMapper
+        {
+            public readonly List IndexMap = new List();
+
+            public BufferAccessor MapBuffer(BufferAccessor srcBuffer) where T : struct
+            {
+                var src = SpanLike.Wrap(srcBuffer.Bytes);
+                var dstBytes = new byte[srcBuffer.Stride * IndexMap.Count];
+                var dst = SpanLike.Wrap(new ArraySegment(dstBytes));
+                for (int i = 0; i < IndexMap.Count; ++i)
+                {
+                    dst[i] = src[IndexMap[i]];
+                }
+                return new BufferAccessor(new ArraySegment(dstBytes), srcBuffer.ComponentType, srcBuffer.AccessorType, IndexMap.Count);
+            }
+
+            public VertexBuffer Map(VertexBuffer src)
+            {
+                var dst = new VertexBuffer();
+                foreach (var (semantic, buffer) in src)
+                {
+                    BufferAccessor mapped = null;
+                    if (buffer.ComponentType == AccessorValueType.FLOAT)
+                    {
+                        switch (buffer.AccessorType)
+                        {
+                            case AccessorVectorType.VEC2:
+                                mapped = MapBuffer(buffer);
+                                break;
+
+                            case AccessorVectorType.VEC3:
+                                mapped = MapBuffer(buffer);
+                                break;
+
+                            case AccessorVectorType.VEC4:
+                                mapped = MapBuffer(buffer);
+                                break;
+
+                            default:
+                                throw new NotImplementedException();
+                        }
+                    }
+                    else if (buffer.ComponentType == AccessorValueType.UNSIGNED_SHORT)
+                    {
+                        if (buffer.AccessorType == AccessorVectorType.VEC4)
+                        {
+                            mapped = MapBuffer(buffer);
+                        }
+                        else
+                        {
+                            throw new NotImplementedException();
+                        }
+                    }
+                    else
+                    {
+                        throw new NotImplementedException();
+                    }
+                    dst.Add(semantic, mapped);
+                }
+
+                return dst;
+            }
+
+            public readonly List Indices = new List();
+
+            public ArraySegment IndexBytes
+            {
+                get
+                {
+                    var bytes = new byte[Indices.Count * 4];
+                    var span = SpanLike.Wrap(new ArraySegment(bytes));
+                    for (int i = 0; i < span.Length; ++i)
+                    {
+                        span[i] = Indices[i];
+                    }
+                    return new ArraySegment(bytes);
+                }
+            }
+
+            public BufferAccessor CreateIndexBuffer()
+            {
+                return new BufferAccessor(IndexBytes,
+                AccessorValueType.UNSIGNED_INT, AccessorVectorType.SCALAR, Indices.Count);
+            }
+
+            void PushIndex(int src)
+            {
+                int index = IndexMap.IndexOf(src);
+                if (index == -1)
+                {
+                    // index to src map
+                    index = IndexMap.Count;
+                    IndexMap.Add(src);
+                }
+                Indices.Add(index);
+            }
+
+            public VertexReorderMapper(IEnumerable> submeshTriangles)
+            {
+                foreach (var t in submeshTriangles)
+                {
+                    PushIndex(t.Item2.Vertex0);
+                    PushIndex(t.Item2.Vertex1);
+                    PushIndex(t.Item2.Vertex2);
+                }
+            }
+        }
+        public static Mesh SeparateMesh(Mesh src, IEnumerable> submeshTriangles,
+            bool includeMorphTarget = false)
+        {
+            var mapper = new VertexReorderMapper(submeshTriangles);
+
+            var mesh = new Mesh
+            {
+                Topology = TopologyType.Triangles,
+                IndexBuffer = mapper.CreateIndexBuffer(),
+                VertexBuffer = mapper.Map(src.VertexBuffer),
+            };
+
+            var offset = 0;
+            foreach (var triangles in submeshTriangles.GroupBy(x => x.Item1, x => x).OrderBy(x => x.Key))
+            {
+                var count = triangles.Count() * 3;
+                mesh.Submeshes.Add(new Submesh(offset, count, src.Submeshes[triangles.Key].Material));
+                offset += count;
+            }
+
+            if (includeMorphTarget)
+            {
+                foreach (var target in src.MorphTargets)
+                {
+                    mesh.MorphTargets.Add(new MorphTarget(target.Name)
+                    {
+                        VertexBuffer = mapper.Map(target.VertexBuffer)
+                    });
+                }
+            }
+
+            return mesh;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Runtime/MeshMorphTargetSplitter.cs.meta b/Assets/VRM10/vrmlib/Runtime/MeshMorphTargetSplitter.cs.meta
new file mode 100644
index 000000000..0001c8a8f
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/MeshMorphTargetSplitter.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f3f90205124143446bab06f56827a6ee
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Model.cs b/Assets/VRM10/vrmlib/Runtime/Model.cs
new file mode 100644
index 000000000..deb5546fb
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Model.cs
@@ -0,0 +1,221 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace VrmLib
+{
+    /// 
+    /// 処理しやすいようにした中間形式
+    /// * index 参照は実参照
+    /// * accessor, bufferView は実バイト列(ArraySegment)
+    /// * meshは、subMesh方式(indexが offset + length)
+    /// 
+    public class Model
+    {
+        public Model(Coordinates coordinates)
+        {
+            Coordinates = coordinates;
+        }
+
+        public ArraySegment OriginalJson;
+
+        public Coordinates Coordinates;
+
+        public string AssetVersion = "2.0";
+        public string AssetGenerator;
+        public string AssetCopyright;
+        public string AssetMinVersion;
+
+        // gltf/images
+        public readonly List Images = new List();
+
+        // gltf/textures
+        public readonly List Textures = new List();
+
+        // gltf/materials
+        public readonly List Materials = new List();
+
+        // gltf/skins
+        public readonly List Skins = new List();
+
+        // gltf/meshes
+        public readonly List MeshGroups = new List();
+
+        // gltf の nodes に含まれない。sceneに相当
+        Node m_root = new Node("__root__");
+
+        public Node Root
+        {
+            get => m_root;
+            private set
+            {
+
+            }
+        }
+        public void SetRoot(Node root)
+        {
+            m_root = root;
+
+            Nodes.Clear();
+            Nodes.AddRange(root.Traverse().Skip(1));
+        }
+
+        // gltf/nodes
+        public List Nodes = new List();
+
+        // gltf/animations
+        public List Animations = new List();
+
+        /// 
+        /// アニメーションに時間を指定するインターフェース
+        /// 
+        public void SetTime(int index, TimeSpan elapsed)
+        {
+            if (index < 0 || index >= Animations.Count)
+            {
+                return;
+            }
+            Animations[index].SetTime(elapsed);
+            Root.CalcWorldMatrix();
+        }
+
+        public Vrm Vrm;
+
+        public Dictionary GetBoneMap()
+        {
+            return Root.Traverse()
+                .Where(x => x.HumanoidBone.HasValue)
+                .ToDictionary(x => x.HumanoidBone.Value, x => x);
+        }
+
+        public override string ToString()
+        {
+            var sb = new StringBuilder();
+            sb.Append($"[GLTF] generator: {AssetGenerator}\n");
+
+            for (int i = 0; i < Images.Count; ++i)
+            {
+                var x = Images[i];
+                sb.Append($"[Image#{i:00}] {x}\n");
+            }
+            // for (int i = 0; i < Textures.Count; ++i)
+            // {
+            //     var t = Textures[i];
+            //     sb.Append($"[Texture#{i:00}] {t}\n");
+            // }
+            for (int i = 0; i < Materials.Count; ++i)
+            {
+                var m = Materials[i];
+                sb.Append($"[Material#{i:00}] {m}\n");
+            }
+            for (int i = 0; i < MeshGroups.Count; ++i)
+            {
+                var m = MeshGroups[i];
+                sb.Append($"[Mesh#{i:00}] {m}\n");
+            }
+            for (int i = 0; i < Animations.Count; ++i)
+            {
+                var a = Animations[i];
+                sb.Append($"[Animation#{i:00}] {a}\n");
+            }
+            sb.Append($"[Node] {Nodes.Count} nodes\n");
+
+            foreach (var skin in Skins)
+            {
+                sb.Append($"[Skin] {skin}\n");
+            }
+
+            //
+            // VRM
+            //
+            if (Vrm != null)
+            {
+                sb.Append($"[VRM] export: {Vrm.ExporterVersion}, spec: {Vrm.SpecVersion}\n");
+                sb.Append($"[VRM][meta] {Vrm.Meta}\n");
+                var boneMap = GetBoneMap();
+                if (boneMap.Any())
+                {
+                    sb.Append($"[VRM][humanoid] {boneMap.Count}/{Enum.GetValues(typeof(HumanoidBones)).Length - 1}\n");
+                    if (boneMap.Keys.Contains(HumanoidBones.unknown))
+                    {
+                        sb.Append($"[VRM][humanoid] {boneMap.Count} contains 'unknown'\n");
+                    }
+                    if (boneMap.TryGetValue(HumanoidBones.jaw, out Node jaw))
+                    {
+                        sb.Append($"[VRM][humanoid] contains 'jaw' => {jaw.Name}\n");
+                    }
+                }
+                if (Vrm.ExpressionManager != null
+                    && Vrm.ExpressionManager.ExpressionList != null
+                    && Vrm.ExpressionManager.ExpressionList.Any())
+                {
+                    sb.Append("[VRM][expression] ");
+                    foreach (var ex in Vrm.ExpressionManager.ExpressionList)
+                    {
+                        sb.Append($"[{ex.Preset}]");
+                    }
+                    sb.Append($"\n");
+                }
+            }
+            return sb.ToString();
+        }
+
+        /// 
+        /// HumanoidBonesの構成チェック
+        /// 
+        /// 
+        public bool CheckVrmHumanoid()
+        {
+            var vrmhumanoids = new HashSet();
+
+            // HumanoidBonesの重複チェック
+            foreach (var node in Nodes)
+            {
+                if (node.HumanoidBone.HasValue)
+                {
+                    if (vrmhumanoids.Contains(node.HumanoidBone.Value))
+                    {
+                        return false;
+                    }
+                    else
+                    {
+                        vrmhumanoids.Add(node.HumanoidBone.Value);
+                    }
+                }
+            }
+
+            // HumanoidBonesでBoneRequiredAttributeが定義されているものすべてが使われているかどうかを判断
+
+            var boneattributes
+                = Enum.GetValues(typeof(HumanoidBones)).Cast()
+                        .Select(bone => bone.GetType().GetField(bone.ToString()))
+                        .Select(info => info.GetCustomAttributes(typeof(BoneRequiredAttribute), false) as BoneRequiredAttribute[])
+                        .Where(attributes => attributes.Length > 0);
+
+            var nodeHumanoids
+                = vrmhumanoids
+                    .Select(humanoid => humanoid.GetType().GetField(humanoid.ToString()))
+                    .Select(info => info.GetCustomAttributes(typeof(BoneRequiredAttribute), false) as BoneRequiredAttribute[])
+                    .Where(attributes => attributes.Length > 0);
+
+            if (nodeHumanoids.Count() != boneattributes.Count()) return false;
+
+            return true;
+        }
+
+        public static Node GetNode(Node root, string path)
+        {
+            var splitted = path.Split('/');
+            var it = splitted.Select(x => x).GetEnumerator();
+
+            var current = root;
+            while (it.MoveNext())
+            {
+                current = current.Children.First(x => x.Name == it.Current);
+            }
+
+            return current;
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/Model.cs.meta b/Assets/VRM10/vrmlib/Runtime/Model.cs.meta
new file mode 100644
index 000000000..e458589f0
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Model.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 988a2640f02f7ff479f717fabdadf436
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/ModelDiff.cs b/Assets/VRM10/vrmlib/Runtime/ModelDiff.cs
new file mode 100644
index 000000000..f4c830b57
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ModelDiff.cs
@@ -0,0 +1,135 @@
+using System;
+using System.Collections.Generic;
+
+namespace VrmLib.Diff
+{
+    static class ObjectExtensions
+    {
+        public static bool IsNull(this T self)
+        {
+            if (typeof(T).IsClass)
+            {
+                return self == null;
+            }
+            else
+            {
+                return false;
+            }
+        }
+    }
+
+    public struct ModelDiff
+    {
+        public string Context;
+        public string Message;
+
+        public override string ToString()
+        {
+            return $"{Context}: {Message}";
+        }
+    }
+
+    public struct ModelDiffContext
+    {
+        public readonly string Path;
+
+        public readonly List List;
+
+        ModelDiffContext(string path, List list)
+        {
+            Path = path;
+            List = list;
+        }
+
+        public bool Push(T lhs, T rhs, Func pred = null)
+        {
+            if (pred != null)
+            {
+                if (!pred(this, lhs, rhs))
+                {
+                    List.Add(new ModelDiff
+                    {
+                        Context = Path,
+                        Message = $"{lhs} != {rhs}",
+                    });
+                    return false;
+                }
+                return true;
+            }
+
+            if (!RequireComapre(lhs, rhs, out bool equals))
+            {
+                return equals;
+            }
+
+            if (lhs.Equals(rhs))
+            {
+                return true;
+            }
+            else
+            {
+                List.Add(new ModelDiff
+                {
+                    Context = Path,
+                    Message = $"{lhs} != {rhs}",
+                });
+                return false;
+            }
+        }
+
+        public ModelDiffContext Enter(string key)
+        {
+            if (string.IsNullOrEmpty(Path))
+            {
+                return new ModelDiffContext(key, List);
+            }
+            else
+            {
+                return new ModelDiffContext(Path + "." + key, List);
+            }
+        }
+
+        public static ModelDiffContext Create()
+        {
+            return new ModelDiffContext("", new List());
+        }
+
+        public bool RequireComapre(object lhs, object rhs, out bool equals)
+        {
+            if (lhs is null)
+            {
+                if (rhs is null)
+                {
+                    equals = true;
+                    return false;
+                }
+                else
+                {
+                    equals = false;
+                    List.Add(new ModelDiff
+                    {
+                        Context = Path,
+                        Message = "lhs is null"
+                    });
+                    return false;
+                }
+            }
+            else
+            {
+                if (rhs is null)
+                {
+                    equals = false;
+                    List.Add(new ModelDiff
+                    {
+                        Context = Path,
+                        Message = "rhs is null"
+                    });
+                    return false;
+                }
+            }
+
+            equals = false;
+            return true;
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/ModelDiff.cs.meta b/Assets/VRM10/vrmlib/Runtime/ModelDiff.cs.meta
new file mode 100644
index 000000000..2f7236761
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ModelDiff.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 1cef47aa218fe78498b3bc0e698b817a
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/ModelDiffExtensions.cs b/Assets/VRM10/vrmlib/Runtime/ModelDiffExtensions.cs
new file mode 100644
index 000000000..d64dc2440
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ModelDiffExtensions.cs
@@ -0,0 +1,534 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using VrmLib.MToon;
+
+namespace VrmLib.Diff
+{
+    public static class ModelDiffExtensions
+    {
+        /// 
+        /// 違うところを集める(debug用)
+        /// 
+        public static List Diff(this Model lhs, Model rhs)
+        {
+            var context = ModelDiffContext.Create();
+            context.Enter(nameof(lhs.AssetGenerator)).Push(lhs.AssetGenerator, rhs.AssetGenerator, StringEquals);
+            context.Enter(nameof(lhs.AssetVersion)).Push(lhs.AssetVersion, rhs.AssetVersion, StringEquals);
+            context.Enter(nameof(lhs.AssetMinVersion)).Push(lhs.AssetMinVersion, rhs.AssetMinVersion, StringEquals);
+            context.Enter(nameof(lhs.AssetCopyright)).Push(lhs.AssetCopyright, rhs.AssetCopyright, StringEquals);
+
+            // Materialの参照で比較する
+            ListDiff(context.Enter("Materials"), lhs.Materials, rhs.Materials, MaterialEquals);
+            ListDiff(context.Enter("Meshes"), lhs.MeshGroups, rhs.MeshGroups, MeshGroupEquals);
+            ListDiff(context.Enter("Nodes"), lhs.Nodes, rhs.Nodes, NodeEquals);
+            ListDiff(context.Enter("Skins"), lhs.Skins, rhs.Skins, SkinEquals);
+            Vrm(context.Enter("Vrm"), lhs, rhs);
+
+            return context.List;
+        }
+
+        #region Private
+        static bool ListDiff(ModelDiffContext context, List lhs, List rhs, Func pred, Func order = null)
+        {
+            var equals = true;
+            if (lhs.Count != rhs.Count)
+            {
+                equals = false;
+                context.List.Add(new ModelDiff
+                {
+                    Context = context.Path,
+                    Message = $"{lhs.Count} != {rhs.Count}",
+                });
+            }
+
+            var l = order != null ? lhs.OrderBy(order).GetEnumerator() : lhs.GetEnumerator();
+            var r = order != null ? rhs.OrderBy(order).GetEnumerator() : rhs.GetEnumerator();
+            for (int i = 0; i < lhs.Count; ++i)
+            {
+                l.MoveNext();
+                r.MoveNext();
+                if (!pred(context.Enter($"{i}"), l.Current, r.Current))
+                    equals = false;
+            }
+            return equals;
+        }
+
+        const float EPSILON = 1e-5f;
+
+        static bool Vector3NearlyEquals(ModelDiffContext _, Vector3 l, Vector3 r)
+        {
+            if (Math.Abs(l.X - r.X) > EPSILON) return false;
+            if (Math.Abs(l.Y - r.Y) > EPSILON) return false;
+            if (Math.Abs(l.Z - r.Z) > EPSILON) return false;
+            return true;
+        }
+
+        static bool QuaternionNearlyEquals(ModelDiffContext _, Quaternion l, Quaternion r)
+        {
+            if (Math.Abs(l.X - r.X) > EPSILON) return false;
+            if (Math.Abs(l.Y - r.Y) > EPSILON) return false;
+            if (Math.Abs(l.Z - r.Z) > EPSILON) return false;
+            if (Math.Abs(l.W - r.W) > EPSILON) return false;
+            return true;
+        }
+
+        static bool StringEquals(ModelDiffContext _, string l, string r)
+        {
+            if (string.IsNullOrEmpty(l))
+            {
+                return string.IsNullOrEmpty(r);
+            }
+            else
+            {
+                if (string.IsNullOrEmpty(r))
+                {
+                    return false;
+                }
+                else
+                {
+                    return l == r;
+                }
+            }
+        }
+
+        static bool ImageBytesEquals(ModelDiffContext context, Image lhs, Image rhs)
+        {
+            if (lhs is null)
+            {
+                if (rhs is null)
+                {
+                    return true;
+                }
+                else
+                {
+                    return false;
+                }
+            }
+            if (rhs is null)
+            {
+                return false;
+            }
+            return lhs.Bytes.SequenceEqual(rhs.Bytes);
+        }
+
+        static void Image(ModelDiffContext context, Image lhs, Image rhs)
+        {
+            context.Enter($"{lhs.Name}:{rhs.Name}").Push(lhs, rhs, ImageBytesEquals);
+        }
+
+        static bool TextureInfoEquals(ModelDiffContext context, TextureInfo lhs, TextureInfo rhs)
+        {
+            if (!context.RequireComapre(lhs, rhs, out bool equals))
+            {
+                return equals;
+            }
+
+            if (lhs.Offset != rhs.Offset)
+            {
+                return false;
+            }
+            if (lhs.Scaling != rhs.Scaling)
+            {
+                return false;
+            }
+            return TextureEquals(context.Enter("Texture"), lhs.Texture, rhs.Texture);
+        }
+
+        static bool TextureEquals(ModelDiffContext context, Texture lhs, Texture rhs)
+        {
+            if (!context.RequireComapre(lhs, rhs, out bool equals))
+            {
+                if (!equals)
+                    return false;
+                return true;
+            }
+
+            equals = true;
+            if (!context.Enter("Name").Push(lhs.Name, rhs.Name, StringEquals))
+                equals = false;
+            if (!context.Enter("MagFilter").Push(lhs.Sampler.MagFilter, rhs.Sampler.MagFilter))
+                equals = false;
+            if (!context.Enter("MinFilter").Push(lhs.Sampler.MinFilter, rhs.Sampler.MinFilter))
+                equals = false;
+            if (!context.Enter("WrapS").Push(lhs.Sampler.WrapS, rhs.Sampler.WrapS))
+                equals = false;
+            if (!context.Enter("WrapT").Push(lhs.Sampler.WrapT, rhs.Sampler.WrapT))
+                equals = false;
+            if (lhs is ImageTexture l && rhs is ImageTexture r)
+            {
+                if (!ImageBytesEquals(context, l.Image, r.Image))
+                    equals = false;
+                return equals;
+            }
+            else
+            {
+                return false;
+            }
+        }
+
+        static void Texture(ModelDiffContext context, Texture lhs, Texture rhs)
+        {
+            context.Enter($"{lhs.Name}:{rhs.Name}").Push(lhs, rhs, TextureEquals);
+        }
+
+        static bool BaseMaterialEquals(ModelDiffContext context, Material lhs, Material rhs)
+        {
+            var equals = true;
+            if (!context.Enter(nameof(lhs.AlphaCutoff)).Push(lhs.AlphaCutoff, rhs.AlphaCutoff)) equals = false;
+            if (!context.Enter(nameof(lhs.AlphaMode)).Push(lhs.AlphaMode, rhs.AlphaMode)) equals = false;
+            if (!context.Enter(nameof(lhs.BaseColorFactor)).Push(lhs.BaseColorFactor, rhs.BaseColorFactor)) equals = false;
+            if (!context.Enter(nameof(lhs.BaseColorTexture)).Push(lhs.BaseColorTexture?.Texture, rhs.BaseColorTexture?.Texture, TextureEquals)) equals = false;
+            if (!context.Enter(nameof(lhs.DoubleSided)).Push(lhs.DoubleSided, rhs.DoubleSided)) equals = false;
+            return equals;
+        }
+
+        static bool PBRMaterialEquals(ModelDiffContext context, PBRMaterial lhs, PBRMaterial rhs)
+        {
+            var equals = true;
+            if (!BaseMaterialEquals(context, lhs, rhs)) equals = false;
+            if (!context.Enter(nameof(lhs.EmissiveFactor)).Push(lhs.EmissiveFactor, rhs.EmissiveFactor)) equals = false;
+            if (!context.Enter(nameof(lhs.EmissiveTexture)).Push(lhs.EmissiveTexture, rhs.EmissiveTexture, TextureEquals)) equals = false;
+            if (!context.Enter(nameof(lhs.MetallicFactor)).Push(lhs.MetallicFactor, rhs.MetallicFactor)) equals = false;
+            if (!context.Enter(nameof(lhs.MetallicRoughnessTexture)).Push(lhs.MetallicRoughnessTexture, rhs.MetallicRoughnessTexture, TextureEquals)) equals = false;
+            if (!context.Enter(nameof(lhs.NormalTexture)).Push(lhs.NormalTexture, rhs.NormalTexture, TextureEquals)) equals = false;
+            if (!context.Enter(nameof(lhs.OcclusionTexture)).Push(lhs.OcclusionTexture, rhs.OcclusionTexture, TextureEquals)) equals = false;
+            if (!context.Enter(nameof(lhs.RoughnessFactor)).Push(lhs.RoughnessFactor, rhs.RoughnessFactor)) equals = false;
+            return equals;
+        }
+
+        static bool MToonDefinitionEquals(ModelDiffContext context, object lhs, object rhs, Type t)
+        {
+            if (!context.RequireComapre(lhs, rhs, out bool equals))
+            {
+                return equals;
+            }
+
+            equals = true;
+            foreach (var fi in t.GetFields())
+            {
+                if (fi.FieldType == typeof(TextureInfo))
+                {
+                    if (!context.Enter(fi.Name).Push(fi.GetValue(lhs) as TextureInfo, fi.GetValue(rhs) as TextureInfo, TextureInfoEquals))
+                        equals = false;
+                }
+                else
+                {
+                    if (!context.Enter(fi.Name).Push(fi.GetValue(lhs), fi.GetValue(rhs)))
+                        equals = false;
+                }
+            }
+            return equals;
+        }
+
+        static bool MToonDefinitionEquals(ModelDiffContext context, MToonDefinition lhs, MToonDefinition rhs)
+        {
+            var equals = true;
+            if (!MToonDefinitionEquals(context.Enter(nameof(MetaDefinition)), lhs?.Meta, rhs?.Meta, typeof(MetaDefinition)))
+                equals = false;
+            if (!MToonDefinitionEquals(context.Enter(nameof(ColorDefinition)), lhs?.Color, rhs?.Color, typeof(ColorDefinition)))
+                equals = false;
+            if (!MToonDefinitionEquals(context.Enter(nameof(OutlineDefinition)), lhs?.Outline, rhs?.Outline, typeof(OutlineDefinition)))
+                equals = false;
+            if (!MToonDefinitionEquals(context.Enter(nameof(LightingInfluenceDefinition)), lhs?.Lighting.LightingInfluence, rhs?.Lighting.LightingInfluence, typeof(LightingInfluenceDefinition)))
+                equals = false;
+            if (!MToonDefinitionEquals(context.Enter(nameof(LitAndShadeMixingDefinition)), lhs?.Lighting.LitAndShadeMixing, rhs?.Lighting.LitAndShadeMixing, typeof(LitAndShadeMixingDefinition)))
+                equals = false;
+            if (!MToonDefinitionEquals(context.Enter(nameof(EmissionDefinition)), lhs?.Emission, rhs?.Emission, typeof(EmissionDefinition)))
+                equals = false;
+            if (!MToonDefinitionEquals(context.Enter(nameof(MatCapDefinition)), lhs?.MatCap, rhs?.MatCap, typeof(MatCapDefinition)))
+                equals = false;
+            if (!MToonDefinitionEquals(context.Enter(nameof(RimDefinition)), lhs?.Rim, rhs?.Rim, typeof(RimDefinition)))
+                equals = false;
+            if (!MToonDefinitionEquals(context.Enter(nameof(TextureUvCoordsDefinition)), lhs?.TextureOption, rhs?.TextureOption, typeof(TextureUvCoordsDefinition)))
+                equals = false;
+            return equals;
+        }
+
+        static bool MToonMaterialEquals(ModelDiffContext context, MToonMaterial lhs, MToonMaterial rhs)
+        {
+            var equals = true;
+            if (!BaseMaterialEquals(context, lhs, rhs))
+                equals = false;
+            if (!context.Enter(nameof(lhs._DebugMode)).Push(lhs._DebugMode, rhs._DebugMode))
+                equals = false;
+            if (!context.Enter(nameof(lhs._DstBlend)).Push(lhs._DstBlend, rhs._DstBlend))
+                equals = false;
+            if (!context.Enter(nameof(lhs._SrcBlend)).Push(lhs._SrcBlend, rhs._SrcBlend))
+                equals = false;
+            if (!context.Enter(nameof(lhs._ZWrite)).Push(lhs._ZWrite, rhs._ZWrite))
+                equals = false;
+            if (!MToonDefinitionEquals(context.Enter(nameof(lhs.Definition)), lhs.Definition, rhs.Definition))
+                equals = false;
+            // context.Enter(nameof(lhs.KeyWords)).Push( lhs.KeyWords, rhs.KeyWords);
+            return equals;
+        }
+
+        static bool UnlitMaterialEquals(ModelDiffContext context, UnlitMaterial lhs, UnlitMaterial rhs)
+        {
+            return BaseMaterialEquals(context, lhs, rhs);
+        }
+
+        public static bool MaterialEquals(ModelDiffContext context, Material l, Material r)
+        {
+            var equals = true;
+            if (!context.Enter("Name").Push(l.Name, r.Name, StringEquals))
+                equals = false;
+
+            // context.Enter($"{i}:{lhs[i].Name}:{rhs[i].Name}").Push( lhs[i], rhs[i], MaterialEquals);
+            if (l.GetType() != r.GetType())
+            {
+                context.Enter($"Type").Push(l.GetType(), r.GetType());
+                equals = false;
+            }
+            else if (l is PBRMaterial lp && r is PBRMaterial rp)
+            {
+                if (!PBRMaterialEquals(context.Enter($"(PBRMaterial)"), lp, rp))
+                    equals = false;
+            }
+            else if (l is MToonMaterial lm && r is MToonMaterial rm)
+            {
+                if (!MToonMaterialEquals(context.Enter($"(MToonMaterial)"), lm, rm))
+                    equals = false;
+            }
+            else if (l is UnlitMaterial lu && r is UnlitMaterial ru)
+            {
+                if (!UnlitMaterialEquals(context.Enter($"(UnlitMaterial)"), lu, ru))
+                    equals = false;
+            }
+            else
+            {
+                throw new Exception();
+            }
+            return equals;
+        }
+
+        static bool AccessorEquals(ModelDiffContext context, BufferAccessor lhs, BufferAccessor rhs)
+        {
+            if (!context.RequireComapre(lhs, rhs, out bool equals))
+            {
+                return equals;
+            }
+
+            return lhs.Bytes.SequenceEqual(rhs.Bytes);
+        }
+
+        static bool VertexBufferEquals(ModelDiffContext context, VertexBuffer lhs, VertexBuffer rhs)
+        {
+            var equals = true;
+            foreach (var kv in lhs)
+            {
+                rhs.TryGetValue(kv.Key, out BufferAccessor accessor);
+                if (!context.Enter(kv.Key).Push(kv.Value, accessor, AccessorEquals)) equals = false;
+            }
+            return equals;
+        }
+
+        static bool MeshEquals(ModelDiffContext context, Mesh lhs, Mesh rhs)
+        {
+            return VertexBufferEquals(context.Enter(nameof(lhs.VertexBuffer)), lhs.VertexBuffer, rhs.VertexBuffer);
+        }
+
+        static bool MeshGroupEquals(ModelDiffContext context, MeshGroup lhs, MeshGroup rhs)
+        {
+            return ListDiff(context.Enter("Meshes"), lhs.Meshes, rhs.Meshes, MeshEquals);
+        }
+
+        static bool NodeEquals(ModelDiffContext context, Node lhs, Node rhs)
+        {
+            if (lhs is null)
+            {
+                if (rhs is null)
+                {
+                    return true;
+                }
+                else
+                {
+                    return false;
+                }
+            }
+            else
+            {
+                if (rhs is null)
+                {
+                    return false;
+                }
+            }
+
+            var equals = true;
+            if (!context.Enter(nameof(lhs.Name)).Push(lhs.Name, rhs.Name, StringEquals)) equals = false;
+            if (!context.Enter(nameof(lhs.LocalTranslation)).Push(lhs.LocalTranslation, rhs.LocalTranslation, Vector3NearlyEquals)) equals = false;
+            if (!context.Enter(nameof(lhs.LocalRotation)).Push(lhs.LocalRotation, rhs.LocalRotation, QuaternionNearlyEquals)) equals = false;
+            if (!context.Enter(nameof(lhs.LocalScaling)).Push(lhs.LocalScaling, rhs.LocalScaling, Vector3NearlyEquals)) equals = false;
+            if (!context.Enter(nameof(lhs.Parent)).Push(lhs.Parent?.Name, rhs.Parent?.Name)) equals = false;
+            if (!context.Enter(nameof(lhs.HumanoidBone)).Push(lhs.HumanoidBone, rhs.HumanoidBone)) equals = false;
+            return equals;
+        }
+
+        static bool SkinEquals(ModelDiffContext context, Skin lhs, Skin rhs)
+        {
+            var equals = true;
+            if (!context.Enter("Root").Push(lhs.Root, rhs.Root, NodeEquals)) equals = false;
+            if (!ListDiff(context.Enter("Joints"), lhs.Joints, rhs.Joints, NodeEquals)) equals = false;
+            if (!context.Enter("InverseMatrices").Push(lhs.InverseMatrices, rhs.InverseMatrices, AccessorEquals)) equals = false;
+            return equals;
+        }
+
+        static void Vrm(ModelDiffContext context, Model lhs, Model rhs)
+        {
+            context.Enter(nameof(lhs.Vrm.SpecVersion)).Push(lhs.Vrm.SpecVersion, rhs.Vrm.SpecVersion);
+            context.Enter(nameof(lhs.Vrm.ExporterVersion)).Push(lhs.Vrm.ExporterVersion, rhs.Vrm.ExporterVersion);
+            VrmMeta(context.Enter(nameof(lhs.Vrm.Meta)), lhs.Vrm.Meta, rhs.Vrm.Meta);
+            ListDiff(context.Enter(nameof(lhs.Vrm.ExpressionManager)), lhs.Vrm.ExpressionManager.ExpressionList, rhs.Vrm.ExpressionManager.ExpressionList, VrmExpressionEquals, x => (int)x.Preset);
+            VrmFirstPerson(context.Enter(nameof(lhs.Vrm.FirstPerson)), lhs.Vrm.FirstPerson, rhs.Vrm.FirstPerson);
+            VrmLookAt(context.Enter(nameof(lhs.Vrm.LookAt)), lhs.Vrm.LookAt, rhs.Vrm.LookAt);
+            ListDiff(context.Enter("SpringBone.Springs"), lhs.Vrm.SpringBone.Springs, rhs.Vrm.SpringBone.Springs, VrmSpringBoneEquals);
+            ListDiff(context.Enter("SpringBone.Colliders"), lhs.Vrm.SpringBone.Colliders, rhs.Vrm.SpringBone.Colliders, VrmSpringBoneColliderEquals);
+        }
+
+        static void VrmMeta(ModelDiffContext context, Meta lhs, Meta rhs)
+        {
+            context.Enter(nameof(lhs.Name)).Push(lhs.Name, rhs.Name);
+            context.Enter(nameof(lhs.Version)).Push(lhs.Version, rhs.Version);
+            context.Enter(nameof(lhs.CopyrightInformation)).Push(lhs.CopyrightInformation, rhs.CopyrightInformation);
+            context.Enter(nameof(lhs.Author)).Push(lhs.Author, rhs.Author);
+            context.Enter(nameof(lhs.ContactInformation)).Push(lhs.ContactInformation, rhs.ContactInformation);
+            context.Enter(nameof(lhs.Reference)).Push(lhs.Reference, rhs.Reference);
+            context.Enter(nameof(lhs.Thumbnail)).Push(lhs.Thumbnail, rhs.Thumbnail, ImageBytesEquals);
+            // AvatarPermission
+            context.Enter(nameof(lhs.AvatarPermission.AvatarUsage)).Push(lhs.AvatarPermission.AvatarUsage, rhs.AvatarPermission.AvatarUsage);
+            context.Enter(nameof(lhs.AvatarPermission.IsAllowedViolentUsage)).Push(lhs.AvatarPermission.IsAllowedViolentUsage, rhs.AvatarPermission.IsAllowedViolentUsage);
+            context.Enter(nameof(lhs.AvatarPermission.IsAllowedSexualUsage)).Push(lhs.AvatarPermission.IsAllowedSexualUsage, rhs.AvatarPermission.IsAllowedSexualUsage);
+            context.Enter(nameof(lhs.AvatarPermission.IsAllowedCommercialUsage)).Push(lhs.AvatarPermission.IsAllowedCommercialUsage, rhs.AvatarPermission.IsAllowedCommercialUsage);
+            context.Enter(nameof(lhs.AvatarPermission.CommercialUsage)).Push(lhs.AvatarPermission.CommercialUsage, rhs.AvatarPermission.CommercialUsage);
+            context.Enter(nameof(lhs.AvatarPermission.IsAllowedCommercialUsage)).Push(lhs.AvatarPermission.IsAllowedCommercialUsage, rhs.AvatarPermission.IsAllowedCommercialUsage);
+            context.Enter(nameof(lhs.AvatarPermission.IsAllowedCommercialUsage)).Push(lhs.AvatarPermission.IsAllowedCommercialUsage, rhs.AvatarPermission.IsAllowedCommercialUsage);
+            context.Enter(nameof(lhs.AvatarPermission.OtherPermissionUrl)).Push(lhs.AvatarPermission.OtherPermissionUrl, rhs.AvatarPermission.OtherPermissionUrl);
+            // RedistributionLicense
+            context.Enter(nameof(lhs.RedistributionLicense.License)).Push(lhs.RedistributionLicense.License, rhs.RedistributionLicense.License);
+            context.Enter(nameof(lhs.RedistributionLicense.OtherLicenseUrl)).Push(lhs.RedistributionLicense.OtherLicenseUrl, rhs.RedistributionLicense.OtherLicenseUrl);
+        }
+
+        static bool VrmExpressionEquals(ModelDiffContext context, Expression lhs, Expression rhs)
+        {
+            if (lhs.IsNull())
+            {
+                if (rhs.IsNull())
+                {
+                    // ok
+                    return true;
+                }
+                else
+                {
+                    context.List.Add(new ModelDiff
+                    {
+                        Context = context.Path,
+                        Message = "lhs is null",
+                    });
+                    return false;
+                }
+            }
+            else
+            {
+                if (rhs.IsNull())
+                {
+                    context.List.Add(new ModelDiff
+                    {
+                        Context = context.Path,
+                        Message = "rhs is null",
+                    });
+                    return false;
+                }
+            }
+
+            var equals = true;
+            if (!context.Enter(nameof(lhs.Preset)).Push(lhs.Preset, rhs.Preset)) equals = false;
+            if (!context.Enter(nameof(lhs.Name)).Push(lhs.Name, rhs.Name, StringEquals)) equals = false;
+            if (!context.Enter(nameof(lhs.IsBinary)).Push(lhs.IsBinary, rhs.IsBinary)) equals = false;
+            if (!ListDiff(context.Enter(nameof(lhs.MorphTargetBinds)), lhs.MorphTargetBinds, rhs.MorphTargetBinds, VrmExpressionBindValueEquals)) equals = false;
+            if (!ListDiff(context.Enter(nameof(lhs.MaterialColorBinds)), lhs.MaterialColorBinds, rhs.MaterialColorBinds, VrmMaterialBindValueEquals)) equals = false;
+            return equals;
+        }
+
+        static bool VrmExpressionBindValueEquals(ModelDiffContext context, MorphTargetBind lhs, MorphTargetBind rhs)
+        {
+            var equals = true;
+            if (!context.Enter("Node").Push(lhs.Node, rhs.Node, NodeEquals)) equals = false;
+            if (!context.Enter("Name").Push(lhs.Name, rhs.Name)) equals = false;
+            if (!context.Enter("Value").Push(lhs.Value, rhs.Value)) equals = false;
+            return equals;
+        }
+
+        static bool VrmMaterialBindValueEquals(ModelDiffContext context, MaterialColorBind lhs, MaterialColorBind rhs)
+        {
+            var equals = true;
+            if (!context.Enter("Material.Name").Push(lhs.Material.Name, rhs.Material.Name)) equals = false;
+            if (!context.Enter("Property").Push(lhs.Property, rhs.Property)) equals = false;
+            // if (!context.Enter("Value").Push(lhs.m_value, rhs.m_value)) equals = false;
+            if (!context.Enter("BindType").Push(lhs.BindType, rhs.BindType)) equals = false;
+            return equals;
+        }
+
+        static bool FirstPersonMeshAnnotationEquals(ModelDiffContext context, FirstPersonMeshAnnotation lhs, FirstPersonMeshAnnotation rhs)
+        {
+            var equals = true;
+            if (!context.Enter("Node").Push(lhs.Node, rhs.Node, NodeEquals)) equals = false;
+            if (!context.Enter("Flag").Push(lhs.FirstPersonFlag, rhs.FirstPersonFlag)) equals = false;
+            return equals;
+        }
+
+        static void VrmFirstPerson(ModelDiffContext context, FirstPerson lhs, FirstPerson rhs)
+        {
+            // context.Enter("HeadNode").Push(lhs.m_fp, rhs.m_fp, NodeEquals);
+            ListDiff(context.Enter("Annotations"), lhs.Annotations, rhs.Annotations, FirstPersonMeshAnnotationEquals);
+        }
+
+        static void VrmLookAt(ModelDiffContext context, LookAt lhs, LookAt rhs)
+        {
+            context.Enter("Offset").Push(lhs.OffsetFromHeadBone, rhs.OffsetFromHeadBone, Vector3NearlyEquals);
+            context.Enter(nameof(lhs.LookAtType)).Push(lhs.LookAtType, rhs.LookAtType);
+            VrmLookAtRangeMap(context.Enter(nameof(lhs.HorizontalInner)), lhs.HorizontalInner, rhs.HorizontalInner);
+            VrmLookAtRangeMap(context.Enter(nameof(lhs.HorizontalOuter)), lhs.HorizontalOuter, rhs.HorizontalOuter);
+            VrmLookAtRangeMap(context.Enter(nameof(lhs.VerticalUp)), lhs.VerticalUp, rhs.VerticalUp);
+            VrmLookAtRangeMap(context.Enter(nameof(lhs.VerticalDown)), lhs.VerticalDown, rhs.VerticalDown);
+        }
+
+        static void VrmLookAtRangeMap(ModelDiffContext context, LookAtRangeMap lhs, LookAtRangeMap rhs)
+        {
+            context.Enter(nameof(lhs.InputMaxValue)).Push(lhs.InputMaxValue, rhs.InputMaxValue);
+            context.Enter(nameof(lhs.OutputScaling)).Push(lhs.OutputScaling, rhs.OutputScaling);
+            context.Enter("Curve").Push(lhs.Curve, rhs.Curve);
+        }
+
+        static bool VrmSpringBoneEquals(ModelDiffContext context, SpringBone lhs, SpringBone rhs)
+        {
+            var equals = true;
+            if (!context.Enter("Comment").Push(lhs.Comment, rhs.Comment)) equals = false;
+            if (!context.Enter("DragForce").Push(lhs.DragForce, rhs.DragForce)) equals = false;
+            if (!context.Enter("GravityDir").Push(lhs.GravityDir, rhs.GravityDir)) equals = false;
+            if (!context.Enter("GravityPower").Push(lhs.GravityPower, rhs.GravityPower)) equals = false;
+            if (!context.Enter("HitRadius").Push(lhs.HitRadius, rhs.HitRadius)) equals = false;
+            if (!context.Enter("Origin").Push(lhs.Origin, rhs.Origin)) equals = false;
+            if (!context.Enter("Stiffness").Push(lhs.Stiffness, rhs.Stiffness)) equals = false;
+            return equals;
+        }
+
+        static bool VrmSpringBoneColliderEquals(ModelDiffContext context, VrmSpringBoneCollider lhs, VrmSpringBoneCollider rhs)
+        {
+            var equals = true;
+            if (!context.Enter("Offset").Push(lhs.Offset, rhs.Offset)) equals = false;
+            if (!context.Enter("Radius").Push(lhs.Radius, rhs.Radius)) equals = false;
+            return equals;
+        }
+
+        static bool VrmSpringBoneColliderEquals(ModelDiffContext context, SpringBoneColliderGroup lhs, SpringBoneColliderGroup rhs)
+        {
+            var equals = true;
+            if (!context.Enter("Node").Push(lhs.Node, rhs.Node, NodeEquals)) equals = false;
+            if (!ListDiff(context.Enter("Colliders"), lhs.Colliders, rhs.Colliders, VrmSpringBoneColliderEquals)) equals = false;
+            return equals;
+        }
+        #endregion
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/ModelDiffExtensions.cs.meta b/Assets/VRM10/vrmlib/Runtime/ModelDiffExtensions.cs.meta
new file mode 100644
index 000000000..3b2d45403
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ModelDiffExtensions.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 93cf70a1afdd7784e8c964726d98edd6
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/ModelExtensions.cs b/Assets/VRM10/vrmlib/Runtime/ModelExtensions.cs
new file mode 100644
index 000000000..2decb5c83
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ModelExtensions.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+
+namespace VrmLib
+{
+    public static class ModelExtensions
+    {
+        public static void ApplyRotationAndScale(this Model model)
+        {
+            // worldPositionを記録する
+            var m_positionMap = model.Nodes.ToDictionary(x => x, x => x.Translation);
+
+            // 回転・拡縮を除去する
+            // 木構造の根元から実行する
+            // Rootは編集対象外
+            foreach (var node in model.Root.Traverse().Skip(1))
+            {
+                // 回転・拡縮を除去
+                if (m_positionMap.TryGetValue(node, out Vector3 pos))
+                {
+                    var t = Matrix4x4.CreateTranslation(pos);
+                    node.SetMatrix(t, false);
+                }
+            }
+        }
+
+        /// 
+        // [Debug向け]secondaryを除去
+        /// 
+        public static void RemoveSecondary(this Model model)
+        {
+            var secondary = model.Nodes
+            .FirstOrDefault(x =>
+                (x.Name == "secondary" || x.Name == "SpringBone")
+                && x.Parent == model.Root
+                && x.Children.Count == 0)
+            ;
+            if (secondary != null)
+            {
+                var mod = new ModelModifier(model);
+                mod.NodeRemove(secondary);
+            }
+        }
+
+        static void CheckIndex(List list, string name) where T : GltfId
+        {
+            for (int i = 0; i < list.Count; ++i)
+            {
+                if (list[i].GltfIndex.HasValue)
+                {
+                    if (list[i].GltfIndex.Value == i)
+                    {
+                        Console.WriteLine($"{name}[{i}] => OK");
+                    }
+                    else
+                    {
+                        Console.WriteLine($"{name}[{i}] => {list[i].GltfIndex}");
+                    }
+                }
+                else
+                {
+                    Console.WriteLine($"{name}[{i}] => null");
+                }
+            }
+        }
+
+        public static void CheckIndex(this Model model)
+        {
+            CheckIndex(model.Images, nameof(model.Images));
+            CheckIndex(model.Textures, nameof(model.Textures));
+            CheckIndex(model.Materials, nameof(model.Materials));
+            CheckIndex(model.Nodes, nameof(model.Nodes));
+            CheckIndex(model.Skins, nameof(model.Skins));
+            CheckIndex(model.MeshGroups, nameof(model.MeshGroups));
+            CheckIndex(model.Animations, nameof(model.Animations));
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/ModelExtensions.cs.meta b/Assets/VRM10/vrmlib/Runtime/ModelExtensions.cs.meta
new file mode 100644
index 000000000..f4d882891
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ModelExtensions.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: abaf884e0faa5d040ad4eae16120cb46
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/ModelExtensionsForBvh.cs b/Assets/VRM10/vrmlib/Runtime/ModelExtensionsForBvh.cs
new file mode 100644
index 000000000..6aad37c32
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ModelExtensionsForBvh.cs
@@ -0,0 +1,303 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Runtime.InteropServices;
+using VrmLib.Bvh;
+
+namespace VrmLib
+{
+    public static class ModelExtensionsForBvh
+    {
+        static float ToRad(float src)
+        {
+            return src / 180.0f * MathFWrap.PI;
+        }
+
+        static BvhNode GetNode(BvhNode root, string path)
+        {
+            var splitted = path.Split('/');
+
+            var it = splitted.Select(x => x).GetEnumerator();
+            var current = root;
+            if (splitted[0] == path)
+            {
+                return current;
+            }
+            it.MoveNext();
+            while (it.MoveNext())
+            {
+                current = current.Children.First(x => x.Name == it.Current);
+            }
+
+            return current;
+        }
+
+        public static Model CreateFromBvh(BvhNode node)
+        {
+            // add nodes
+            var model = new Model(Coordinates.Gltf);
+            model.Root.Name = "__bvh_root__";
+
+            AddBvhNodeRecursive(model, model.Root, node);
+
+            return model;
+        }
+
+        static void AddBvhNodeRecursive(Model model, Node parent, BvhNode node)
+        {
+            var newNode = new Node(node.Name)
+            {
+                HumanoidBone = node.Bone,
+            };
+
+            model.Nodes.Add(newNode);
+            parent.Add(newNode);
+            newNode.Translation = node.SkeletonLocalPosition;
+
+            foreach (var child in node.Children)
+            {
+                AddBvhNodeRecursive(model, newNode, child);
+            }
+        }
+
+        class BvhNodeCurves
+        {
+            public Bvh.ChannelCurve LocalPositionX;
+            public Bvh.ChannelCurve LocalPositionY;
+            public Bvh.ChannelCurve LocalPositionZ;
+
+            public Bvh.ChannelCurve EulerX;
+            public Bvh.ChannelCurve EulerY;
+            public Bvh.ChannelCurve EulerZ;
+
+            public void Set(string prop, Bvh.ChannelCurve curve)
+            {
+                switch (prop)
+                {
+                    case "localPosition.x":
+                        LocalPositionX = curve;
+                        break;
+
+                    case "localPosition.y":
+                        LocalPositionY = curve;
+                        break;
+
+                    case "localPosition.z":
+                        LocalPositionZ = curve;
+                        break;
+
+                    case "localEulerAnglesBaked.x":
+                        EulerX = curve;
+                        break;
+
+                    case "localEulerAnglesBaked.y":
+                        EulerY = curve;
+                        break;
+
+                    case "localEulerAnglesBaked.z":
+                        EulerZ = curve;
+                        break;
+
+                    default:
+                        break;
+                }
+            }
+        }
+
+        static Animation LoadAnimation(string name, Bvh.Bvh bvh, Model model, float scalingFactor)
+        {
+            var animation = new Animation(name);
+
+            Dictionary pathMap = new Dictionary();
+
+            for (int i = 0; i < bvh.Channels.Length; ++i)
+            {
+                var channel = bvh.Channels[i];
+
+                if (!bvh.TryGetPathWithPropertyFromChannel(channel, out Bvh.Bvh.PathWithProperty prop))
+                {
+                    throw new Exception();
+                }
+
+                if (!pathMap.TryGetValue(prop.Path, out BvhNodeCurves curves))
+                {
+                    curves = new BvhNodeCurves();
+                    pathMap.Add(prop.Path, curves);
+                }
+
+                curves.Set(prop.Property, channel);
+            }
+
+            // setup time
+            var timeBytes = new byte[Marshal.SizeOf(typeof(float)) * bvh.FrameCount];
+            var timeSpan = SpanLike.Wrap(new ArraySegment(timeBytes));
+            var now = 0.0;
+            for (int i = 0; i < timeSpan.Length; ++i, now += bvh.FrameTime.TotalSeconds)
+            {
+                timeSpan[i] = (float)now;
+            }
+            var times = new BufferAccessor(new ArraySegment(timeBytes), AccessorValueType.FLOAT, AccessorVectorType.SCALAR, bvh.FrameCount);
+
+            foreach (var (key, nodeCurve) in pathMap)
+            {
+                var node = Model.GetNode(model.Root, key);
+                var bvhNode = GetNode(bvh.Root, key);
+                var curve = new NodeAnimation();
+
+                if (nodeCurve.LocalPositionX != null)
+                {
+                    var values = new byte[Marshal.SizeOf(typeof(Vector3))
+                        * nodeCurve.LocalPositionX.Keys.Length];
+                    var span = SpanLike.Wrap(new ArraySegment(values));
+                    for (int i = 0; i < nodeCurve.LocalPositionX.Keys.Length; ++i)
+                    {
+                        span[i] = new Vector3
+                        {
+                            X = nodeCurve.LocalPositionX.Keys[i] * scalingFactor,
+                            Y = nodeCurve.LocalPositionY.Keys[i] * scalingFactor,
+                            Z = nodeCurve.LocalPositionZ.Keys[i] * scalingFactor,
+                        };
+                    }
+                    var sampler = new CurveSampler
+                    {
+                        In = times,
+                        Out = new BufferAccessor(new ArraySegment(values),
+                            AccessorValueType.FLOAT, AccessorVectorType.VEC3, span.Length)
+                    };
+                    curve.Curves.Add(AnimationPathType.Translation, sampler);
+                }
+
+                if (nodeCurve.EulerX != null)
+                {
+                    var values = new byte[Marshal.SizeOf(typeof(Quaternion))
+                        * nodeCurve.EulerX.Keys.Length];
+                    var span = SpanLike.Wrap(new ArraySegment(values));
+
+                    Func getRot = (q, c, i) => q;
+
+                    foreach (var ch in bvhNode.Channels)
+                    {
+                        var tmp = getRot;
+                        switch (ch)
+                        {
+                            case Channel.Xrotation:
+                                getRot = (_, c, i) =>
+                                {
+                                    return tmp(_, c, i) *
+                                    Quaternion.CreateFromAxisAngle(Vector3.UnitX, ToRad(c.EulerX.Keys[i]));
+                                };
+                                break;
+                            case Channel.Yrotation:
+                                getRot = (_, c, i) =>
+                                {
+                                    return tmp(_, c, i) *
+                                    Quaternion.CreateFromAxisAngle(Vector3.UnitY, ToRad(c.EulerY.Keys[i]));
+                                };
+                                break;
+                            case Channel.Zrotation:
+                                getRot = (_, c, i) =>
+                                {
+                                    return tmp(_, c, i) *
+                                    Quaternion.CreateFromAxisAngle(Vector3.UnitZ, ToRad(c.EulerZ.Keys[i]));
+                                };
+                                break;
+                            default:
+                                // throw new NotImplementedException();
+                                break;
+                        }
+                    }
+
+                    for (int i = 0; i < nodeCurve.EulerX.Keys.Length; ++i)
+                    {
+                        span[i] = getRot(Quaternion.Identity, nodeCurve, i);
+                    }
+                    var sampler = new CurveSampler
+                    {
+                        In = times,
+                        Out = new BufferAccessor(new ArraySegment(values),
+                            AccessorValueType.FLOAT, AccessorVectorType.VEC4, span.Length)
+                    };
+                    curve.Curves.Add(AnimationPathType.Rotation, sampler);
+                }
+
+                animation.AddCurve(node, curve);
+            }
+
+            return animation;
+        }
+
+        public static Model Load(string name, Bvh.Bvh bvh)
+        {
+            var model = CreateFromBvh(bvh.Root);
+
+            // estimate skeleton
+            var skeleton = SkeletonEstimator.Detect(model.Root);
+            if (skeleton == null)
+            {
+                throw new Exception("fail to estimate skeleton");
+            }
+
+            // foot to zero
+            var minY = model.Nodes.Min(x => x.Translation.Y);
+            var hips = model.Nodes.First(x => x.HumanoidBone == HumanoidBones.hips);
+            if (model.Root.Children.Count != 1)
+            {
+                throw new Exception();
+            }
+            if (model.Root.Children[0] != hips)
+            {
+                throw new Exception();
+            }
+            hips.Translation -= new Vector3(0, minY, 0);
+
+            // normalize scale
+            var pos = hips.Translation;
+            var factor = 1.0f;
+            if (pos.Y != 0)
+            {
+                factor = 1.0f / pos.Y;
+                foreach (var x in hips.Traverse())
+                {
+                    x.LocalTranslation *= factor;
+                }
+                hips.Translation = new Vector3(pos.X, 1.0f, pos.Z);
+            }
+
+            // animation
+            model.Animations.Add(LoadAnimation(name, bvh, model, factor));
+
+            // add origin
+            var origin = new Node("origin");
+            origin.Add(model.Root.Children[0]);
+            model.Nodes.Add(origin);
+            model.Root.Add(origin);
+
+            return model;
+        }
+
+        public static void CreateBoxMan(this Model model)
+        {
+            // skin
+            var skin = new Skin();
+            skin.Joints.AddRange(model.Nodes);
+            skin.CalcInverseMatrices();
+
+            // mesh
+            var group = new MeshGroup("box-man")
+            {
+                Skin = skin,
+            };
+            var builder = new MeshBuilder();
+            builder.Build(model.Nodes);
+            group.Meshes.Add(builder.CreateMesh());
+            model.MeshGroups.Add(group);
+
+            // node
+            var meshNode = new Node("mesh");
+            meshNode.MeshGroup = group;
+            model.Nodes.Add(meshNode);
+            model.Root.Add(meshNode);
+        }
+    }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Runtime/ModelExtensionsForBvh.cs.meta b/Assets/VRM10/vrmlib/Runtime/ModelExtensionsForBvh.cs.meta
new file mode 100644
index 000000000..cf83bf98c
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ModelExtensionsForBvh.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 13b0cacd23e61e1468b9b9891e0e6656
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/ModelExtensionsForCoordinates.cs b/Assets/VRM10/vrmlib/Runtime/ModelExtensionsForCoordinates.cs
new file mode 100644
index 000000000..45dae6d52
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ModelExtensionsForCoordinates.cs
@@ -0,0 +1,238 @@
+using System;
+using System.Linq;
+using System.Numerics;
+
+namespace VrmLib
+{
+    public static class ModelExtensionsForCoordinates
+    {
+        /// 
+        /// ignoreVrm: VRM-0.XX では無変換で入出力してた。VRM-1.0 では変換する。
+        /// 
+        public static void ConvertCoordinate(this Model model, Coordinates coordinates, bool ignoreVrm = false)
+        {
+            if (model.Coordinates.Equals(coordinates))
+            {
+                return;
+            }
+
+            if (model.Coordinates.IsGltf && coordinates.IsUnity)
+            {
+                model.ReverseZAndFlipTriangle(ignoreVrm);
+                model.UVVerticalFlip();
+            }
+            else if (model.Coordinates.IsUnity && coordinates.IsGltf)
+            {
+                model.ReverseZAndFlipTriangle(ignoreVrm);
+                model.UVVerticalFlip();
+            }
+            else
+            {
+                throw new NotImplementedException();
+            }
+        }
+
+        /// 
+        /// UVのVを反転する。 => V = 1.0 - V
+        /// 
+        static void UVVerticalFlip(this Model model)
+        {
+            foreach (var g in model.MeshGroups)
+            {
+                foreach (var m in g.Meshes)
+                {
+                    var uv = m.VertexBuffer.TexCoords;
+                    if (uv != null)
+                    {
+                        var span = SpanLike.Wrap(uv.Bytes);
+                        for (int i = 0; i < span.Length; ++i)
+                        {
+                            span[i] = span[i].UVVerticalFlip();
+                        }
+                    }
+                }
+            }
+        }
+
+        /// 
+        /// * Position, Normal の Z座標に -1 を乗算する
+        /// * Rotation => Axis Angle に分解 => Axis の Z座標に -1 を乗算。Angle に -1 を乗算
+        /// * Triangle の index を 0, 1, 2 から 2, 1, 0 に反転する
+        /// 
+        static void ReverseZAndFlipTriangle(this Model model, bool ignoreVrm)
+        {
+            foreach (var g in model.MeshGroups)
+            {
+                foreach (var m in g.Meshes)
+                {
+                    foreach (var (k, v) in m.VertexBuffer)
+                    {
+                        if (k == VertexBuffer.PositionKey || k == VertexBuffer.NormalKey)
+                        {
+                            ReverseZ(v);
+                        }
+                        if (k == VertexBuffer.TangentKey)
+                        {
+                            // I don't know
+                        }
+                    }
+
+                    switch (m.IndexBuffer.ComponentType)
+                    {
+                        case AccessorValueType.UNSIGNED_BYTE:
+                            FlipTriangle(SpanLike.Wrap(m.IndexBuffer.Bytes));
+                            break;
+                        case AccessorValueType.UNSIGNED_SHORT:
+                            FlipTriangle(SpanLike.Wrap(m.IndexBuffer.Bytes));
+                            break;
+                        case AccessorValueType.UNSIGNED_INT:
+                            FlipTriangle(SpanLike.Wrap(m.IndexBuffer.Bytes));
+                            break;
+                        default:
+                            throw new NotImplementedException();
+                    }
+
+                    foreach (var mt in m.MorphTargets)
+                    {
+                        foreach (var (k, v) in mt.VertexBuffer)
+                        {
+                            if (k == VertexBuffer.PositionKey || k == VertexBuffer.NormalKey)
+                            {
+                                ReverseZ(v);
+                            }
+                            if (k == VertexBuffer.TangentKey)
+                            {
+                                // I don't know
+                            }
+                        }
+                    }
+                }
+            }
+
+            // 親から順に処理する
+            // Rootは原点決め打ちのノード(GLTFに含まれない)
+            foreach (var n in model.Root.Traverse().Skip(1))
+            {
+                n.SetMatrix(n.Matrix.ReverseZ(), false);
+            }
+            // 親から順に処理したので不要
+            // model.Root.CalcWorldMatrix();
+
+            foreach (var s in model.Skins)
+            {
+                if (s.InverseMatrices != null)
+                {
+                    ReverseZ(s.InverseMatrices);
+                }
+            }
+
+            foreach (var a in model.Animations)
+            {
+                // TODO:
+            }
+
+            if (model.Vrm != null)
+            {
+                if (!ignoreVrm)
+                {
+                    // LookAt
+                    if (model.Vrm.LookAt != null)
+                    {
+                        model.Vrm.LookAt.OffsetFromHeadBone = model.Vrm.LookAt.OffsetFromHeadBone.ReverseZ();
+                    }
+
+                    // SpringBone
+                    if (model.Vrm.SpringBone != null)
+                    {
+                        foreach (var b in model.Vrm.SpringBone.Springs)
+                        {
+                            foreach (var c in b.Colliders)
+                            {
+                                for (int i = 0; i < c.Colliders.Count; ++i)
+                                {
+                                    var s = c.Colliders[i];
+                                    switch (s.ColliderType)
+                                    {
+                                        case VrmSpringBoneColliderTypes.Sphere:
+                                            c.Colliders[i] = VrmSpringBoneCollider.CreateSphere(s.Offset.ReverseZ(), s.Radius);
+                                            break;
+
+                                        case VrmSpringBoneColliderTypes.Capsule:
+                                            c.Colliders[i] = VrmSpringBoneCollider.CreateCapsule(s.Offset.ReverseZ(), s.Radius, s.CapsuleTail.ReverseZ());
+                                            break;
+
+                                        default:
+                                            throw new NotImplementedException();
+                                    }
+                                }
+                            }
+
+                            b.GravityDir = b.GravityDir.ReverseZ();
+                        }
+                    }
+                }
+            }
+        }
+
+        static void ReverseZ(BufferAccessor ba)
+        {
+            if (ba.ComponentType != AccessorValueType.FLOAT)
+            {
+                throw new Exception();
+            }
+            if (ba.AccessorType == AccessorVectorType.VEC3)
+            {
+                var span = SpanLike.Wrap(ba.Bytes);
+                for (int i = 0; i < span.Length; ++i)
+                {
+                    span[i] = span[i].ReverseZ();
+                }
+            }
+            else if (ba.AccessorType == AccessorVectorType.MAT4)
+            {
+                var span = SpanLike.Wrap(ba.Bytes);
+                for (int i = 0; i < span.Length; ++i)
+                {
+                    span[i] = span[i].ReverseZ();
+                }
+            }
+            else
+            {
+                throw new NotImplementedException();
+            }
+        }
+
+        static void FlipTriangle(SpanLike indices)
+        {
+            for (int i = 0; i < indices.Length; i += 3)
+            {
+                // 0, 1, 2 to 2, 1, 0
+                var tmp = indices[i + 2];
+                indices[i + 2] = indices[i];
+                indices[i] = tmp;
+            }
+        }
+
+        static void FlipTriangle(SpanLike indices)
+        {
+            for (int i = 0; i < indices.Length; i += 3)
+            {
+                // 0, 1, 2 to 2, 1, 0
+                var tmp = indices[i + 2];
+                indices[i + 2] = indices[i];
+                indices[i] = tmp;
+            }
+        }
+
+        static void FlipTriangle(SpanLike indices)
+        {
+            for (int i = 0; i < indices.Length; i += 3)
+            {
+                // 0, 1, 2 to 2, 1, 0
+                var tmp = indices[i + 2];
+                indices[i + 2] = indices[i];
+                indices[i] = tmp;
+            }
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/ModelExtensionsForCoordinates.cs.meta b/Assets/VRM10/vrmlib/Runtime/ModelExtensionsForCoordinates.cs.meta
new file mode 100644
index 000000000..775a5bd90
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ModelExtensionsForCoordinates.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 382bf6be84d3135438af6a559a599fa3
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/ModelExtensionsForHumanoid.cs b/Assets/VRM10/vrmlib/Runtime/ModelExtensionsForHumanoid.cs
new file mode 100644
index 000000000..b46dee7d1
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ModelExtensionsForHumanoid.cs
@@ -0,0 +1,193 @@
+using System;
+using System.Linq;
+using System.Numerics;
+
+namespace VrmLib
+{
+    public static class ModelExtensionsForHumanoid
+    {
+        static ValueTuple GetUpperLower(Node root)
+        {
+            var legL = root.Traverse().FirstOrDefault(x => x.HumanoidBone == HumanoidBones.leftUpperLeg);
+            var legR = root.Traverse().FirstOrDefault(x => x.HumanoidBone == HumanoidBones.rightUpperLeg);
+            var head = root.Traverse().FirstOrDefault(x => x.HumanoidBone == HumanoidBones.head);
+
+            var parentL = legL.Parent;
+            var parentR = legR.Parent;
+            if (parentL != parentR)
+            {
+                throw new Exception("different leftLeg parent and rightLeg parent");
+            }
+            var lower = parentL;
+
+            var upperAncestors = head.Ancestors().ToList();
+            if (upperAncestors.Any(x => x == lower))
+            {
+                throw new Exception("lower is ancestor of head");
+            }
+
+            var lowerAncestors = legL.Ancestors().ToList();
+
+            while (true)
+            {
+                if (upperAncestors.Last() != lowerAncestors.Last())
+                {
+                    break;
+                }
+                upperAncestors.RemoveAt(upperAncestors.Count - 1);
+                lowerAncestors.RemoveAt(lowerAncestors.Count - 1);
+            }
+
+            return (upperAncestors.Last(), lowerAncestors.Last());
+        }
+
+        /// 
+        /// 
+        /// root
+        ///   upper
+        ///   lower
+        ///     legL
+        ///     legR
+        ///     
+        /// ↓
+        /// 
+        /// ①上半身をchestにする例
+        /// 
+        /// root(hips)
+        ///   legL
+        ///   legR
+        ///   lower(spine: 上下反転するため頭の位置が変わる)
+        ///     upper(chest)
+        /// 
+        /// ②上半身をspineにする例もありえる。その場合は、下半身とその親を近接させて 
+        /// 下半身にはhumanoidボーンを割り当てない(hipsとみなす)
+        /// 
+        /// root(hips)
+        ///   legL
+        ///   legR
+        ///   lower(上下反転するため頭の位置が変わる)
+        ///     upper(spine)
+        ///
+        /// ③もしくは下半身をhipsに繰り上げる
+        /// 
+        /// lower(hips: 上下反転するため頭の位置が変わる)
+        ///   legL
+        ///   legR
+        ///   upper(spine)
+        ///
+        /// 
+        public static string FixInvertedPelvis(this Model model)
+        {
+            var (upper, lower) = GetUpperLower(model.Root);
+            if (upper == null)
+            {
+                return "FixInvertedPelvis: upper not found. this is not humanoid ? do nothing";
+            }
+            if (lower == null)
+            {
+                return "FixInvertedPelvis: lower not found. this is model's pelvis is not inverted. do nothing";
+            }
+
+            // found lower. fix inverted pelvis...
+
+            var hips = model.Root.FindBone(HumanoidBones.hips);
+            {
+                hips.HumanoidBone = null;
+            }
+            var spine = model.Root.FindBone(HumanoidBones.spine);
+            {
+                spine.HumanoidBone = null;
+            }
+            var chest = model.Root.FindBone(HumanoidBones.chest);
+            if (chest != null)
+            {
+                chest.HumanoidBone = null;
+            }
+            var legL = model.Root.FindBone(HumanoidBones.leftUpperLeg);
+            var legR = model.Root.FindBone(HumanoidBones.rightUpperLeg);
+
+            // [chest]
+            // 上半身を下半身の子にしてchestとなす
+            lower.Add(upper);
+            upper.HumanoidBone = HumanoidBones.chest;
+
+            // [hips]
+            // lowerの親をhipsとして両足の間に配置する
+            var newHips = lower.Parent;
+            newHips.Translation = new Vector3(0, legL.Translation.Y, legL.Translation.Z);
+            newHips.HumanoidBone = HumanoidBones.hips;
+
+            // [spine]
+            // 下半身をspineとして
+            lower.HumanoidBone = HumanoidBones.spine;
+            // hips と chest の中間に配置する
+            lower.Translation = (upper.Translation + hips.Translation) * 0.5f;
+
+            // [legs]
+            // 足の親を下半身からrootに変える
+            hips.Add(legL);
+            hips.Add(legR);
+
+            return $"FixInvertedPelvis: lower: {lower.Name}";
+        }
+
+        static void StringBuilder(System.Text.StringBuilder sb, Node n, string indent = "")
+        {
+            sb.Append($"{indent}{n}\n");
+
+            foreach (var child in n.Children)
+            {
+                StringBuilder(sb, child, indent + "  ");
+            }
+        }
+
+        public static string HumanoidBoneEstimate(this Model model)
+        {
+            var sb = new System.Text.StringBuilder();
+            sb.Append("HumanoidBoneEstimate: ");
+
+            // estimate skeleton
+            var skeleton = SkeletonEstimator.Detect(model.Root);
+            if (skeleton == null)
+            {
+                return "fail to estimate skeleton";
+            }
+
+            // rename bone
+            foreach (var kv in skeleton)
+            {
+                kv.Value.Name = kv.Key.ToString();
+            }
+
+            if (model.Vrm == null)
+            {
+                sb.Append("add vrm humanoid");
+                model.Vrm = new Vrm(new Meta
+                {
+                }, "UniVRM-0.51.0", "0.0");
+            }
+            else
+            {
+
+            }
+
+            StringBuilder(sb, skeleton[HumanoidBones.hips]);
+
+            foreach (var skin in model.Skins)
+            {
+                if (skin.Root == null)
+                {
+                    skin.Root = (Node)skeleton[HumanoidBones.hips].Parent;
+                    sb.Append($"{skin}: set {skin.Root}\n");
+                }
+                else
+                {
+                    sb.Append($"{skin}: {skin.Root}\n");
+                }
+            }
+
+            return sb.ToString(); ;
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Runtime/ModelExtensionsForHumanoid.cs.meta b/Assets/VRM10/vrmlib/Runtime/ModelExtensionsForHumanoid.cs.meta
new file mode 100644
index 000000000..a935ab25a
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ModelExtensionsForHumanoid.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e290d6e2d1cfe684488ad5504f0ce8dc
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/ModelExtensionsForSingleMesh.cs b/Assets/VRM10/vrmlib/Runtime/ModelExtensionsForSingleMesh.cs
new file mode 100644
index 000000000..ea4d782f9
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ModelExtensionsForSingleMesh.cs
@@ -0,0 +1,132 @@
+using System.Linq;
+
+namespace VrmLib
+{
+    public static class ModelExtensionsForSingleMesh
+    {
+        ///
+        /// 各ノードのスキニングで使用されている回数
+        ///
+        public static int[] GetNodeSkinUseCount(Model model)
+        {
+            // create new skin
+            var useCountList = new int[model.Nodes.Count];
+            foreach (var n in model.Root.Traverse().Skip(1))
+            {
+                var g = n.MeshGroup;
+                if (g == null)
+                {
+                    continue;
+                }
+
+                if (g.Skin == null)
+                {
+                    // Skin無し。そのMeshに乗る
+                    var index = model.Nodes.IndexOf(n);
+                    ++useCountList[index];
+                }
+                else
+                {
+                    // Skinあり。VertexBufferの JOINT_0 と WEIGHT_0 を見る
+                    var skinJoints = g.Skin.Joints;
+                    foreach (var m in g.Meshes)
+                    {
+                        var joints = m.VertexBuffer.GetOrCreateJoints();
+                        var weights = m.VertexBuffer.GetOrCreateWeights();
+                        for (int i = 0; i < joints.Length; ++i)
+                        {
+                            var j = joints[i];
+                            var w = weights[i];
+                            if (w.X > 0)
+                            {
+                                var node = skinJoints[j.Joint0];
+                                var index = model.Nodes.IndexOf(node);
+                                ++useCountList[index];
+                            }
+                            if (w.Y > 0)
+                            {
+                                var node = skinJoints[j.Joint1];
+                                var index = model.Nodes.IndexOf(node);
+                                ++useCountList[index];
+                            }
+                            if (w.Z > 0)
+                            {
+                                var node = skinJoints[j.Joint2];
+                                var index = model.Nodes.IndexOf(node);
+                                ++useCountList[index];
+                            }
+                            if (w.W > 0)
+                            {
+                                var node = skinJoints[j.Joint3];
+                                var index = model.Nodes.IndexOf(node);
+                                ++useCountList[index];
+                            }
+                        }
+                    }
+                }
+            }
+            return useCountList;
+        }
+
+        /// 
+        /// Integrate meshes to a single mesh
+        /// 
+        /// 
+        /// 
+        public static MeshGroup CreateSingleMesh(this Model model, string name)
+        {
+            // new mesh to store result
+            var meshGroup = new MeshGroup(name);
+            var mesh = new Mesh
+            {
+                VertexBuffer = new VertexBuffer()
+            };
+            meshGroup.Meshes.Add(mesh);
+
+            var useCountList = GetNodeSkinUseCount(model);
+
+            // new Skin.
+            // Joints has include all joint
+            meshGroup.Skin = new Skin();
+            for (int i = 0; i < useCountList.Length; ++i)
+            {
+                if (useCountList[i] > 0)
+                {
+                    // add joint that has bone weight
+                    meshGroup.Skin.Joints.Add(model.Nodes[i]);
+                }
+            }
+            model.Skins.Clear();
+            model.Skins.Add(meshGroup.Skin);
+
+            // concatenate all mesh
+            foreach (var node in model.Root.Traverse().Skip(1))
+            {
+                var g = node.MeshGroup;
+                if (g != null)
+                {
+                    foreach (var m in g.Meshes)
+                    {
+                        if (g.Skin != null && m.VertexBuffer.Joints != null && m.VertexBuffer.Weights != null)
+                        {
+                            var jointIndexMap = g.Skin.Joints.Select(x => meshGroup.Skin.Joints.IndexOf(x)).ToArray();
+                            mesh.Append(m.VertexBuffer, m.IndexBuffer, m.Submeshes, m.MorphTargets, jointIndexMap);
+                        }
+                        else
+                        {
+                            var rootIndex = meshGroup.Skin.Joints.IndexOf(node);
+                            mesh.Append(m.VertexBuffer, m.IndexBuffer, m.Submeshes, m.MorphTargets, null, rootIndex, node.Matrix);
+                        }
+                    }
+                }
+            }
+
+            foreach (var target in mesh.MorphTargets)
+            {
+                target.VertexBuffer.Resize(mesh.VertexBuffer.Count);
+            }
+
+            return meshGroup;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Runtime/ModelExtensionsForSingleMesh.cs.meta b/Assets/VRM10/vrmlib/Runtime/ModelExtensionsForSingleMesh.cs.meta
new file mode 100644
index 000000000..b9223cc94
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ModelExtensionsForSingleMesh.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ff27373084460f24b8866d4853a9acda
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/ModelExtensionsForValidation.cs b/Assets/VRM10/vrmlib/Runtime/ModelExtensionsForValidation.cs
new file mode 100644
index 000000000..4664e0e43
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ModelExtensionsForValidation.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Linq;
+
+namespace VrmLib
+{
+    public static class ModelExtensionsForValidation
+    {
+        public static void Validate(this Model model, Node node, string message)
+        {
+            if (node is null)
+            {
+                throw new ArgumentNullException(message);
+            }
+            if (!model.Nodes.Contains(node))
+            {
+                throw new ArgumentException($"{message}: node found in nodes");
+            }
+        }
+
+        public static void Validate(this Model model)
+        {
+            foreach (var node in model.Root.Traverse().Skip(1))
+            {
+                model.Validate(node, "nodes must Contains node");
+            }
+
+            foreach (var skin in model.Skins)
+            {
+                foreach (var joint in skin.Joints)
+                {
+                    model.Validate(joint, "nodes must Contatins joint");
+                }
+            }
+
+            if (model.Vrm != null)
+            {
+                if (model.Vrm.ExpressionManager != null)
+                {
+                    foreach (var b in model.Vrm.ExpressionManager.ExpressionList)
+                    {
+                        foreach (var v in b.MorphTargetBinds)
+                        {
+                            model.Validate(v.Node, "MorphTargetBindValue.Node is null");
+                        }
+                    }
+                }
+
+                if (model.Vrm.FirstPerson != null)
+                {
+                    foreach (var a in model.Vrm.FirstPerson.Annotations)
+                    {
+                        model.Validate(a.Node, "FirstPersonMeshAnnotation.Node is null");
+                    }
+                }
+
+                var humanDict = model.Root.Traverse()
+                    .Where(x => x.HumanoidBone.HasValue)
+                    .ToDictionary(x => x.HumanoidBone.Value, x => x);
+
+                foreach (var required in new[]{
+                    HumanoidBones.hips,
+                })
+                {
+                    if (!humanDict.ContainsKey(required))
+                    {
+                        throw new Exception($"no {required}");
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/ModelExtensionsForValidation.cs.meta b/Assets/VRM10/vrmlib/Runtime/ModelExtensionsForValidation.cs.meta
new file mode 100644
index 000000000..b2ad6bb8e
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ModelExtensionsForValidation.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e7f73e8a96ec0774dadaa1f526c80210
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/ModelModifier.cs b/Assets/VRM10/vrmlib/Runtime/ModelModifier.cs
new file mode 100644
index 000000000..b349d755b
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ModelModifier.cs
@@ -0,0 +1,214 @@
+using System;
+using System.Linq;
+
+namespace VrmLib
+{
+    /// 
+    /// Modelを安全に変更できるようにラップする
+    /// 
+    public class ModelModifier
+    {
+        /// 
+        /// 直接データを変更する場合は整合性に注意
+        /// 
+        public Model Model;
+
+        public ModelModifier(Model model)
+        {
+            Model = model;
+        }
+
+        /// 
+        /// Meshを置き換える。
+        ///
+        /// src=null, dst!=null で追加。
+        /// src!=null, dst=null で削除。
+        /// 
+        /// 置き換え元
+        /// 置き換え先
+        public void MeshReplace(MeshGroup src, MeshGroup dst)
+        {
+            // replace: Meshes
+            if (src != null)
+            {
+                Model.MeshGroups.RemoveAll(x => x == src);
+            }
+            if (dst != null && !Model.MeshGroups.Contains(dst))
+            {
+                Model.MeshGroups.Add(dst);
+            }
+
+            // replace: Node.Mesh
+            foreach (var node in Model.Nodes)
+            {
+                if (src != null && src == node.MeshGroup)
+                {
+                    node.MeshGroup = dst;
+                }
+            }
+        }
+
+        #region Node
+        public void NodeAdd(Node node, Node parent = null)
+        {
+            if (parent is null)
+            {
+                parent = Model.Root;
+            }
+            parent.Add(node);
+            if (Model.Nodes.Contains(node))
+            {
+                throw new ArgumentException($"Nodes contain {node}");
+            }
+            Model.Nodes.Add(node);
+        }
+
+        public void NodeRemove(Node remove)
+        {
+            foreach (var node in Model.Nodes)
+            {
+                if (node.Parent == remove)
+                {
+                    remove.Remove(node);
+                }
+                if (remove.Parent == node)
+                {
+                    node.Remove(remove);
+                }
+            }
+            if (Model.Root.Children.Contains(remove))
+            {
+                Model.Root.Remove(remove);
+            }
+
+            Model.Nodes.Remove(remove);
+
+            if (Model.Vrm != null)
+            {
+                if (Model.Vrm.ExpressionManager != null)
+                {
+                    foreach (var b in Model.Vrm.ExpressionManager.ExpressionList)
+                    {
+                        foreach (var v in b.MorphTargetBinds)
+                        {
+                            if (v.Node == remove)
+                            {
+                                throw new NotImplementedException("referenced from morphtargetbind");
+                            }
+                        }
+                    }
+                }
+
+                if (Model.Vrm.FirstPerson != null)
+                {
+                    foreach (var a in Model.Vrm.FirstPerson.Annotations)
+                    {
+                        if (a.Node == remove)
+                        {
+                            throw new NotImplementedException("referenced from firstPerson");
+                        }
+                    }
+                }
+            }
+        }
+
+        /// 
+        /// Nodeを置き換える。参照を置換する。
+        /// 
+        public void NodeReplace(Node src, Node dst)
+        {
+            if (src == null)
+            {
+                throw new ArgumentNullException();
+            }
+            if (dst == null)
+            {
+                throw new ArgumentNullException();
+            }
+
+            // add dst same parent
+            src.Parent.Add(dst, ChildMatrixMode.KeepWorld);
+
+            // remove all child
+            foreach (var child in src.Children.ToArray())
+            {
+                dst.Add(child, ChildMatrixMode.KeepWorld);
+            }
+
+            // remove from parent
+            src.Parent.Remove(src);
+            Model.Nodes.Remove(src);
+
+            // remove from skinning
+            foreach (var skin in Model.Skins)
+            {
+                skin.Replace(src, dst);
+            }
+
+            // fix animation reference
+            foreach (var animation in Model.Animations)
+            {
+                if (animation.NodeMap.TryGetValue(src, out NodeAnimation nodeAnimation))
+                {
+                    animation.NodeMap.Remove(src);
+                    animation.NodeMap.Add(dst, nodeAnimation);
+                }
+            }
+
+            if (Model.Nodes.Contains(dst))
+            {
+                throw new Exception("already exists");
+            }
+            Model.Nodes.Add(dst);
+
+            // fix VRM
+            if (Model.Vrm != null)
+            {
+                // replace: VrmMorphTargetBind.Mesh
+                if (Model.Vrm.ExpressionManager != null)
+                {
+                    foreach (var x in Model.Vrm.ExpressionManager.ExpressionList)
+                    {
+                        for (int i = 0; i < x.MorphTargetBinds.Count; ++i)
+                        {
+                            var v = x.MorphTargetBinds[i];
+                            if (src == v.Node)
+                            {
+                                v.Node = dst;
+                            }
+                        }
+                    }
+                }
+
+                // replace: VrmFirstPerson.MeshAnnotations
+                Model.Vrm.FirstPerson.Annotations.RemoveAll(x => x.Node == src);
+                if (!Model.Vrm.FirstPerson.Annotations.Any(x => x.Node == dst))
+                {
+                    Model.Vrm.FirstPerson.Annotations.Add(
+                        new FirstPersonMeshAnnotation(dst, FirstPersonMeshType.Auto));
+                }
+            }
+
+            // TODO: SpringBone
+        }
+        #endregion
+
+        public void MaterialReplace(Material src, Material dst)
+        {
+            // replace material of submesh
+            foreach (var group in Model.MeshGroups)
+            {
+                foreach (var mesh in group.Meshes)
+                {
+                    foreach (var submesh in mesh.Submeshes)
+                    {
+                        if (submesh.Material == src)
+                        {
+                            submesh.Material = dst;
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Runtime/ModelModifier.cs.meta b/Assets/VRM10/vrmlib/Runtime/ModelModifier.cs.meta
new file mode 100644
index 000000000..5826823ee
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ModelModifier.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8a5b1c7c663b54b49913c6ade2a342c2
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/ModelModifierExtensions.cs b/Assets/VRM10/vrmlib/Runtime/ModelModifierExtensions.cs
new file mode 100644
index 000000000..a65745823
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ModelModifierExtensions.cs
@@ -0,0 +1,365 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+
+namespace VrmLib
+{
+    public static class ModelModifierExtensions
+    {
+        public static bool TryAdd(this IDictionary dict, TKey key, TValue addValue)
+        {
+            bool canAdd = !dict.ContainsKey(key);
+
+            if (canAdd)
+                dict.Add(key, addValue);
+
+            return canAdd;
+        }
+
+        public static TValue GetValueOrDefault(this System.Collections.Generic.IReadOnlyDictionary dictionary, TKey key)
+        {
+            if (dictionary.TryGetValue(key, out TValue value))
+            {
+                return value;
+            }
+            else
+            {
+                return default;
+            }
+        }
+
+        static void ReplaceMorphTargetAnimationNode(IEnumerable animations, Node dst)
+        {
+            foreach (var animation in animations)
+            {
+                var dstAnimation = animation.GetOrCreateNodeAnimation(dst);
+                foreach (var (node, nodeAnimation) in animation.NodeMap)
+                {
+                    if (nodeAnimation.Curves.TryGetValue(AnimationPathType.Weights, out CurveSampler curve))
+                    {
+                        // remove
+                        nodeAnimation.Curves.Remove(AnimationPathType.Weights);
+
+                        // add
+                        if (!dstAnimation.Curves.TryAdd(AnimationPathType.Weights, curve))
+                        {
+                            Console.Error.WriteLine($"already exists. skip: {node.Name}: {nodeAnimation}");
+                        }
+                    }
+                }
+            }
+        }
+
+        /// Expression
+        /// FirstPersonの置き換え
+        public static void MeshNodeReplace(this ModelModifier modifier, Node src, Node dst)
+        {
+            var vrm = modifier.Model.Vrm;
+            if (vrm is null)
+            {
+                return;
+            }
+
+            if (vrm.ExpressionManager != null)
+            {
+                foreach (var b in vrm.ExpressionManager.ExpressionList)
+                {
+                    foreach (var v in b.MorphTargetBinds)
+                    {
+                        if (v.Node == src)
+                        {
+                            v.Node = dst;
+                        }
+                    }
+                }
+            }
+            if (vrm.FirstPerson != null)
+            {
+                foreach (var a in vrm.FirstPerson.Annotations)
+                {
+                    if (a.Node == src)
+                    {
+                        a.Node = dst;
+                    }
+                }
+            }
+        }
+
+        public static string SingleMesh(this ModelModifier modifier, string name)
+        {
+            var count = modifier.Model.MeshGroups.Sum(x => x.Meshes.Count);
+            var meshes = modifier.Model.Root.Traverse()
+                .Select(x => x.MeshGroup)
+                .Where(x => x != null)
+                .Select(x => $"[{x.Name}]")
+                .ToArray();
+            if (meshes.Length == 0)
+            {
+                return "SingleMesh: no mesh. do nothing";
+            }
+            if (meshes.Length <= 1)
+            {
+                return "SingleMesh: one mesh. do nothing";
+            }
+
+            var mesh = modifier.Model.CreateSingleMesh(name);
+            var meshNode = new Node(mesh.Name)
+            {
+                MeshGroup = mesh,
+            };
+            mesh.Skin.Root = meshNode;
+
+            // fix bone weight (0, x, 0, 0) => (x, 0, 0, 0)
+            // mesh.Meshes[0].VertexBuffer.FixBoneWeight();
+
+            // replace morphAnimation reference
+            ReplaceMorphTargetAnimationNode(modifier.Model.Animations, meshNode);
+
+            // update Model
+            foreach (var x in modifier.Model.MeshGroups.ToArray())
+            {
+                modifier.MeshReplace(x, mesh);
+            }
+            foreach (var node in modifier.Model.Nodes)
+            {
+                if (node.MeshGroup != null)
+                {
+                    node.MeshGroup = null;
+                    modifier.MeshNodeReplace(node, meshNode);
+                }
+
+            }
+            modifier.NodeAdd(meshNode);
+
+            var names = string.Join("", meshes);
+            // return $"SingleMesh: {names}";
+            return $"SingleMesh: {count} => {modifier.Model.MeshGroups.Sum(x => x.Meshes.Count)}";
+        }
+
+        public static void SepareteByMorphTarget(this ModelModifier modifier, MeshGroup mesh)
+        {
+            var (with, without) = mesh.SepareteByMorphTarget();
+            var list = new List();
+            if (with != null) list.Add(with);
+            if (without != null) list.Add(without);
+
+            // 分割モデルで置き換え
+            if (list.Any())
+            {
+                modifier.MeshReplace(mesh, list[0]);
+                // rename node
+                modifier.Model.Nodes.Find(x => x.MeshGroup == list[0]).Name = list[0].Name;
+            }
+
+            if (list.Count > 1)
+            {
+                // morph無しと有り両方存在する場合に2つ目を追加する
+                modifier.MeshReplace(null, list[1]);
+                modifier.NodeAdd(new Node(list[1].Name)
+                {
+                    MeshGroup = list[1]
+                });
+            }
+        }
+
+        public static void SepareteByHeadBone(this ModelModifier modifier, MeshGroup mesh, HashSet boneIndices)
+        {
+            var (with, without) = mesh.SepareteByHeadBone(boneIndices);
+            var list = new List();
+            if (with != null) list.Add(with);
+            if (without != null) list.Add(without);
+
+            // 分割モデルで置き換え
+            if (list.Any())
+            {
+                modifier.MeshReplace(mesh, list[0]);
+                // rename node
+                modifier.Model.Nodes.Find(x => x.MeshGroup == list[0]).Name = list[0].Name;
+            }
+
+            if (list.Count > 1)
+            {
+                // 頭と胴体で分割後2つ以上ある場合、2つ目を追加する
+                modifier.MeshReplace(null, list[1]);
+                modifier.NodeAdd(new Node(list[1].Name)
+                {
+                    MeshGroup = list[1]
+                });
+            }
+        }
+
+        public static string NodeReduce(this ModelModifier modifier)
+        {
+            var count = modifier.Model.Nodes.Count;
+            var removeNames = new List();
+
+            // ノードを削除する
+            foreach (var node in modifier.Model.GetRemoveNodes())
+            {
+                modifier.NodeRemove(node);
+                removeNames.Add($"[{node.Name}]");
+                foreach (var skin in modifier.Model.Skins)
+                {
+                    var index = skin.Joints.IndexOf(node);
+                    if (index != -1)
+                    {
+                        // remove
+                        skin.Joints[index] = null;
+                    }
+                }
+            }
+
+            // 削除されたノードを参照する頂点バッファを修正する
+            foreach (var meshGroup in modifier.Model.MeshGroups)
+            {
+                var skin = meshGroup.Skin;
+                if (skin != null && skin.Joints.Contains(null))
+                {
+                    foreach (var mesh in meshGroup.Meshes)
+                    {
+                        skin.FixBoneWeight(mesh.VertexBuffer.Joints, mesh.VertexBuffer.Weights);
+                    }
+                }
+            }
+
+            var joined = string.Join("", removeNames);
+
+            return $"NodeReduce: {count} => {modifier.Model.Nodes.Count}";
+            // return $"NodeReduce: {joined}";
+        }
+
+        public static string SkinningBake(this ModelModifier modifier)
+        {
+            foreach (var node in modifier.Model.Nodes)
+            {
+                var meshGroup = node.MeshGroup;
+                if (meshGroup == null)
+                {
+                    continue;
+                }
+
+                if (meshGroup.Skin != null)
+                {
+                    // 正規化されていれば1つしかない
+                    // されていないと Primitive の数だけある
+                    foreach (var mesh in meshGroup.Meshes)
+                    {
+                        {
+                            // Skinningの出力先を自身にすることでBakeする
+                            meshGroup.Skin.Skinning(mesh.VertexBuffer);
+                        }
+
+                        // morphのPositionは相対値が入っているはずなので、手を加えない(正規化されていない場合、二重に補正が掛かる)
+                        /*
+                                                foreach (var morph in mesh.MorphTargets)
+                                                {
+                                                    if (morph.VertexBuffer.Positions != null)
+                                                    {
+                                                        meshGroup.Skin.Skinning(morph.VertexBuffer);
+                                                    }
+                                                }
+                                                */
+                    }
+
+                    meshGroup.Skin.Root = null;
+                    meshGroup.Skin.InverseMatrices = null;
+                }
+                else
+                {
+                    foreach (var mesh in meshGroup.Meshes)
+                    {
+                        // nodeに対して疑似的にSkinningする
+                        // 回転と拡縮を適用し位置は適用しない
+                        mesh.ApplyRotationAndScaling(node.Matrix);
+                    }
+                }
+            }
+
+            // 回転・拡縮を除去する
+            modifier.Model.ApplyRotationAndScale();
+
+            // inverse matrix の再計算
+            foreach (var node in modifier.Model.Nodes)
+            {
+                var meshGroup = node.MeshGroup;
+                if (meshGroup == null)
+                {
+                    continue;
+                }
+
+                foreach (var mesh in meshGroup.Meshes)
+                {
+                    if (meshGroup.Skin != null)
+                    {
+                        meshGroup.Skin.CalcInverseMatrices();
+                    }
+                }
+            }
+
+            return "SkinningBake";
+        }
+
+        public static string CloneSharedMesh(this ModelModifier modifier)
+        {
+            Dictionary m_useMap = new Dictionary();
+
+            var cloned = new List();
+
+            foreach (var node in modifier.Model.Nodes)
+            {
+                if (node.MeshGroup == null)
+                {
+                    continue;
+                }
+
+                var n = m_useMap.GetValueOrDefault(node.MeshGroup);
+                if (n > 0)
+                {
+                    // copy
+                    node.MeshGroup = node.MeshGroup.Clone();
+                    cloned.Add($"[{node.MeshGroup.Name}]");
+                }
+                m_useMap[node.MeshGroup] = n + 1;
+            }
+
+            if (!cloned.Any())
+            {
+                return "CloneSharedMesh: no shared mesh. do nothing";
+            }
+            else
+            {
+                var joined = string.Join("", cloned);
+                return $"CloneSharedMesh: copy {joined}";
+            }
+        }
+
+        public static string MaterialIntegrate(this ModelModifier modifier)
+        {
+            var sb = new System.Text.StringBuilder();
+            var materials = new List();
+
+            foreach (var material in modifier.Model.Materials.ToArray())
+            {
+                var found = materials.FirstOrDefault(x => x.CanIntegrate(material));
+                if (found != null)
+                {
+                    // merge
+                    modifier.MaterialReplace(material, found);
+                }
+                else
+                {
+                    // add
+                    materials.Add(material);
+                }
+            }
+
+            sb.Append($"MaterialIntegrate: {modifier.Model.Materials.Count} => {materials.Count}");
+
+            modifier.Model.Materials.Clear();
+            modifier.Model.Materials.AddRange(materials);
+
+            return sb.ToString();
+        }
+    }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Runtime/ModelModifierExtensions.cs.meta b/Assets/VRM10/vrmlib/Runtime/ModelModifierExtensions.cs.meta
new file mode 100644
index 000000000..b5a46a790
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/ModelModifierExtensions.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 54fd566dc84b51643886ce8858a671db
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/MorphTarget.cs b/Assets/VRM10/vrmlib/Runtime/MorphTarget.cs
new file mode 100644
index 000000000..88cd44cd6
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/MorphTarget.cs
@@ -0,0 +1,24 @@
+namespace VrmLib
+{
+    public class MorphTarget
+    {
+        public readonly string Name;
+        public VertexBuffer VertexBuffer;
+
+        public override string ToString()
+        {
+            var sb = new System.Text.StringBuilder();
+            sb.Append(Name);
+            foreach (var kv in VertexBuffer)
+            {
+                sb.Append($"[{kv.Key}]");
+            }
+            return sb.ToString();
+        }
+
+        public MorphTarget(string name)
+        {
+            Name = name;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Runtime/MorphTarget.cs.meta b/Assets/VRM10/vrmlib/Runtime/MorphTarget.cs.meta
new file mode 100644
index 000000000..cc460a3a0
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/MorphTarget.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 1adbe1d2bd9bcd3458b01621faae1ff5
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Node.cs b/Assets/VRM10/vrmlib/Runtime/Node.cs
new file mode 100644
index 000000000..77c615bfc
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Node.cs
@@ -0,0 +1,384 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+
+
+namespace VrmLib
+{
+    public enum ChildMatrixMode
+    {
+        KeepLocal,
+        KeepWorld,
+    }
+
+    public class Node : GltfId, IEnumerable
+    {
+        static int s_nextUniqueId = 1;
+
+        public readonly int UniqueID;
+
+        public string Name
+        {
+            get;
+            set;
+        }
+
+        public Node(string name)
+        {
+            UniqueID = s_nextUniqueId++;
+            Name = name;
+        }
+
+        #region Transform
+        //
+        // Localで値を保持する
+        //
+        public Vector3 LocalTranslationWithoutUpdate = Vector3.Zero;
+        public Vector3 LocalTranslation
+        {
+            get => LocalTranslationWithoutUpdate;
+            set
+            {
+                if (LocalTranslationWithoutUpdate == value) return;
+                LocalTranslationWithoutUpdate = value;
+                CalcWorldMatrix(Parent != null ? Parent.Matrix : Matrix4x4.Identity);
+            }
+        }
+
+        public Quaternion LocalRotationWithoutUpdate = Quaternion.Identity;
+        public Quaternion LocalRotation
+        {
+            get => LocalRotationWithoutUpdate;
+            set
+            {
+                if (LocalRotationWithoutUpdate == value) return;
+                LocalRotationWithoutUpdate = value;
+                CalcWorldMatrix(Parent != null ? Parent.Matrix : Matrix4x4.Identity);
+            }
+        }
+
+        public Vector3 LocalScalingWithoutUpdate = Vector3.One;
+        public Vector3 LocalScaling
+        {
+            get => LocalScalingWithoutUpdate;
+            set
+            {
+                if (LocalScalingWithoutUpdate == value) return;
+                LocalScalingWithoutUpdate = value;
+                CalcWorldMatrix(Parent != null ? Parent.Matrix : Matrix4x4.Identity);
+            }
+        }
+
+        public Matrix4x4 LocalMatrix
+        {
+            get => Matrix4x4.CreateScale(LocalScaling)
+            * Matrix4x4.CreateFromQuaternion(LocalRotation)
+            * Matrix4x4.CreateTranslation(LocalTranslation);
+        }
+
+        public void SetLocalMatrix(Matrix4x4 value, bool calcWorldMatrix)
+        {
+            if (Matrix4x4.Decompose(value, out LocalScalingWithoutUpdate, out LocalRotationWithoutUpdate, out LocalTranslationWithoutUpdate))
+            {
+                CalcWorldMatrix(Parent != null ? Parent.Matrix : Matrix4x4.Identity, calcWorldMatrix);
+            }
+            else
+            {
+                throw new Exception($"fail to decompose matrix: {Name}");
+            }
+        }
+
+        Matrix4x4 m_matrix = Matrix4x4.Identity;
+        public Matrix4x4 Matrix
+        {
+            get => m_matrix;
+        }
+        // public void SetMatrixWithoutUpdate(Matrix4x4 m)
+        // {
+        //     m_matrix = m;
+        // }
+
+        public Quaternion Rotation
+        {
+            get
+            {
+                return Quaternion.CreateFromRotationMatrix(Matrix);
+            }
+            set
+            {
+                if (Parent == null)
+                {
+                    LocalRotation = value;
+                }
+                else
+                {
+                    LocalRotation = Quaternion.Inverse(Parent.Rotation) * value;
+                }
+            }
+        }
+
+        public void SetMatrix(Matrix4x4 m, bool calcWorldMatrix)
+        {
+            if (Parent != null)
+            {
+                SetLocalMatrix(m * Parent.InverseMatrix, calcWorldMatrix);
+            }
+            else
+            {
+                SetLocalMatrix(m, calcWorldMatrix);
+            }
+        }
+
+        public Matrix4x4 InverseMatrix
+        {
+            get
+            {
+                Matrix4x4 inverted = Matrix4x4.Identity;
+                if (!Matrix4x4.Invert(Matrix, out inverted))
+                {
+                    throw new Exception();
+                }
+                return inverted;
+            }
+        }
+
+        public void CalcWorldMatrix(bool calcChildren = true)
+        {
+            if (Parent == null)
+            {
+                CalcWorldMatrix(Matrix4x4.Identity, calcChildren);
+            }
+            else
+            {
+                CalcWorldMatrix(Parent.Matrix, calcChildren);
+            }
+        }
+
+        public void CalcWorldMatrix(Matrix4x4 parent, bool calcChildren = true)
+        {
+            var value = LocalMatrix * parent;
+            // if (value == m_matrix) return;
+            m_matrix = value;
+
+            RaiseMatrixUpdated();
+
+            // if (float.IsNaN(m_matrix.M11))
+            // {
+            //     var a = 0;
+            // }
+            if (calcChildren)
+            {
+                foreach (var child in Children)
+                {
+                    child.CalcWorldMatrix(m_matrix, calcChildren);
+                }
+            }
+        }
+
+        public event Action MatrixUpdated;
+        void RaiseMatrixUpdated()
+        {
+            var handle = MatrixUpdated;
+            if (handle != null)
+            {
+                handle(Matrix);
+            }
+        }
+
+        public Vector3 Translation
+        {
+            get => Matrix.Translation;
+            set
+            {
+                if (Parent == null)
+                {
+                    LocalTranslation = value;
+                }
+                else
+                {
+                    var localPosition = Vector4.Transform(value, Parent.InverseMatrix);
+                    LocalTranslation = new Vector3(localPosition.X, localPosition.Y, localPosition.Z);
+                }
+            }
+        }
+
+        public Vector3 SkeletonLocalPosition
+        {
+            get => Translation;
+            set
+            {
+                Translation = value;
+            }
+        }
+        #endregion
+
+        #region Hierarchy
+
+        public Node Parent { get; private set; }
+
+        public IEnumerable Ancestors()
+        {
+            if (Parent == null)
+            {
+                yield break;
+            }
+            yield return Parent;
+            foreach (var x in Parent.Ancestors())
+            {
+                yield return x;
+            }
+        }
+
+        readonly List m_children = new List();
+
+        public void Add(Node child, ChildMatrixMode mode = ChildMatrixMode.KeepLocal)
+        {
+            if (child.Parent != null)
+            {
+                child.Parent.m_children.Remove(child);
+            }
+            m_children.Add(child);
+            child.Parent = this;
+
+            switch (mode)
+            {
+                case ChildMatrixMode.KeepLocal:
+                    child.CalcWorldMatrix(Matrix);
+                    break;
+
+                case ChildMatrixMode.KeepWorld:
+                    child.SetMatrix(child.Matrix, true);
+                    break;
+            }
+        }
+
+        public void Remove(Node child)
+        {
+            child.Parent = null;
+            m_children.Remove(child);
+        }
+
+        public IEnumerable Traverse()
+        {
+            yield return this;
+
+            foreach (var child in Children)
+            {
+                foreach (var x in child.Traverse())
+                {
+                    yield return x;
+                }
+            }
+        }
+
+        public IEnumerator GetEnumerator()
+        {
+            return ((IEnumerable)Children).GetEnumerator();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return ((IEnumerable)Children).GetEnumerator();
+        }
+        #endregion
+
+
+        public MeshGroup MeshGroup;
+
+        // VRMでは、Meshes.Count==1
+        public Mesh Mesh => MeshGroup?.Meshes?[0];
+
+        HumanoidBones? m_bone;
+        public HumanoidBones? HumanoidBone
+        {
+            get => m_bone;
+            set
+            {
+                if (m_bone == value)
+                {
+                    return;
+                }
+                if (value == HumanoidBones.unknown)
+                {
+                    return;
+                }
+                m_bone = value;
+            }
+        }
+
+        public IReadOnlyList Children => m_children;
+
+        public Node FindBone(HumanoidBones bone)
+        {
+            return Traverse().FirstOrDefault(x => x.HumanoidBone == bone);
+        }
+
+        public void RotateFromTo(Vector3 worldSrc, Vector3 worldDst)
+        {
+            // world to local
+            var src = Vector3.Transform(Vector3.Normalize(worldSrc), Quaternion.Inverse(Rotation));
+            var dst = Vector3.Transform(Vector3.Normalize(worldDst), Quaternion.Inverse(Rotation));
+
+            var dot = Vector3.Dot(src, dst);
+            Quaternion rot;
+            if (Math.Abs(1.0f - dot) < float.Epsilon)
+            {
+                // 0degree
+                rot = Quaternion.Identity;
+            }
+            else if (Math.Abs(-1.0f - dot) < float.Epsilon)
+            {
+                // 180degree
+                rot = Quaternion.CreateFromYawPitchRoll(MathFWrap.PI, 0, 0);
+            }
+            else
+            {
+                var axis = Vector3.Normalize(Vector3.Cross(src, dst));
+                rot = Quaternion.CreateFromAxisAngle(axis, (float)Math.Acos(dot));
+            }
+
+            LocalRotation = rot;
+        }
+
+        public override string ToString()
+        {
+            if (HumanoidBone.HasValue)
+            {
+                return $"{Name}[{HumanoidBone.Value}]: {LocalTranslation.X:0.00}, {LocalTranslation.Y:0.00}, {LocalTranslation.Z:0.00}";
+            }
+            else
+            {
+                return $"{Name}: {LocalTranslation.X:0.00}, {LocalTranslation.Y:0.00}, {LocalTranslation.Z:0.00}";
+            }
+        }
+    }
+
+    public static class NodeExtensions
+    {
+        public static IEnumerable Traverse(this Node self)
+        {
+            yield return self;
+            foreach (var child in self.Children)
+            {
+                foreach (var x in child.Traverse())
+                {
+                    yield return x;
+                }
+            }
+        }
+
+        public static Vector3 CenterOfDescendant(this Node self)
+        {
+            var sum = Vector3.Zero;
+            int i = 0;
+            foreach (var x in self.Traverse())
+            {
+                sum += x.SkeletonLocalPosition;
+                ++i;
+            }
+            return sum / i;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Runtime/Node.cs.meta b/Assets/VRM10/vrmlib/Runtime/Node.cs.meta
new file mode 100644
index 000000000..31dd693fb
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Node.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: c23140d8dd58d4e45a7c4798dd577035
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/NodeAnimation.cs b/Assets/VRM10/vrmlib/Runtime/NodeAnimation.cs
new file mode 100644
index 000000000..f130f1058
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/NodeAnimation.cs
@@ -0,0 +1,134 @@
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using System.Runtime.InteropServices;
+
+namespace VrmLib
+{
+    public enum AnimationPathType
+    {
+        Translation,
+        Rotation,
+        Scale,
+        Weights,
+    }
+
+    public class CurveSampler
+    {
+        public BufferAccessor In;
+        public BufferAccessor Out;
+
+        public float LastTime
+        {
+            get
+            {
+                if (In.ComponentType == AccessorValueType.FLOAT)
+                {
+                    var times = SpanLike.Wrap(In.Bytes);
+                    return times[times.Length - 1];
+                }
+                else
+                {
+                    throw new NotImplementedException();
+                }
+            }
+        }
+
+        public CurveSampler()
+        {
+        }
+
+        ValueTuple GetRange(float seconds)
+        {
+            var keys = In.GetSpan();
+
+            if (seconds <= keys[0])
+            {
+                return (0, 0, 0);
+            }
+            else if (seconds >= keys[keys.Length - 1])
+            {
+                return (keys.Length - 1, keys.Length - 1, 0);
+            }
+
+            // search range
+            float begin = keys[0];
+            float end;
+            for (int i = 1; i < keys.Length; ++i)
+            {
+                end = keys[i];
+
+                if (seconds == end)
+                {
+                    return (i, i, 0);
+                }
+                else if (seconds < end)
+                {
+                    var ratio = (seconds - begin) / (end - begin);
+                    return (i - 1, i, ratio);
+                }
+
+                begin = end;
+            }
+
+            throw new Exception("not found");
+        }
+
+        public Vector3 GetVector3(TimeSpan elapsed)
+        {
+            var (begin, end, ratio) = GetRange((float)elapsed.TotalSeconds);
+            var values = Out.GetSpan();
+            if (begin == end)
+            {
+                return values[begin];
+            }
+            else
+            {
+                // TODO: curve interpolation
+                return Vector3.Lerp(values[begin], values[end], ratio);
+            }
+        }
+
+        public Quaternion GetQuaternion(TimeSpan elapsed)
+        {
+            var (begin, end, ratio) = GetRange((float)elapsed.TotalSeconds);
+            var values = Out.GetSpan();
+            if (begin == end)
+            {
+                return values[begin];
+            }
+            else
+            {
+                // TODO: curve interpolation
+                return Quaternion.Lerp(values[begin], values[end], ratio);
+            }
+        }
+
+        public void SkipFrame(int skipFrames)
+        {
+            In = In.Skip(skipFrames);
+            Out = Out.Skip(skipFrames);
+        }
+    }
+
+    public class NodeAnimation
+    {
+        public TimeSpan Duration
+        {
+            get
+            {
+                var lastTime = float.NegativeInfinity;
+                foreach (var kv in Curves)
+                {
+                    if (kv.Value.LastTime > lastTime)
+                    {
+                        lastTime = kv.Value.LastTime;
+                    }
+                }
+                return TimeSpan.FromSeconds(lastTime);
+            }
+        }
+
+        public Dictionary Curves = new Dictionary();
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/NodeAnimation.cs.meta b/Assets/VRM10/vrmlib/Runtime/NodeAnimation.cs.meta
new file mode 100644
index 000000000..e8fde976a
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/NodeAnimation.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9dbb0b339c10c3a4faf4754a406830c1
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/NumericsExtensions.cs b/Assets/VRM10/vrmlib/Runtime/NumericsExtensions.cs
new file mode 100644
index 000000000..43def578a
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/NumericsExtensions.cs
@@ -0,0 +1,135 @@
+using System;
+using System.Numerics;
+
+namespace VrmLib
+{
+    public static class NumericsExtensions
+    {
+        const float EPSILON = 1e-5f;
+
+        public static bool NearlyEqual(this float lhs, float rhs)
+        {
+            return Math.Abs(lhs - rhs) <= EPSILON;
+        }
+
+        public static bool NearlyEqual(this Vector3 lhs, Vector3 rhs)
+        {
+            if (Math.Abs(lhs.X - rhs.X) > EPSILON) return false;
+            if (Math.Abs(lhs.Y - rhs.Y) > EPSILON) return false;
+            if (Math.Abs(lhs.Z - rhs.Z) > EPSILON) return false;
+            return true;
+        }
+
+        public static bool NearlyEqual(this Quaternion lhs, Quaternion rhs)
+        {
+            if (Math.Abs(lhs.X - rhs.X) > EPSILON) return false;
+            if (Math.Abs(lhs.Y - rhs.Y) > EPSILON) return false;
+            if (Math.Abs(lhs.Z - rhs.Z) > EPSILON) return false;
+            if (Math.Abs(lhs.W - rhs.W) > EPSILON) return false;
+            return true;
+        }
+
+        public const float TO_RAD = (float)(Math.PI / 180.0);
+
+        public static Vector2 UVVerticalFlip(this Vector2 src)
+        {
+            return new Vector2(src.X, 1.0f - src.Y);
+        }
+
+        public static Vector3 ReverseZ(this Vector3 src)
+        {
+            return new Vector3(src.X, src.Y, -src.Z);
+        }
+
+        public static (Vector3, float) GetAxisAngle(this Quaternion q)
+        {
+            var qw = q.W;
+            var angle = 2 * Math.Acos(qw);
+            var x = q.X / Math.Sqrt(1 - qw * qw);
+            var y = q.Y / Math.Sqrt(1 - qw * qw);
+            var z = q.Z / Math.Sqrt(1 - qw * qw);
+            return (new Vector3((float)x, (float)y, (float)z), (float)angle);
+        }
+
+        public static Quaternion ReverseZ(this Quaternion src)
+        {
+            var (axis, angle) = src.GetAxisAngle();
+            return Quaternion.CreateFromAxisAngle(axis.ReverseZ(), -angle);
+        }
+
+        public static Vector3 ExtractPosition(this Matrix4x4 matrix)
+        {
+            Vector3 position;
+            position.X = matrix.M41;
+            position.Y = matrix.M42;
+            position.Z = matrix.M43;
+            return position;
+        }
+
+        public static Quaternion ExtractRotation(this Matrix4x4 matrix)
+        {
+            return Quaternion.CreateFromRotationMatrix(matrix);
+        }
+
+        public static Vector3 ExtractScale(this Matrix4x4 matrix)
+        {
+            Vector3 scale;
+            scale.X = new Vector4(matrix.M11, matrix.M12, matrix.M13, matrix.M14).Length();
+            scale.Y = new Vector4(matrix.M21, matrix.M22, matrix.M23, matrix.M24).Length();
+            scale.Z = new Vector4(matrix.M31, matrix.M32, matrix.M33, matrix.M34).Length();
+            return scale;
+        }
+
+        public static Matrix4x4 FromTRS(Vector3 t, Quaternion r, Vector3 s)
+        {
+            var tt = Matrix4x4.CreateTranslation(t);
+            var rr = Matrix4x4.CreateFromQuaternion(r);
+            var ss = Matrix4x4.CreateScale(s);
+            // return tt * rr * ss;
+            return ss * rr * tt;
+        }
+
+        public static (Vector3, Quaternion, Vector3) Decompose(this Matrix4x4 m)
+        {
+            var s = m.ExtractScale();
+            var mm = Matrix4x4.CreateScale(1.0f / s.X, 1.0f / s.Y, 1.0f / s.Z) * m;
+            return (mm.ExtractPosition(), mm.ExtractRotation(), s);
+        }
+
+        public static bool IsOnlyTranslation(this Matrix4x4 m)
+        {
+            if (m.M11 != 1.0f) return false;
+            if (m.M22 != 1.0f) return false;
+            if (m.M33 != 1.0f) return false;
+            if (m.M12 != 0) return false;
+            if (m.M13 != 0) return false;
+            if (m.M23 != 0) return false;
+            if (m.M21 != 0) return false;
+            if (m.M31 != 0) return false;
+            if (m.M32 != 0) return false;
+            return true;
+        }
+
+        /// 
+        /// 移動 z反転
+        /// 回転 z反転
+        /// 拡大 据え置き
+        ///
+        /// これでいいのか?
+        /// 
+        public static Matrix4x4 ReverseZ(this Matrix4x4 m)
+        {
+            if (m.IsOnlyTranslation())
+            {
+                var ret = m;
+                ret.M43 = -ret.M43;
+                return ret;
+            }
+            else
+            {
+                var (t, r, s) = m.Decompose();
+                return FromTRS(t.ReverseZ(), r.ReverseZ(), s);
+            }
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/NumericsExtensions.cs.meta b/Assets/VRM10/vrmlib/Runtime/NumericsExtensions.cs.meta
new file mode 100644
index 000000000..99b9f75ca
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/NumericsExtensions.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 482b4ffcefb6ff04d90272038c48b27f
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Skin.cs b/Assets/VRM10/vrmlib/Runtime/Skin.cs
new file mode 100644
index 000000000..67bbbd8cc
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Skin.cs
@@ -0,0 +1,312 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace VrmLib
+{
+    // Bone skinning
+    public class Skin : GltfId
+    {
+        public BufferAccessor InverseMatrices;
+
+        public Node Root;
+
+        public List Joints = new List();
+
+        Matrix4x4[] m_matrices;
+        public Matrix4x4[] SkinningMatrices => m_matrices;
+
+        ushort m_indexOfRoot = ushort.MaxValue;
+
+        public Skin()
+        {
+        }
+
+        /// 
+        /// BoneSkinningもしくはMorphTargetの適用
+        /// 
+        public void Skinning(VertexBuffer vertexBuffer = null)
+        {
+            m_indexOfRoot = (ushort)Joints.IndexOf(Root);
+            var addRoot = Root != null && m_indexOfRoot == ushort.MaxValue;
+            if (addRoot)
+            {
+                m_indexOfRoot = (ushort)Joints.Count;
+                Joints.Add(Root);
+            }
+
+            if (m_matrices == null)
+            {
+                m_matrices = new Matrix4x4[Joints.Count];
+            }
+
+            if (InverseMatrices == null)
+            {
+                CalcInverseMatrices();
+            }
+            else
+            {
+                if (addRoot)
+                {
+                    var inverseArray = SpanLike.Wrap(InverseMatrices.Bytes).ToArray();
+                    var concat = inverseArray.Concat(new[] { Root.InverseMatrix }).ToArray();
+                    InverseMatrices.Assign(concat);
+                }
+            }
+
+            var inverse = InverseMatrices.GetSpan();
+
+            // if (Root != null)
+            // {
+            //     var rootInverse = Root.InverseMatrix;
+            //     var root = Root.Matrix;
+            //     for (int i = 0; i < m_matrices.Length; ++i)
+            //     {
+            //         m_matrices[i] = inverse[i] * Joints[i].Matrix * rootInverse;
+            //     }
+            // }
+            // else
+            {
+                for (int i = 0; i < m_matrices.Length; ++i)
+                {
+                    var inv = i < inverse.Length ? inverse[i] : Joints[i].InverseMatrix;
+                    m_matrices[i] = inv * Joints[i].Matrix;
+                }
+            }
+
+            if (vertexBuffer != null)
+            {
+                Apply(vertexBuffer);
+            }
+        }
+
+        void Apply(VertexBuffer vertexBuffer)
+        {
+            var dstPosition = SpanLike.Wrap(vertexBuffer.Positions.Bytes);
+            // Span emptyNormal = stackalloc Vector3[0];
+            Apply(vertexBuffer, dstPosition, vertexBuffer.Normals != null ? SpanLike.Wrap(vertexBuffer.Normals.Bytes) : default);
+        }
+
+        public void Apply(VertexBuffer vertexBuffer, SpanLike dstPosition, SpanLike dstNormal)
+        {
+            var jointsBuffer = vertexBuffer.Joints;
+            var joints = (jointsBuffer != null || jointsBuffer.Count == 0)
+                ? SpanLike.Wrap(jointsBuffer.Bytes)
+                : SpanLike.Create(vertexBuffer.Count) // when MorphTarget only
+                ;
+
+            var weightsBuffer = vertexBuffer.Weights;
+            var weights = (weightsBuffer != null || weightsBuffer.Count == 0)
+                ? SpanLike.Wrap(weightsBuffer.Bytes)
+                : SpanLike.Create(vertexBuffer.Count) // when MorphTarget only
+                ;
+
+            var positionBuffer = vertexBuffer.Positions;
+            var position = SpanLike.Wrap(positionBuffer.Bytes);
+
+            bool useNormal = false;
+            if (dstNormal.Length > 0)
+            {
+                useNormal = vertexBuffer.Normals != null && dstNormal.Length == dstPosition.Length;
+            }
+
+            for (int i = 0; i < position.Length; ++i)
+            {
+                var j = joints[i];
+                var w = weights[i];
+
+                var sum = (w.X + w.Y + w.Z + w.W);
+                float factor;
+                if (sum > 0)
+                {
+                    factor = 1.0f / sum;
+                }
+                else
+                {
+                    factor = 1.0f;
+                    j = new SkinJoints(m_indexOfRoot, 0, 0, 0);
+                    w = new Vector4(1, 0, 0, 0);
+                }
+                if (j.Joint0 == ushort.MaxValue) w.X = 0;
+                if (j.Joint1 == ushort.MaxValue) w.Y = 0;
+                if (j.Joint2 == ushort.MaxValue) w.Z = 0;
+                if (j.Joint3 == ushort.MaxValue) w.W = 0;
+
+                {
+                    var src = new Vector4(position[i], 1); // 位置ベクトル
+                    var dst = Vector4.Zero;
+                    if (w.X > 0) dst += Vector4.Transform(src, m_matrices[j.Joint0]) * w.X * factor;
+                    if (w.Y > 0) dst += Vector4.Transform(src, m_matrices[j.Joint1]) * w.Y * factor;
+                    if (w.Z > 0) dst += Vector4.Transform(src, m_matrices[j.Joint2]) * w.Z * factor;
+                    if (w.W > 0) dst += Vector4.Transform(src, m_matrices[j.Joint3]) * w.W * factor;
+                    dstPosition[i] = new Vector3(dst.X, dst.Y, dst.Z);
+                }
+                if (useNormal)
+                {
+                    var normalBuffer = vertexBuffer.Normals;
+                    var normal = normalBuffer != null ? SpanLike.Wrap(normalBuffer.Bytes) : dstNormal;
+                    var src = new Vector4(normal[i], 0); // 方向ベクトル
+                    var dst = Vector4.Zero;
+                    if (w.X > 0) dst += Vector4.Transform(src, m_matrices[j.Joint0]) * w.X * factor;
+                    if (w.Y > 0) dst += Vector4.Transform(src, m_matrices[j.Joint1]) * w.Y * factor;
+                    if (w.Z > 0) dst += Vector4.Transform(src, m_matrices[j.Joint2]) * w.Z * factor;
+                    if (w.W > 0) dst += Vector4.Transform(src, m_matrices[j.Joint3]) * w.W * factor;
+                    dstNormal[i] = new Vector3(dst.X, dst.Y, dst.Z);
+                }
+            }
+        }
+
+        // だいたい Identity
+        static bool IsIdentity(Matrix4x4 m)
+        {
+            // 回転・スケール・しあー
+            if (
+                m.M11 == 1 && m.M12 == 0 && m.M13 == 0 && m.M14 == 0
+                && m.M21 == 0 && m.M22 == 1 && m.M23 == 0 && m.M24 == 0
+                && m.M31 == 0 && m.M32 == 0 && m.M33 == 1 && m.M34 == 0
+                && m.M44 == 1
+            )
+            {
+
+            }
+            else
+            {
+                return false;
+            }
+
+            if (Math.Abs(m.M41) > 1e-5f) return false;
+            if (Math.Abs(m.M42) > 1e-5f) return false;
+            if (Math.Abs(m.M43) > 1e-5f) return false;
+
+            return true;
+        }
+
+        public override string ToString()
+        {
+            if (InverseMatrices != null)
+            {
+                var sb = new StringBuilder();
+                var matrices = SpanLike.Wrap(InverseMatrices.Bytes);
+                var count = 0;
+                // var rootMatrix = Matrix4x4.Identity;
+                // if (Root != null)
+                // {
+                //     rootMatrix = Root.InverseMatrix;
+                // }
+                for (int i = 0; i < matrices.Length; ++i)
+                {
+                    var m = matrices[i] * Joints[i].Matrix;
+                    if (!IsIdentity(m))
+                    {
+                        ++count;
+                    }
+                }
+                if (count > 0)
+                {
+                    sb.Append($"{count}/{Joints.Count} is not normalized");
+                }
+                else
+                {
+                    sb.Append($"{Joints.Count} joints normalized");
+                }
+                return sb.ToString();
+            }
+            else
+            {
+                return $"{Joints.Count} joints without InverseMatrices";
+            }
+        }
+
+        public void Replace(Node src, Node dst)
+        {
+            var removeIndex = Joints.IndexOf(src);
+            if (removeIndex >= 0)
+            {
+                Joints[removeIndex] = dst;
+
+                // エクスポート時に再計算させる
+                CalcInverseMatrices();
+            }
+        }
+
+        public void CalcInverseMatrices()
+        {
+            // var root = Root;
+            // if (root == null)
+            // {
+            //     root = Joints[0].Ancestors().Last();
+            // }
+            // root.CalcWorldMatrix(Matrix4x4.Identity, true);
+
+            // calc inverse bind matrices
+            var matricesBytes = new Byte[Marshal.SizeOf(typeof(Matrix4x4)) * Joints.Count];
+            var matrices = SpanLike.Wrap(new ArraySegment(matricesBytes));
+            for (int i = 0; i < Joints.Count; ++i)
+            {
+                // var w = Joints[i].Matrix;
+                // Matrix4x4.Invert(w, out Matrix4x4 inv);
+                if (Joints[i] != null)
+                {
+                    matrices[i] = Joints[i].InverseMatrix;
+                }
+            }
+            InverseMatrices = new BufferAccessor(new ArraySegment(matricesBytes), AccessorValueType.FLOAT, AccessorVectorType.MAT4, Joints.Count);
+        }
+
+        static void Update(ref float weight, ref ushort index, int[] indexMap)
+        {
+            if (indexMap[index] == -1)
+            {
+                if (weight > 0)
+                {
+                    throw new Exception();
+                }
+                //削除された
+                weight = 0;
+                index = 0;
+            }
+            else
+            {
+                // 参照を更新(変わっているかもしれない)
+                index = (ushort)indexMap[index];
+            }
+        }
+
+        /// 
+        /// nullになったjointを除去して、boneweightを前に詰める
+        /// 
+        public void FixBoneWeight(BufferAccessor jointsAccessor, BufferAccessor weightsAccessor)
+        {
+            var map = Joints.Select((x, i) => ValueTuple.Create(i, x)).Where(x => x.Item2 != null).ToArray();
+            var indexMap = Enumerable.Repeat(-1, Joints.Count).ToArray();
+            {
+                for (int i = 0; i < map.Length; ++i)
+                {
+                    indexMap[map[i].Item1] = i;
+                }
+            }
+            Joints.RemoveAll(x => x == null);
+
+            var joints = jointsAccessor.GetSpan();
+            var weights = weightsAccessor.GetSpan();
+            for (int i = 0; i < joints.Length; ++i)
+            {
+                var j = joints[i];
+                var w = weights[i];
+
+                Update(ref w.X, ref j.Joint0, indexMap);
+                Update(ref w.Y, ref j.Joint1, indexMap);
+                Update(ref w.Z, ref j.Joint2, indexMap);
+                Update(ref w.W, ref j.Joint3, indexMap);
+
+                joints[i] = j;
+                weights[i] = w;
+            }
+
+            CalcInverseMatrices();
+        }
+    }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Runtime/Skin.cs.meta b/Assets/VRM10/vrmlib/Runtime/Skin.cs.meta
new file mode 100644
index 000000000..12db0b351
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Skin.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 7435dea123c851c4a93dd046d5e40b70
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/SkinJoint.cs b/Assets/VRM10/vrmlib/Runtime/SkinJoint.cs
new file mode 100644
index 000000000..ecdcde104
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/SkinJoint.cs
@@ -0,0 +1,29 @@
+using System;
+
+namespace VrmLib
+{
+    public struct SkinJoints : IEquatable
+    {
+        public ushort Joint0;
+        public ushort Joint1;
+        public ushort Joint2;
+        public ushort Joint3;
+
+        public SkinJoints(ushort j0, ushort j1, ushort j2, ushort j3)
+        {
+            Joint0 = j0;
+            Joint1 = j1;
+            Joint2 = j2;
+            Joint3 = j3;
+        }
+
+        public bool Equals(SkinJoints other)
+        {
+            if (Joint0 != other.Joint0) return false;
+            if (Joint1 != other.Joint1) return false;
+            if (Joint2 != other.Joint2) return false;
+            if (Joint3 != other.Joint3) return false;
+            return true;
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/SkinJoint.cs.meta b/Assets/VRM10/vrmlib/Runtime/SkinJoint.cs.meta
new file mode 100644
index 000000000..817fa7c89
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/SkinJoint.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 51bc7ca26fb1f094588cd8c181d48c89
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/SpanLike.cs b/Assets/VRM10/vrmlib/Runtime/SpanLike.cs
new file mode 100644
index 000000000..7ab5b24bc
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/SpanLike.cs
@@ -0,0 +1,513 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+namespace VrmLib
+{
+    public struct Byte4
+    {
+        public byte X;
+        public byte Y;
+        public byte Z;
+        public byte W;
+    }
+
+    public struct UShort4
+    {
+        public ushort X;
+        public ushort Y;
+        public ushort Z;
+        public ushort W;
+    }
+
+    static class BitWriter
+    {
+        public static void Write(byte[] bytes, int i, Int32 value)
+        {
+            var tmp = BitConverter.GetBytes(value);
+            bytes[i++] = tmp[0];
+            bytes[i++] = tmp[1];
+            bytes[i++] = tmp[2];
+            bytes[i++] = tmp[3];
+        }
+
+        #region UINT
+        public static void Write(byte[] bytes, int i, Byte value)
+        {
+            bytes[i++] = value;
+        }
+
+        public static void Write(byte[] bytes, int i, UInt16 value)
+        {
+            var tmp = BitConverter.GetBytes(value);
+            bytes[i++] = tmp[0];
+            bytes[i++] = tmp[1];
+        }
+
+        public static void Write(byte[] bytes, int i, UInt32 value)
+        {
+            var tmp = BitConverter.GetBytes(value);
+            bytes[i++] = tmp[0];
+            bytes[i++] = tmp[1];
+            bytes[i++] = tmp[2];
+            bytes[i++] = tmp[3];
+        }
+        #endregion
+
+        public static void Write(byte[] bytes, int i, Single value)
+        {
+            var tmp = BitConverter.GetBytes(value);
+            bytes[i++] = tmp[0];
+            bytes[i++] = tmp[1];
+            bytes[i++] = tmp[2];
+            bytes[i++] = tmp[3];
+        }
+
+        public static void Write(byte[] bytes, int i, System.Numerics.Vector2 value)
+        {
+            Write(bytes, i, value.X);
+            Write(bytes, i + 4, value.Y);
+        }
+
+        public static void Write(byte[] bytes, int i, System.Numerics.Vector3 value)
+        {
+            Write(bytes, i, value.X);
+            Write(bytes, i + 4, value.Y);
+            Write(bytes, i + 8, value.Z);
+        }
+
+        public static void Write(byte[] bytes, int i, System.Numerics.Vector4 value)
+        {
+            Write(bytes, i, value.X);
+            Write(bytes, i + 4, value.Y);
+            Write(bytes, i + 8, value.Z);
+            Write(bytes, i + 12, value.W);
+        }
+
+        public static void Write(byte[] bytes, int i, System.Numerics.Quaternion value)
+        {
+            Write(bytes, i, value.X);
+            Write(bytes, i + 4, value.Y);
+            Write(bytes, i + 8, value.Z);
+            Write(bytes, i + 12, value.W);
+        }
+
+        public static void Write(byte[] bytes, int i, System.Numerics.Matrix4x4 value)
+        {
+            Write(bytes, i, value.M11);
+            Write(bytes, i + 4, value.M12);
+            Write(bytes, i + 8, value.M13);
+            Write(bytes, i + 12, value.M14);
+            Write(bytes, i + 16, value.M21);
+            Write(bytes, i + 20, value.M22);
+            Write(bytes, i + 24, value.M23);
+            Write(bytes, i + 28, value.M24);
+            Write(bytes, i + 32, value.M31);
+            Write(bytes, i + 36, value.M32);
+            Write(bytes, i + 40, value.M33);
+            Write(bytes, i + 44, value.M34);
+            Write(bytes, i + 48, value.M41);
+            Write(bytes, i + 52, value.M42);
+            Write(bytes, i + 56, value.M43);
+            Write(bytes, i + 60, value.M44);
+        }
+
+        public static void Write(byte[] bytes, int i, UShort4 value)
+        {
+            Write(bytes, i, value.X);
+            Write(bytes, i + 2, value.Y);
+            Write(bytes, i + 4, value.Z);
+            Write(bytes, i + 6, value.W);
+        }
+
+        public static void Write(byte[] bytes, int i, SkinJoints value)
+        {
+            Write(bytes, i, value.Joint0);
+            Write(bytes, i + 2, value.Joint1);
+            Write(bytes, i + 4, value.Joint2);
+            Write(bytes, i + 6, value.Joint3);
+        }
+
+        public static void Write(byte[] bytes, int i, Byte4 value)
+        {
+            bytes[i++] = value.X;
+            bytes[i++] = value.Y;
+            bytes[i++] = value.Z;
+            bytes[i++] = value.W;
+        }
+
+        public static void Write(byte[] bytes, int i, UnityEngine.Vector2 value)
+        {
+            Write(bytes, i, value.x);
+            Write(bytes, i + 4, value.y);
+        }
+
+        public static void Write(byte[] bytes, int i, UnityEngine.Vector3 value)
+        {
+            Write(bytes, i, value.x);
+            Write(bytes, i + 4, value.y);
+            Write(bytes, i + 8, value.z);
+        }
+
+        public static void Write(byte[] bytes, int i, UnityEngine.Vector4 value)
+        {
+            Write(bytes, i, value.x);
+            Write(bytes, i + 4, value.y);
+            Write(bytes, i + 8, value.z);
+            Write(bytes, i + 12, value.w);
+        }
+        public static void Write(byte[] bytes, int i, UnityEngine.Matrix4x4 value)
+        {
+            Write(bytes, i, value.m00);
+            Write(bytes, i + 4, value.m01);
+            Write(bytes, i + 8, value.m02);
+            Write(bytes, i + 12, value.m03);
+            Write(bytes, i + 16, value.m10);
+            Write(bytes, i + 20, value.m11);
+            Write(bytes, i + 24, value.m12);
+            Write(bytes, i + 28, value.m13);
+            Write(bytes, i + 32, value.m20);
+            Write(bytes, i + 36, value.m21);
+            Write(bytes, i + 40, value.m22);
+            Write(bytes, i + 44, value.m23);
+            Write(bytes, i + 48, value.m30);
+            Write(bytes, i + 52, value.m31);
+            Write(bytes, i + 56, value.m32);
+            Write(bytes, i + 60, value.m33);
+        }
+    }
+
+    public static class KeyValuePariExtensions
+    {
+        public static void Deconstruct(this KeyValuePair pair, out T key, out U value)
+        {
+            key = pair.Key;
+            value = pair.Value;
+        }
+    }
+
+    public static class ArraySegmentExtensions
+    {
+        public static ArraySegment Slice(this ArraySegment self, int start, int length)
+        {
+            if (start + length > self.Count)
+            {
+                throw new ArgumentOutOfRangeException();
+            }
+            return new ArraySegment(
+                self.Array,
+                self.Offset + start,
+                length
+            );
+        }
+
+        public static ArraySegment Slice(this ArraySegment self, int start)
+        {
+            if (start > self.Count)
+            {
+                throw new ArgumentOutOfRangeException();
+            }
+            return self.Slice(start, self.Count - start);
+        }
+    }
+
+    public struct SpanLike : IEquatable>, IEnumerable
+    where T : struct
+    {
+        public readonly ArraySegment Bytes;
+
+        readonly int m_itemSize;
+        public readonly int Length;
+
+        public delegate T Getter(byte[] bytes, int start);
+        readonly Getter m_getter;
+
+        public delegate void Setter(byte[] bytes, int i, T value);
+        readonly Setter m_setter;
+
+        public T this[int i]
+        {
+            set
+            {
+                m_setter(Bytes.Array, Bytes.Offset + i * m_itemSize, value);
+            }
+            get
+            {
+                return m_getter(Bytes.Array, Bytes.Offset + i * m_itemSize);
+            }
+        }
+
+        public SpanLike(ArraySegment bytes, int itemSize, Getter getter, Setter setter)
+        {
+            Bytes = bytes;
+            m_itemSize = itemSize;
+            Length = Bytes.Count / m_itemSize;
+            m_getter = getter;
+            m_setter = setter;
+        }
+
+        public SpanLike Slice(int offset, int count)
+        {
+            var bytesOffset = offset * m_itemSize;
+            var bytesLength = count * m_itemSize;
+            if (bytesOffset + bytesLength > Bytes.Count)
+            {
+                throw new ArgumentOutOfRangeException();
+            }
+            return new SpanLike(new ArraySegment(
+                Bytes.Array,
+                Bytes.Offset + bytesOffset,
+                bytesLength
+            ), m_itemSize, m_getter, m_setter);
+        }
+
+        public SpanLike Slice(int offset)
+        {
+            var bytesOffset = offset * m_itemSize;
+            if (bytesOffset > Bytes.Count)
+            {
+                throw new ArgumentOutOfRangeException();
+            }
+            var bytesLength = Bytes.Count - bytesOffset;
+            return new SpanLike(new ArraySegment(
+                Bytes.Array,
+                Bytes.Offset + bytesOffset,
+                bytesLength
+            ), m_itemSize, m_getter, m_setter);
+        }
+
+        public T[] ToArray()
+        {
+            var array = new T[Length];
+            Bytes.FromBytes(array);
+            return array;
+        }
+
+        public bool Equals(SpanLike other)
+        {
+            if (Length != other.Length)
+            {
+                return false;
+            }
+
+            var end = Length;
+            for (int i = 0; i < end; ++i)
+            {
+                if (!this[i].Equals(other[i]))
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        public IEnumerator GetEnumerator()
+        {
+            for (int i = 0; i < Length; ++i)
+            {
+                yield return this[i];
+            }
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return (IEnumerator)GetEnumerator();
+        }
+    }
+
+    public static class SpanLike
+    {
+        struct GetSet where T : struct
+        {
+            public SpanLike.Getter Getter;
+            public SpanLike.Setter Setter;
+        }
+
+        public static readonly Dictionary Map = new Dictionary()
+        {
+            {typeof(Byte), new GetSet{
+                Getter = (array, start) => array[start],
+                Setter = BitWriter.Write}},
+            {typeof(UInt16), new GetSet{
+                Getter = BitConverter.ToUInt16,
+                Setter = BitWriter.Write}},
+            {typeof(UInt32), new GetSet{
+                Getter = BitConverter.ToUInt32,
+                Setter = BitWriter.Write}},
+            {typeof(Int32), new GetSet{
+                Getter = BitConverter.ToInt32,
+                Setter = BitWriter.Write}},
+            {typeof(Single), new GetSet{
+                Getter = BitConverter.ToSingle,
+                Setter = BitWriter.Write}},
+            {typeof(System.Numerics.Vector2), new GetSet{
+                Getter = (array, start) =>
+                    new System.Numerics.Vector2(
+                        BitConverter.ToSingle(array, start),
+                        BitConverter.ToSingle(array, start + 4)
+                    ),
+                Setter = BitWriter.Write
+            }},
+            {typeof(System.Numerics.Vector3), new GetSet{
+                Getter = (array, start) =>
+                    new System.Numerics.Vector3(
+                        BitConverter.ToSingle(array, start),
+                        BitConverter.ToSingle(array, start + 4),
+                        BitConverter.ToSingle(array, start + 8)
+                    ),
+                Setter = BitWriter.Write
+            }},
+            {typeof(System.Numerics.Vector4), new GetSet{
+                Getter = (array, start) =>
+                    new System.Numerics.Vector4(
+                        BitConverter.ToSingle(array, start),
+                        BitConverter.ToSingle(array, start + 4),
+                        BitConverter.ToSingle(array, start + 8),
+                        BitConverter.ToSingle(array, start + 12)
+                    ),
+                Setter = BitWriter.Write
+            }},
+            {typeof(System.Numerics.Quaternion), new GetSet{
+                Getter = (array, start) =>
+                    new System.Numerics.Quaternion(
+                        BitConverter.ToSingle(array, start),
+                        BitConverter.ToSingle(array, start + 4),
+                        BitConverter.ToSingle(array, start + 8),
+                        BitConverter.ToSingle(array, start + 12)
+                    ),
+                Setter = BitWriter.Write
+            }},
+            {typeof(System.Numerics.Matrix4x4), new GetSet{
+                Getter = (array, start) =>
+                    new System.Numerics.Matrix4x4(
+                        BitConverter.ToSingle(array, start),
+                        BitConverter.ToSingle(array, start + 4),
+                        BitConverter.ToSingle(array, start + 8),
+                        BitConverter.ToSingle(array, start + 12),
+                        BitConverter.ToSingle(array, start + 16),
+                        BitConverter.ToSingle(array, start + 20),
+                        BitConverter.ToSingle(array, start + 24),
+                        BitConverter.ToSingle(array, start + 28),
+                        BitConverter.ToSingle(array, start + 32),
+                        BitConverter.ToSingle(array, start + 36),
+                        BitConverter.ToSingle(array, start + 40),
+                        BitConverter.ToSingle(array, start + 44),
+                        BitConverter.ToSingle(array, start + 48),
+                        BitConverter.ToSingle(array, start + 52),
+                        BitConverter.ToSingle(array, start + 56),
+                        BitConverter.ToSingle(array, start + 60)
+                    ),
+                Setter = BitWriter.Write
+            }},
+            {typeof(Byte4), new GetSet{
+                Getter = (array, start) =>
+                    new Byte4
+                    {
+                        X = array[start],
+                        Y = array[start + 1],
+                        Z = array[start + 2],
+                        W = array[start + 3],
+                    },
+                Setter = BitWriter.Write
+            }},
+            {typeof(UShort4), new GetSet{
+                Getter = (array, start) =>
+                    new UShort4
+                    {
+                        X = BitConverter.ToUInt16(array, start),
+                        Y = BitConverter.ToUInt16(array, start + 2),
+                        Z = BitConverter.ToUInt16(array, start + 4),
+                        W = BitConverter.ToUInt16(array, start + 6)
+                    },
+                Setter = BitWriter.Write
+            }},
+            {typeof(SkinJoints), new GetSet{
+                Getter = (array, start) =>
+                    new SkinJoints
+                    {
+                        Joint0 = BitConverter.ToUInt16(array, start),
+                        Joint1 = BitConverter.ToUInt16(array, start + 2),
+                        Joint2 = BitConverter.ToUInt16(array, start + 4),
+                        Joint3 = BitConverter.ToUInt16(array, start + 6),
+                    },
+                Setter = BitWriter.Write
+            }},
+            {typeof(UnityEngine.Vector2), new GetSet{
+                Getter = (array, start) =>
+                    new UnityEngine.Vector2(
+                        BitConverter.ToSingle(array, start),
+                        BitConverter.ToSingle(array, start + 4)
+                    ),
+                Setter = BitWriter.Write
+            }},
+            {typeof(UnityEngine.Vector3), new GetSet{
+                Getter = (array, start) =>
+                    new UnityEngine.Vector3(
+                        BitConverter.ToSingle(array, start),
+                        BitConverter.ToSingle(array, start + 4),
+                        BitConverter.ToSingle(array, start + 8)
+                    ),
+                Setter = BitWriter.Write
+            }},
+            {typeof(UnityEngine.Vector4), new GetSet{
+                Getter = (array, start) =>
+                    new UnityEngine.Vector4(
+                        BitConverter.ToSingle(array, start),
+                        BitConverter.ToSingle(array, start + 4),
+                        BitConverter.ToSingle(array, start + 8),
+                        BitConverter.ToSingle(array, start + 12)
+                    ),
+                Setter = BitWriter.Write
+            }},
+            {typeof(UnityEngine.Matrix4x4), new GetSet{
+                Getter = (array, start) =>
+                    new UnityEngine.Matrix4x4(
+                        new UnityEngine.Vector4(BitConverter.ToSingle(array, start),
+                        BitConverter.ToSingle(array, start + 4),
+                        BitConverter.ToSingle(array, start + 8),
+                        BitConverter.ToSingle(array, start + 12)),
+                        new UnityEngine.Vector4(BitConverter.ToSingle(array, start+16),
+                        BitConverter.ToSingle(array, start + 20),
+                        BitConverter.ToSingle(array, start + 24),
+                        BitConverter.ToSingle(array, start + 28)),
+                        new UnityEngine.Vector4(BitConverter.ToSingle(array, start+32),
+                        BitConverter.ToSingle(array, start + 36),
+                        BitConverter.ToSingle(array, start + 40),
+                        BitConverter.ToSingle(array, start + 44)),
+                        new UnityEngine.Vector4(BitConverter.ToSingle(array, start+48),
+                        BitConverter.ToSingle(array, start + 52),
+                        BitConverter.ToSingle(array, start + 56),
+                        BitConverter.ToSingle(array, start + 60))
+                    ),
+                Setter = BitWriter.Write
+            }},
+
+        };
+
+        public static SpanLike Wrap(ArraySegment bytes) where T : struct
+        {
+            if (!Map.TryGetValue(typeof(T), out object value))
+            {
+                throw new KeyNotFoundException($"{typeof(T)}");
+            }
+            var getset = (GetSet)value;
+            return new SpanLike(bytes, Marshal.SizeOf(), getset.Getter, getset.Setter);
+        }
+
+        public static SpanLike Create(int count) where T : struct
+        {
+            var itemSize = Marshal.SizeOf();
+            var array = new byte[count / itemSize];
+            return Wrap(new ArraySegment(array));
+        }
+
+        public static SpanLike CopyFrom(T[] src) where T : struct
+        {
+            var buffer = new byte[src.Length * Marshal.SizeOf()];
+            src.ToBytes(new ArraySegment(buffer));
+            return Wrap(new ArraySegment(buffer));
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/SpanLike.cs.meta b/Assets/VRM10/vrmlib/Runtime/SpanLike.cs.meta
new file mode 100644
index 000000000..7610729e5
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/SpanLike.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 48a1d420ea119e944861a729a7d1c6f4
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/VertexBuffer.cs b/Assets/VRM10/vrmlib/Runtime/VertexBuffer.cs
new file mode 100644
index 000000000..601933b9c
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/VertexBuffer.cs
@@ -0,0 +1,290 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Runtime.InteropServices;
+
+namespace VrmLib
+{
+    public class VertexBuffer : IEnumerable>
+    {
+        public Dictionary VertexBuffers = new Dictionary();
+
+        public bool ContainsKey(string key)
+        {
+            return VertexBuffers.ContainsKey(key);
+        }
+
+        public IEnumerator> GetEnumerator()
+        {
+            return VertexBuffers.GetEnumerator();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+
+        public void Add(string key, BufferAccessor accessor)
+        {
+            VertexBuffers.Add(key, accessor);
+        }
+
+        public bool TryGetValue(string key, out BufferAccessor accessor)
+        {
+            return VertexBuffers.TryGetValue(key, out accessor);
+        }
+
+        public int Count
+        {
+            get
+            {
+                if (VertexBuffers.TryGetValue(PositionKey, out BufferAccessor buffer))
+                {
+                    return buffer.Count;
+                }
+                return 0;
+            }
+        }
+
+        public const string PositionKey = "POSITION";
+
+        public BufferAccessor Positions
+        {
+            get
+            {
+                if (VertexBuffers.TryGetValue(PositionKey, out BufferAccessor buffer))
+                {
+                    return buffer;
+                }
+                return null;
+            }
+        }
+
+        public const string NormalKey = "NORMAL";
+        public BufferAccessor Normals
+        {
+            get
+            {
+                if (VertexBuffers.TryGetValue(NormalKey, out BufferAccessor buffer))
+                {
+                    return buffer;
+                }
+                return null;
+            }
+        }
+
+        public const string TangentKey = "TANGENT";
+        public const string ColorKey = "COLOR_0";
+        public BufferAccessor Colors
+        {
+            get
+            {
+                if (VertexBuffers.TryGetValue(ColorKey, out BufferAccessor buffer))
+                {
+                    return buffer;
+                }
+                return null;
+            }
+        }
+
+        public const string TexCoordKey = "TEXCOORD_0";
+        public BufferAccessor TexCoords
+        {
+            get
+            {
+                if (VertexBuffers.TryGetValue(TexCoordKey, out BufferAccessor buffer))
+                {
+                    return buffer;
+                }
+                return null;
+            }
+        }
+
+        public const string TexCoordKey2 = "TEXCOORD_1";
+
+        public const string JointKey = "JOINTS_0";
+        public BufferAccessor Joints
+        {
+            get
+            {
+                if (VertexBuffers.TryGetValue(JointKey, out BufferAccessor buffer))
+                {
+                    return buffer;
+                }
+                return null;
+            }
+        }
+
+        public const string WeightKey = "WEIGHTS_0";
+        public BufferAccessor Weights
+        {
+            get
+            {
+                if (VertexBuffers.TryGetValue(WeightKey, out BufferAccessor buffer))
+                {
+                    return buffer;
+                }
+                return null;
+            }
+        }
+
+        public void RemoveTangent()
+        {
+            if (VertexBuffers.ContainsKey(TangentKey))
+            {
+                VertexBuffers.Remove(TangentKey);
+            }
+        }
+
+        public int ByteLength
+        {
+            get
+            {
+                return VertexBuffers.Sum(x => x.Value.ByteLength);
+            }
+        }
+
+        public void ValidateLength(string name = "")
+        {
+            foreach (var kv in VertexBuffers)
+            {
+                if (kv.Key == PositionKey) continue;
+
+                if (kv.Value.Count != Count)
+                {
+                    var msg = "vertex attribute not same length";
+                    if (!string.IsNullOrEmpty(name))
+                    {
+                        msg = $"{name}: {msg}";
+                    }
+                    throw new ArgumentException(msg);
+                }
+            }
+        }
+
+        public void ValidateNAN()
+        {
+            foreach (var kv in VertexBuffers)
+            {
+                if (kv.Value.ComponentType == AccessorValueType.FLOAT)
+                {
+                    var values = kv.Value.GetSpan(false);
+                    int i = 0;
+                    foreach (var f in values)
+                    {
+                        if (float.IsNaN(f)) throw new ArithmeticException("float error");
+                        ++i;
+                    }
+                }
+            }
+        }
+
+        public VertexBuffer()
+        {
+        }
+
+        public VertexBuffer CloneWithOffset(int offsetCount)
+        {
+            var vb = new VertexBuffer();
+            foreach (var kv in VertexBuffers)
+            {
+                vb.VertexBuffers[kv.Key] = kv.Value.CloneWithOffset(offsetCount);
+            }
+            return vb;
+        }
+
+        public SpanLike GetOrCreateJoints()
+        {
+            var buffer = Joints;
+            if (buffer == null)
+            {
+                buffer = new BufferAccessor(
+                    new ArraySegment(new byte[Marshal.SizeOf(typeof(SkinJoints)) * Count]),
+                    AccessorValueType.UNSIGNED_SHORT,
+                    AccessorVectorType.VEC4, Count);
+                Add(JointKey, buffer);
+            }
+            return SpanLike.Wrap(buffer.Bytes);
+        }
+
+        public SpanLike GetOrCreateWeights()
+        {
+            var buffer = Weights;
+            if (buffer == null)
+            {
+                buffer = new BufferAccessor(
+                    new ArraySegment(new byte[Marshal.SizeOf(typeof(Vector4)) * Count]),
+                    AccessorValueType.FLOAT,
+                    AccessorVectorType.VEC4, Count);
+                Add(WeightKey, buffer);
+            }
+            return SpanLike.Wrap(buffer.Bytes);
+        }
+
+        static bool HasSameKeys(Dictionary lhs, Dictionary rhs)
+        {
+            if (lhs.Count != rhs.Count) return false;
+            foreach (var (l, r) in Enumerable.Zip(lhs.Keys.OrderBy(x => x), rhs.Keys.OrderBy(x => x), (l, r) => (l, r)))
+            {
+                if (l != r)
+                {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        public void Append(VertexBuffer v)
+        {
+            var keys = VertexBuffers.Keys.ToList();
+
+            var lastCount = Count;
+
+            // v から VertexBufferfs に足す
+            foreach (var kv in v.VertexBuffers)
+            {
+                if (VertexBuffers.TryGetValue(kv.Key, out BufferAccessor buffer))
+                {
+                    // used
+                    keys.Remove(kv.Key);
+                    if (buffer.Count != lastCount)
+                    {
+                        throw new ArgumentException();
+                    }
+                }
+                else
+                {
+                    // add empty
+                    var byteLength = lastCount * kv.Value.Stride;
+                    buffer = new BufferAccessor(new ArraySegment(new byte[byteLength]), kv.Value.ComponentType, kv.Value.AccessorType, lastCount);
+                    if (buffer.Count != lastCount)
+                    {
+                        throw new ArgumentException();
+                    }
+                    VertexBuffers.Add(kv.Key, buffer);
+                }
+
+                buffer.Append(kv.Value);
+            }
+
+            // 足されなかったキーに同じ長さを詰める
+            foreach (var key in keys)
+            {
+                var dst = VertexBuffers[key];
+                dst.Extend(v.Positions.Count);
+            }
+
+            ValidateLength();
+        }
+
+        public void Resize(int n)
+        {
+            foreach (var kv in VertexBuffers)
+            {
+                kv.Value.Resize(n);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Runtime/VertexBuffer.cs.meta b/Assets/VRM10/vrmlib/Runtime/VertexBuffer.cs.meta
new file mode 100644
index 000000000..ba13bdd3f
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/VertexBuffer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: fd0c2117beaa13845aa49d48c7bd8f48
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/VertexBufferExtensions.cs b/Assets/VRM10/vrmlib/Runtime/VertexBufferExtensions.cs
new file mode 100644
index 000000000..6f0b4c6e3
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/VertexBufferExtensions.cs
@@ -0,0 +1,103 @@
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+
+namespace VrmLib
+{
+    public static class VertexBufferExtensions
+    {
+        public static void Add(this VertexBuffer v, string key, T[] list) where T : struct
+        {
+            v.Add(key, BufferAccessor.Create(list));
+        }
+
+        public static void FixBoneWeight(this VertexBuffer v)
+        {
+            var joints = v.Joints.GetSpan();
+            var weights = v.Weights.GetSpan();
+            if (joints.Length != weights.Length)
+            {
+                throw new System.Exception();
+            }
+
+            for (int i = 0; i < joints.Length; ++i)
+            {
+                var j = joints[i];
+                var w = weights[i];
+
+                int n = 0;
+                if (w.X > 0) ++n;
+                if (w.Y > 0) ++n;
+                if (w.Z > 0) ++n;
+                if (w.W > 0) ++n;
+                if (n == 1)
+                {
+                    if (w.X == 0)
+                    {
+                        if (w.Y > 0)
+                        {
+                            w.X = w.Y;
+                            w.Y = 0;
+                            j.Joint0 = j.Joint1;
+                            j.Joint1 = 0;
+                        }
+                        else
+                        {
+                            throw new Exception();
+                        }
+                    }
+                }
+                else if (n == 2)
+                {
+                    if (w.X == 0 || w.Y == 0)
+                    {
+                        throw new Exception();
+                    }
+                    else
+                    {
+                        if (w.X >= w.Y)
+                        {
+
+                        }
+                        else
+                        {
+                            throw new Exception();
+                        }
+                    }
+                }
+                else if (n == 3)
+                {
+                    if (w.W != 0)
+                    {
+                        throw new Exception();
+                    }
+                    else
+                    {
+                        if (w.X >= w.Y && w.Y >= w.Z)
+                        {
+
+                        }
+                        else
+                        {
+                            throw new Exception();
+                        }
+                    }
+                }
+                else if (n == 4)
+                {
+                    if (w.X >= w.Y && w.Y >= w.Z && w.Z >= w.W)
+                    {
+
+                    }
+                    else
+                    {
+                        throw new Exception();
+                    }
+                }
+
+                joints[i] = j;
+                weights[i] = w;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Runtime/VertexBufferExtensions.cs.meta b/Assets/VRM10/vrmlib/Runtime/VertexBufferExtensions.cs.meta
new file mode 100644
index 000000000..1e6e4d17d
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/VertexBufferExtensions.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 1c95aaad1a35e0c45a6f37f4a5604458
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm.meta b/Assets/VRM10/vrmlib/Runtime/Vrm.meta
new file mode 100644
index 000000000..26ebf0757
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 1a5f2134b543a044fb0396a2655abf72
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/Expression.cs b/Assets/VRM10/vrmlib/Runtime/Vrm/Expression.cs
new file mode 100644
index 000000000..0caf33f6a
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/Expression.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+
+namespace VrmLib
+{
+    public class Expression
+    {
+        public readonly ExpressionPreset Preset;
+        public readonly string Name;
+
+        public bool IsBinary;
+
+        public bool IgnoreBlink;
+        public bool IgnoreLookAt;
+        public bool IgnoreMouth;
+
+        public readonly List MorphTargetBinds = new List();
+
+        public readonly List MaterialColorBinds = new List();
+
+        public readonly List TextureTransformBinds = new List();
+
+        public void CleanupUVScaleOffset()
+        {
+            // ST_S, ST_T を統合する
+            var count = TextureTransformBinds.Count;
+            var map = new Dictionary();
+            foreach (var uv in TextureTransformBinds.OrderBy(uv => uv.Material.Name).Distinct())
+            {
+                if (!map.TryGetValue(uv.Material, out TextureTransformBind value))
+                {
+                    value = new TextureTransformBind(uv.Material, Vector2.One, Vector2.Zero);
+                }
+                map[uv.Material] = value.Merge(uv);
+            }
+            TextureTransformBinds.Clear();
+            foreach (var kv in map)
+            {
+                TextureTransformBinds.Add(new TextureTransformBind(kv.Key,
+                    kv.Value.Scale,
+                    kv.Value.Offset));
+            }
+            // Console.WriteLine($"MergeUVScaleOffset: {count} => {UVScaleOffsetValues.Count}");
+        }
+
+        public Expression(ExpressionPreset preset, string name, bool isBinary)
+        {
+            Preset = preset;
+            Name = name;
+            IsBinary = isBinary;
+        }
+
+        public override string ToString()
+        {
+            return Preset.ToString();
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/Expression.cs.meta b/Assets/VRM10/vrmlib/Runtime/Vrm/Expression.cs.meta
new file mode 100644
index 000000000..afba40f28
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/Expression.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e4a3ac008751679488d4e9263bc80d45
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/ExpressionManager.cs b/Assets/VRM10/vrmlib/Runtime/Vrm/ExpressionManager.cs
new file mode 100644
index 000000000..eafaffdb9
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/ExpressionManager.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+
+namespace VrmLib
+{
+    public class ExpressionManager
+    {
+        public readonly List ExpressionList = new List();
+    }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/ExpressionManager.cs.meta b/Assets/VRM10/vrmlib/Runtime/Vrm/ExpressionManager.cs.meta
new file mode 100644
index 000000000..4fc228183
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/ExpressionManager.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 4cf8de25227692f408aed2703ff27c6c
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/ExpressionPreset.cs b/Assets/VRM10/vrmlib/Runtime/Vrm/ExpressionPreset.cs
new file mode 100644
index 000000000..26158eb1f
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/ExpressionPreset.cs
@@ -0,0 +1,24 @@
+namespace VrmLib
+{
+    public enum ExpressionPreset
+    {
+        Custom,
+        Aa,
+        Ih,
+        Ou,
+        Ee,
+        Oh,
+        Blink,
+        Joy,
+        Angry,
+        Sorrow,
+        Fun,
+        LookUp,
+        LookDown,
+        LookLeft,
+        LookRight,
+        BlinkLeft,
+        BlinkRight,
+        Neutral,
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/ExpressionPreset.cs.meta b/Assets/VRM10/vrmlib/Runtime/Vrm/ExpressionPreset.cs.meta
new file mode 100644
index 000000000..b819b9722
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/ExpressionPreset.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ebb92c36b2be88c4c835c55346488158
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/FirstPerson.cs b/Assets/VRM10/vrmlib/Runtime/Vrm/FirstPerson.cs
new file mode 100644
index 000000000..9cd7cd5f6
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/FirstPerson.cs
@@ -0,0 +1,31 @@
+using System.Collections.Generic;
+using System.Numerics;
+
+namespace VrmLib
+{
+    public enum FirstPersonMeshType
+    {
+        Auto, // Create headlessModel
+        Both, // Default layer
+        ThirdPersonOnly,
+        FirstPersonOnly,
+    }
+
+    public class FirstPersonMeshAnnotation
+    {
+        public Node Node;
+
+        public readonly FirstPersonMeshType FirstPersonFlag;
+
+        public FirstPersonMeshAnnotation(Node node, FirstPersonMeshType flag)
+        {
+            Node = node;
+            FirstPersonFlag = flag;
+        }
+    }
+
+    public class FirstPerson
+    {
+        public readonly List Annotations = new List();
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/FirstPerson.cs.meta b/Assets/VRM10/vrmlib/Runtime/Vrm/FirstPerson.cs.meta
new file mode 100644
index 000000000..7ec89205e
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/FirstPerson.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 757db113e51e3b14b970eaa4301738d3
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/IAvatarPermission.cs b/Assets/VRM10/vrmlib/Runtime/Vrm/IAvatarPermission.cs
new file mode 100644
index 000000000..7146056a9
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/IAvatarPermission.cs
@@ -0,0 +1,72 @@
+namespace VrmLib
+{
+    public enum AvatarUsageType
+    {
+        OnlyAuthor,
+        ExplicitlyLicensedPerson,
+        Everyone,
+    }
+
+    public enum CommercialUsageType
+    {
+        PersonalNonCommercialNonProfit,
+        PersonalNonCommercialProfit,
+        PersonalCommercial,
+        Corporation,
+    }
+
+    public interface IAvatarPermission
+    {
+        AvatarUsageType AvatarUsage { get; }
+        bool IsAllowedViolentUsage { get; }
+        bool IsAllowedSexualUsage { get; }
+
+        // 1.0 removed
+        bool IsAllowedCommercialUsage { get; }
+
+        // 1.0 added
+        CommercialUsageType CommercialUsage { get; }
+
+        // 1.0 added
+        bool IsAllowedPoliticalOrReligiousUsage { get; }
+
+        // 1.0 added
+        bool IsAllowedGameUsage { get; }
+
+        string OtherPermissionUrl { get; }
+    }
+
+    /// 
+    /// 1.0向け
+    /// 
+    public class AvatarPermission : IAvatarPermission
+    {
+        public AvatarUsageType AvatarUsage { get; set; }
+
+        public bool IsAllowedViolentUsage { get; set; }
+
+        public bool IsAllowedSexualUsage { get; set; }
+
+        public bool IsAllowedCommercialUsage
+        {
+            get
+            {
+                switch (CommercialUsage)
+                {
+                    case CommercialUsageType.Corporation:
+                    case CommercialUsageType.PersonalCommercial:
+                        return true;
+                }
+                return false;
+            }
+        }
+
+        public CommercialUsageType CommercialUsage { get; set; }
+
+        public bool IsAllowedPoliticalOrReligiousUsage { get; set; }
+
+        public bool IsAllowedGameUsage { get; set; }
+
+        public string OtherPermissionUrl { get; set; } = "";
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/IAvatarPermission.cs.meta b/Assets/VRM10/vrmlib/Runtime/Vrm/IAvatarPermission.cs.meta
new file mode 100644
index 000000000..d2629a7e9
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/IAvatarPermission.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 2254356479156e24b8da9a4310c126aa
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/IRedistributionLicense.cs b/Assets/VRM10/vrmlib/Runtime/Vrm/IRedistributionLicense.cs
new file mode 100644
index 000000000..0bfb223d7
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/IRedistributionLicense.cs
@@ -0,0 +1,60 @@
+using System;
+
+namespace VrmLib
+{
+    public enum DistributionLicenseType
+    {
+        Redistribution_Prohibited,
+        CC0,
+        CC_BY,
+        CC_BY_NC,
+        CC_BY_SA,
+        CC_BY_NC_SA,
+        CC_BY_ND,
+        CC_BY_NC_ND,
+        Other
+    }
+
+    public enum CreditNotationType
+    {
+        Required,
+        Unnecessary,
+        Abandoned,
+    }
+
+    public enum ModificationLicenseType
+    {
+        Prohibited,
+        Inherited,
+        NotInherited
+    }
+
+    public interface IRedistributionLicense
+    {
+        // 1.0 removed
+        DistributionLicenseType License { get; }
+
+        CreditNotationType CreditNotation { get; }
+        bool IsAllowRedistribution { get; }
+        string OtherLicenseUrl { get; }
+
+        ModificationLicenseType ModificationLicense { get; }
+    }
+
+    /// 1.0 向け
+    public class RedistributionLicense : IRedistributionLicense
+    {
+        public DistributionLicenseType License
+        {
+            get => DistributionLicenseType.Redistribution_Prohibited;
+        }
+
+        public CreditNotationType CreditNotation { get; set; }
+
+        public bool IsAllowRedistribution { get; set; }
+
+        public ModificationLicenseType ModificationLicense { get; set; }
+
+        public string OtherLicenseUrl { get; set; } = "";
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/IRedistributionLicense.cs.meta b/Assets/VRM10/vrmlib/Runtime/Vrm/IRedistributionLicense.cs.meta
new file mode 100644
index 000000000..21334d0ef
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/IRedistributionLicense.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 275466b758478f442a575a611fd92faf
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/LookAt.cs b/Assets/VRM10/vrmlib/Runtime/Vrm/LookAt.cs
new file mode 100644
index 000000000..1a0897e49
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/LookAt.cs
@@ -0,0 +1,41 @@
+
+
+using System.Numerics;
+
+namespace VrmLib
+{
+    public enum LookAtType
+    {
+        Bone,
+        Expression,
+    }
+
+    public class LookAtRangeMap
+    {
+        /// 
+        /// Yaw, Pitch 各を 0 ~ InputMaxValue で clamp する
+        /// 
+        public float InputMaxValue = 90.0f;
+
+        /// 
+        /// 0 ~ InputMaxValue を 0 ~ 1 に map してから乗算する
+        /// 
+        public float OutputScaling = 10.0f;
+
+        /// 4つでひとつのキー。最低8
+        public float[] Curve;
+    }
+
+    public class LookAt
+    {
+        public Vector3 OffsetFromHeadBone;
+
+        public LookAtType LookAtType;
+
+        public LookAtRangeMap HorizontalInner = new LookAtRangeMap();
+        public LookAtRangeMap HorizontalOuter = new LookAtRangeMap();
+        public LookAtRangeMap VerticalUp = new LookAtRangeMap();
+        public LookAtRangeMap VerticalDown = new LookAtRangeMap();
+
+    }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/LookAt.cs.meta b/Assets/VRM10/vrmlib/Runtime/Vrm/LookAt.cs.meta
new file mode 100644
index 000000000..394eaa7c0
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/LookAt.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 2023bcdc0e48f3a4398c6db4ef343de4
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/MToonMaterial.cs b/Assets/VRM10/vrmlib/Runtime/Vrm/MToonMaterial.cs
new file mode 100644
index 000000000..90ab95f21
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/MToonMaterial.cs
@@ -0,0 +1,84 @@
+using System;
+
+namespace VrmLib
+{
+    public class MToonMaterial : UnlitMaterial
+    {
+        public override LinearColor BaseColorFactor
+        {
+            get => Definition.Color.LitColor;
+            set => Definition.Color.LitColor = value;
+        }
+
+        public override TextureInfo BaseColorTexture
+        {
+            get => Definition.Color.LitMultiplyTexture;
+            set => Definition.Color.LitMultiplyTexture = value;
+        }
+
+        public override AlphaModeType AlphaMode
+        {
+            get
+            {
+                switch (Definition.Rendering.RenderMode)
+                {
+                    case MToon.RenderMode.Opaque: return AlphaModeType.OPAQUE;
+                    case MToon.RenderMode.Cutout: return AlphaModeType.MASK;
+                    case MToon.RenderMode.Transparent: return AlphaModeType.BLEND;
+                    case MToon.RenderMode.TransparentWithZWrite: return AlphaModeType.BLEND_ZWRITE;
+                    default: throw new NotImplementedException();
+                }
+            }
+            set
+            {
+                switch (value)
+                {
+                    case AlphaModeType.OPAQUE: Definition.Rendering.RenderMode = MToon.RenderMode.Opaque; break;
+                    case AlphaModeType.MASK: Definition.Rendering.RenderMode = MToon.RenderMode.Cutout; break;
+                    case AlphaModeType.BLEND: Definition.Rendering.RenderMode = MToon.RenderMode.Transparent; break;
+                    case AlphaModeType.BLEND_ZWRITE: Definition.Rendering.RenderMode = MToon.RenderMode.TransparentWithZWrite; break;
+                    default: throw new NotImplementedException();
+                }
+            }
+        }
+
+        public override float AlphaCutoff
+        {
+            get => Definition.Color.CutoutThresholdValue;
+            set => Definition.Color.CutoutThresholdValue = value;
+        }
+
+        public MToonMaterial(string name) : base(name)
+        {
+        }
+
+        public new const string ExtensionName = "VRMC_materials_mtoon";
+
+        public override string ToString()
+        {
+            return $"[MTOON]{Name}";
+        }
+
+        public float _DebugMode;
+        public float _SrcBlend;
+        public float _DstBlend;
+        public float _ZWrite;
+
+        public MToon.MToonDefinition Definition;
+
+        public override bool CanIntegrate(Material _rhs)
+        {
+            var rhs = _rhs as MToonMaterial;
+            if (rhs == null)
+            {
+                return false;
+            }
+
+            if (!Definition.Equals(rhs.Definition)) return false;
+
+            return true;
+        }
+
+        public const string MToonShaderName = "VRM/MToon";
+    }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/MToonMaterial.cs.meta b/Assets/VRM10/vrmlib/Runtime/Vrm/MToonMaterial.cs.meta
new file mode 100644
index 000000000..6cba80fcd
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/MToonMaterial.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3f7171f053cf12643bddfdba3afe9e80
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/MaterialBindType.cs b/Assets/VRM10/vrmlib/Runtime/Vrm/MaterialBindType.cs
new file mode 100644
index 000000000..9640c07be
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/MaterialBindType.cs
@@ -0,0 +1,21 @@
+namespace VrmLib
+{
+    public enum MaterialBindType
+    {
+        // /// float2: テクスチャーのUVの拡大率。UVでアクセスするテクスチャーすべてに適用される
+        // UvScale,
+        // /// float2: テクスチャーのUVのoffset。UVでアクセスするテクスチャーすべてに適用される
+        // UvOffset,
+
+        // float4: Unlit, PBR, MToon
+        Color,
+        /// float4: PBR, MToon
+        EmissionColor,
+        /// float4: MToon
+        ShadeColor,
+        /// float4: MToon
+        RimColor,
+        /// float4: MToon
+        OutlineColor,
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/MaterialBindType.cs.meta b/Assets/VRM10/vrmlib/Runtime/Vrm/MaterialBindType.cs.meta
new file mode 100644
index 000000000..efafffd64
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/MaterialBindType.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: c8ff16fe448a1ae4dbcb5bc28bc6efd5
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/MaterialBindTypeExtensions.cs b/Assets/VRM10/vrmlib/Runtime/Vrm/MaterialBindTypeExtensions.cs
new file mode 100644
index 000000000..a838f242f
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/MaterialBindTypeExtensions.cs
@@ -0,0 +1,229 @@
+using System;
+
+namespace VrmLib
+{
+    public static class MaterialBindTypeExtensions
+    {
+        public const string UV_PROPERTY = "_MainTex_ST";
+        public const string COLOR_PROPERTY = "_Color";
+        public const string EMISSION_COLOR_PROPERTY = "_EmissionColor";
+        public const string RIM_COLOR_PROPERTY = "_RimColor";
+        public const string OUTLINE_COLOR_PROPERTY = "_OutlineColor";
+        public const string SHADE_COLOR_PROPERTY = "_ShadeColor";
+
+        #region UnlitMaterial
+        static string _GetProperty(UnlitMaterial unlit, MaterialBindType bindType)
+        {
+            switch (bindType)
+            {
+                // case MaterialBindType.UvOffset:
+                // case MaterialBindType.UvScale:
+                //     return UV_PROPERTY;
+
+                case MaterialBindType.Color:
+                    return COLOR_PROPERTY;
+            }
+
+            throw new NotImplementedException();
+        }
+
+        static MaterialBindType _GetBindType(UnlitMaterial unlit, string property)
+        {
+            switch (property)
+            {
+                // case UV_PROPERTY:
+                //     return MaterialBindType.UvOffset;
+
+                case COLOR_PROPERTY:
+                    return MaterialBindType.Color;
+            }
+
+            throw new NotImplementedException();
+        }
+        #endregion
+
+        #region PBRMaterial
+        static string _GetProperty(PBRMaterial pbr, MaterialBindType bindType)
+        {
+            switch (bindType)
+            {
+                // case MaterialBindType.UvOffset:
+                // case MaterialBindType.UvScale:
+                //     return UV_PROPERTY;
+
+                case MaterialBindType.Color:
+                    return COLOR_PROPERTY;
+
+                case MaterialBindType.EmissionColor:
+                    return EMISSION_COLOR_PROPERTY;
+            }
+
+            throw new NotImplementedException();
+        }
+
+        static MaterialBindType _GetBindType(PBRMaterial pbr, string property)
+        {
+            switch (property)
+            {
+                // case UV_PROPERTY:
+                //     return MaterialBindType.UvOffset;
+
+                case COLOR_PROPERTY:
+                    return MaterialBindType.Color;
+
+                case EMISSION_COLOR_PROPERTY:
+                    return MaterialBindType.EmissionColor;
+            }
+
+            throw new NotImplementedException();
+        }
+        #endregion
+
+        #region MToon
+        static string _GetProperty(MToonMaterial mtoon, MaterialBindType bindType)
+        {
+            switch (bindType)
+            {
+                // case MaterialBindType.UvOffset:
+                // case MaterialBindType.UvScale:
+                //     return UV_PROPERTY;
+
+                case MaterialBindType.Color:
+                    return COLOR_PROPERTY;
+
+                case MaterialBindType.EmissionColor:
+                    return EMISSION_COLOR_PROPERTY;
+
+                case MaterialBindType.ShadeColor:
+                    return SHADE_COLOR_PROPERTY;
+
+                case MaterialBindType.RimColor:
+                    return RIM_COLOR_PROPERTY;
+
+                case MaterialBindType.OutlineColor:
+                    return OUTLINE_COLOR_PROPERTY;
+
+            }
+
+            throw new NotImplementedException();
+        }
+
+        static MaterialBindType _GetBindType(this MToonMaterial mtoon, string property)
+        {
+            switch (property)
+            {
+                // case UV_PROPERTY:
+                //     return MaterialBindType.UvOffset;
+
+                case COLOR_PROPERTY:
+                    return MaterialBindType.Color;
+
+                case EMISSION_COLOR_PROPERTY:
+                    return MaterialBindType.EmissionColor;
+
+                case RIM_COLOR_PROPERTY:
+                    return MaterialBindType.RimColor;
+
+                case SHADE_COLOR_PROPERTY:
+                    return MaterialBindType.ShadeColor;
+
+                case OUTLINE_COLOR_PROPERTY:
+                    return MaterialBindType.OutlineColor;
+            }
+
+            throw new NotImplementedException();
+        }
+        #endregion
+
+        public static string GetProperty(this MaterialBindType bindType, Material material)
+        {
+            if (material is MToonMaterial mtoon)
+            {
+                return _GetProperty(mtoon, bindType);
+            }
+
+            if (material is UnlitMaterial unlit)
+            {
+                return _GetProperty(unlit, bindType);
+            }
+
+            if (material is PBRMaterial pbr)
+            {
+                return _GetProperty(pbr, bindType);
+            }
+
+            throw new NotImplementedException();
+        }
+
+        public static MaterialBindType GetBindType(this Material material, string property)
+        {
+            if (material is MToonMaterial mtoon)
+            {
+                return _GetBindType(mtoon, property);
+            }
+
+            if (material is UnlitMaterial unlit)
+            {
+                return _GetBindType(unlit, property);
+            }
+
+            if (material is PBRMaterial pbr)
+            {
+                return _GetBindType(pbr, property);
+            }
+
+            throw new NotImplementedException();
+        }
+
+        public static string GetProperty(MaterialBindType bindType)
+        {
+            switch (bindType)
+            {
+                // case MaterialBindType.UvOffset:
+                // case MaterialBindType.UvScale:
+                //     return UV_PROPERTY;
+
+                case MaterialBindType.Color:
+                    return COLOR_PROPERTY;
+
+                case MaterialBindType.EmissionColor:
+                    return EMISSION_COLOR_PROPERTY;
+
+                case MaterialBindType.ShadeColor:
+                    return SHADE_COLOR_PROPERTY;
+
+                case MaterialBindType.RimColor:
+                    return RIM_COLOR_PROPERTY;
+
+                case MaterialBindType.OutlineColor:
+                    return OUTLINE_COLOR_PROPERTY;
+
+            }
+
+            throw new NotImplementedException();
+        }
+
+        public static MaterialBindType GetBindType(string property)
+        {
+            switch (property)
+            {
+                case COLOR_PROPERTY:
+                    return MaterialBindType.Color;
+
+                case EMISSION_COLOR_PROPERTY:
+                    return MaterialBindType.EmissionColor;
+
+                case RIM_COLOR_PROPERTY:
+                    return MaterialBindType.RimColor;
+
+                case SHADE_COLOR_PROPERTY:
+                    return MaterialBindType.ShadeColor;
+
+                case OUTLINE_COLOR_PROPERTY:
+                    return MaterialBindType.OutlineColor;
+            }
+
+            throw new NotImplementedException();
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/MaterialBindTypeExtensions.cs.meta b/Assets/VRM10/vrmlib/Runtime/Vrm/MaterialBindTypeExtensions.cs.meta
new file mode 100644
index 000000000..3178348dc
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/MaterialBindTypeExtensions.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a78413978cc956e4aac99418efc0d0f5
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/MaterialColorBind.cs b/Assets/VRM10/vrmlib/Runtime/Vrm/MaterialColorBind.cs
new file mode 100644
index 000000000..e9462c658
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/MaterialColorBind.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+
+namespace VrmLib
+{
+    public class MaterialColorBind
+    {
+        public readonly Material Material;
+
+        /// 
+        /// Material どのプロパティを変化させるのか
+        /// 
+        public readonly MaterialBindType BindType;
+
+        /// 
+        /// Unity仕様の Property名 + Vector4
+        ///        
+        public KeyValuePair Property
+        {
+            get
+            {
+                // switch (BindType)
+                // {
+                //     case MaterialBindType.UvScale:
+                //         return new KeyValuePair(
+                //             MaterialBindTypeExtensions.UV_PROPERTY,
+                //             new Vector4(m_value.X, m_value.Y, 0, 0)
+                //             );
+
+                //     case MaterialBindType.UvOffset:
+                //         return new KeyValuePair(
+                //             MaterialBindTypeExtensions.UV_PROPERTY,
+                //             new Vector4(1, 1, m_value.X, m_value.Y)
+                //             );
+                // }
+
+                return new KeyValuePair(BindType.GetProperty(Material), m_value);
+            }
+        }
+
+        readonly Vector4 m_value;
+
+        // public MaterialBindValue(Material material, String property, Vector4 value)
+        // {
+        //     Material = material;
+        //     BindType = material.GetBindType(property);
+        //     m_value = value;
+        // }
+
+        public MaterialColorBind(Material material, MaterialBindType bindType, Vector4 value)
+        {
+            Material = material;
+            BindType = bindType;
+            m_value = value;
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/MaterialColorBind.cs.meta b/Assets/VRM10/vrmlib/Runtime/Vrm/MaterialColorBind.cs.meta
new file mode 100644
index 000000000..1aea6187b
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/MaterialColorBind.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ddd6cccfb542ae640aaaa61a45ffa805
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/Meta.cs b/Assets/VRM10/vrmlib/Runtime/Vrm/Meta.cs
new file mode 100644
index 000000000..acc5d0837
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/Meta.cs
@@ -0,0 +1,55 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace VrmLib
+{
+    public class Meta
+    {
+        public string Name = "";
+
+        public string Version = "";
+
+        // 1.0 added
+        public string CopyrightInformation = "";
+
+        // 1.0 added
+        public List Authors = new List();
+
+        // backward compatibility
+        public string Author
+        {
+            get => Authors.FirstOrDefault();
+            set
+            {
+                Authors.Clear();
+                if (!string.IsNullOrEmpty(value))
+                {
+                    Authors.Add(value);
+                }
+            }
+        }
+
+        public string ContactInformation = "";
+
+        public List References = new List();
+
+        public string Reference
+        {
+            get => References.FirstOrDefault();
+            set
+            {
+                References.Clear();
+                if (!string.IsNullOrEmpty(value))
+                {
+                    References.Add(value);
+                }
+            }
+        }
+
+        public Image Thumbnail;
+
+        public IAvatarPermission AvatarPermission;
+
+        public IRedistributionLicense RedistributionLicense;
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/Meta.cs.meta b/Assets/VRM10/vrmlib/Runtime/Vrm/Meta.cs.meta
new file mode 100644
index 000000000..d82463d9e
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/Meta.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f885e15cc61535b4399979af17aeaa44
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/ModelExtensionsForNode.cs b/Assets/VRM10/vrmlib/Runtime/Vrm/ModelExtensionsForNode.cs
new file mode 100644
index 000000000..482f962fc
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/ModelExtensionsForNode.cs
@@ -0,0 +1,126 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+
+
+namespace VrmLib
+{
+    public static class ModelExtensionsForNode
+    {
+        class NodeUsage
+        {
+            public bool HasMesh;
+            public int WeightUsed;
+            public HumanoidBones? HumanBone;
+            public bool TreeHasHumanBone;
+
+            /// 
+            /// 子階層に消さずに残すBoneが含まれるか
+            /// 
+            public bool TreeHasUsedBone;
+
+            public bool SpringUse;
+
+            public override string ToString()
+            {
+                if (HumanBone.HasValue)
+                {
+                    return $"{HumanBone.Value}";
+                }
+                else
+                {
+                    return $"{Used}";
+                }
+            }
+
+            public bool Used
+            {
+                get
+                {
+                    if (HasMesh) return true;
+                    if (WeightUsed > 0) return true;
+                    if (HumanBone.HasValue && HumanBone.Value != HumanoidBones.unknown) return true;
+                    if (SpringUse) return true;
+                    if (TreeHasHumanBone) return true;
+                    if (TreeHasUsedBone) return true;
+                    return false;
+                }
+            }
+        }
+
+        public static IEnumerable GetRemoveNodes(this Model model)
+        {
+            var nodeUsage = model.Nodes.Select(x =>
+                new NodeUsage
+                {
+                    HasMesh = x.MeshGroup != null,
+                    HumanBone = x.HumanoidBone,
+                    TreeHasHumanBone = x.Traverse().Any(y => y.HumanoidBone.HasValue),
+                })
+                .ToArray();
+
+            var bones = model.Nodes.Where(x => x.HumanoidBone.HasValue).ToArray();
+
+            // joint use
+            foreach (var meshGroup in model.MeshGroups)
+            {
+                var skin = meshGroup.Skin;
+                if (skin != null)
+                {
+                    foreach (var mesh in meshGroup.Meshes)
+                    {
+                        var joints = mesh.VertexBuffer.Joints;
+                        var weights = mesh.VertexBuffer.Weights;
+                        if (joints != null && weights != null)
+                        {
+                            var jointsSpan = SpanLike.Wrap(joints.Bytes);
+                            var weightsSpan = SpanLike.Wrap(weights.Bytes);
+                            for (int i = 0; i < jointsSpan.Length; ++i)
+                            {
+                                var w = weightsSpan[i];
+                                var j = jointsSpan[i];
+                                if (w.X > 0) nodeUsage[model.Nodes.IndexOf(skin.Joints[j.Joint0])].WeightUsed++;
+                                if (w.Y > 0) nodeUsage[model.Nodes.IndexOf(skin.Joints[j.Joint1])].WeightUsed++;
+                                if (w.Z > 0) nodeUsage[model.Nodes.IndexOf(skin.Joints[j.Joint2])].WeightUsed++;
+                                if (w.W > 0) nodeUsage[model.Nodes.IndexOf(skin.Joints[j.Joint3])].WeightUsed++;
+                            }
+                        }
+                    }
+                }
+            }
+
+            // 削除されるNodeのうち、子階層に1つでも残るNodeがあればそのNodeも残す
+            for (int i = 0; i < nodeUsage.Length; i++)
+            {
+                if (nodeUsage[i].Used) continue;
+
+                var children = model.Nodes[i].Traverse();
+
+                nodeUsage[i].TreeHasUsedBone = children.Where(x => nodeUsage[model.Nodes.IndexOf(x)].Used).Any();
+            }
+
+
+            var spring = model.Vrm?.SpringBone;
+            if (spring != null)
+            {
+                foreach (var x in spring.Springs)
+                {
+                    foreach (var y in x.Bones)
+                    {
+                        nodeUsage[model.Nodes.IndexOf(y)].SpringUse = true;
+                    }
+                }
+                foreach (var x in spring.Colliders)
+                {
+                    nodeUsage[model.Nodes.IndexOf(x.Node)].SpringUse = true;
+                }
+            }
+
+            var nodes = nodeUsage.Select((x, i) => (i, x))
+                .Where(x => !x.Item2.Used)
+                .Select(x => model.Nodes[x.Item1])
+                .ToArray();
+            return nodes;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/ModelExtensionsForNode.cs.meta b/Assets/VRM10/vrmlib/Runtime/Vrm/ModelExtensionsForNode.cs.meta
new file mode 100644
index 000000000..af76e43ab
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/ModelExtensionsForNode.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f2ab63c74d9ba6444aedc57776a8a1c2
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/MorphTargetBind.cs b/Assets/VRM10/vrmlib/Runtime/Vrm/MorphTargetBind.cs
new file mode 100644
index 000000000..bbca8c022
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/MorphTargetBind.cs
@@ -0,0 +1,27 @@
+namespace VrmLib
+{
+    public class MorphTargetBind
+    {
+        /// 
+        /// 対象のMesh(Renderer)
+        /// 
+        public Node Node;
+
+        /// 
+        /// MorphTarget の name
+        /// 
+        public readonly string Name;
+
+        /// 
+        /// MorphTarget の適用度
+        /// 
+        public readonly float Value;
+
+        public MorphTargetBind(Node node, string name, float value)
+        {
+            Node = node;
+            Name = name;
+            Value = value;
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/MorphTargetBind.cs.meta b/Assets/VRM10/vrmlib/Runtime/Vrm/MorphTargetBind.cs.meta
new file mode 100644
index 000000000..0eb1d2c1d
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/MorphTargetBind.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a9c5b4a0882f60e45b9ba9dea21b51b4
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/SpringBoneManager.cs b/Assets/VRM10/vrmlib/Runtime/Vrm/SpringBoneManager.cs
new file mode 100644
index 000000000..f0afb1e52
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/SpringBoneManager.cs
@@ -0,0 +1,78 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+
+namespace VrmLib
+{
+    public enum VrmSpringBoneColliderTypes
+    {
+        Sphere,
+        Capsule,
+    }
+
+    public struct VrmSpringBoneCollider
+    {
+        public readonly VrmSpringBoneColliderTypes ColliderType;
+        public readonly Vector3 Offset;
+        public readonly float Radius;
+        public readonly Vector3 CapsuleTail;
+
+        VrmSpringBoneCollider(VrmSpringBoneColliderTypes type, Vector3 offset, float radius, Vector3 tail)
+        {
+            ColliderType = type;
+            Offset = offset;
+            Radius = radius;
+            CapsuleTail = tail;
+        }
+
+        public static VrmSpringBoneCollider CreateSphere(Vector3 offset, float radius)
+        {
+            return new VrmSpringBoneCollider(VrmSpringBoneColliderTypes.Sphere, offset, radius, Vector3.Zero);
+        }
+
+        public static VrmSpringBoneCollider CreateCapsule(Vector3 offset, float radius, Vector3 tail)
+        {
+            return new VrmSpringBoneCollider(VrmSpringBoneColliderTypes.Capsule, offset, radius, tail);
+        }
+    }
+
+    public class SpringBoneColliderGroup
+    {
+        public readonly Node Node;
+
+        public readonly List Colliders;
+
+        public SpringBoneColliderGroup(Node node, IEnumerable colliders)
+        {
+            Node = node;
+            Colliders = colliders.ToList();
+        }
+    }
+
+    public class SpringBone
+    {
+        public const string ExtensionName = "VRMC_springBone";
+        public readonly List Bones = new List();
+        public Node Origin;
+
+        public readonly List Colliders = new List();
+
+        public string Comment = "";
+
+        public float DragForce;
+
+        public Vector3 GravityDir;
+
+        public float GravityPower;
+
+        public float HitRadius;
+
+        public float Stiffness;
+    }
+
+    public class SpringBoneManager
+    {
+        public readonly List Springs = new List();
+        public readonly List Colliders = new List();
+    }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/SpringBoneManager.cs.meta b/Assets/VRM10/vrmlib/Runtime/Vrm/SpringBoneManager.cs.meta
new file mode 100644
index 000000000..b7414c79b
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/SpringBoneManager.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 2735f48f8080886478a861cca79c14d9
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/TextureTransformBind.cs b/Assets/VRM10/vrmlib/Runtime/Vrm/TextureTransformBind.cs
new file mode 100644
index 000000000..c7b2358c2
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/TextureTransformBind.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Numerics;
+
+namespace VrmLib
+{
+    public class TextureTransformBind : IEquatable
+    {
+        public readonly Material Material;
+        public readonly Vector2 Scale; // default = [1, 1]
+        public readonly Vector2 Offset; // default = [0, 0]
+
+        public TextureTransformBind(Material material, Vector2 scale, Vector2 offset)
+        {
+            Material = material;
+            Scale = scale;
+            Offset = offset;
+        }
+
+        public override int GetHashCode()
+        {
+            return Material.GetHashCode();
+        }
+
+        public bool Equals(TextureTransformBind other)
+        {
+            return Material == other.Material && Scale == other.Scale && Offset == other.Offset;
+        }
+
+        /// 
+        /// Scaleは平均。Offsetは足す
+        /// 
+        /// 
+        /// 
+        public TextureTransformBind Merge(TextureTransformBind rhs)
+        {
+            if (Material != rhs.Material)
+            {
+                throw new System.Exception();
+            }
+            return new TextureTransformBind(Material, (Scale + rhs.Scale) / 2, Offset + rhs.Offset);
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/TextureTransformBind.cs.meta b/Assets/VRM10/vrmlib/Runtime/Vrm/TextureTransformBind.cs.meta
new file mode 100644
index 000000000..5d6107e3d
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/TextureTransformBind.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d69e0550fa5272248ac04fb52bef427f
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/Vrm.cs b/Assets/VRM10/vrmlib/Runtime/Vrm/Vrm.cs
new file mode 100644
index 000000000..c2b02dbc2
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/Vrm.cs
@@ -0,0 +1,30 @@
+namespace VrmLib
+{
+    public class Vrm
+    {
+        public Meta Meta = new Meta();
+
+        public string ExporterVersion;
+        public string SpecVersion;
+
+        public Vrm(Meta meta, string exporterVersion, string specVersion)
+        {
+            Meta = meta;
+            ExporterVersion = exporterVersion;
+            SpecVersion = specVersion;
+        }
+
+        public override string ToString()
+        {
+            return $"{Meta}";
+        }
+
+        public ExpressionManager ExpressionManager = new ExpressionManager();
+
+        public SpringBoneManager SpringBone = new SpringBoneManager();
+
+        public FirstPerson FirstPerson = new FirstPerson();
+
+        public LookAt LookAt = new LookAt();
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Runtime/Vrm/Vrm.cs.meta b/Assets/VRM10/vrmlib/Runtime/Vrm/Vrm.cs.meta
new file mode 100644
index 000000000..f5e682de4
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/Vrm/Vrm.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 2f9ba743e96ecdc47abdc36b799ad539
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Runtime/VrmLib.asmdef b/Assets/VRM10/vrmlib/Runtime/VrmLib.asmdef
new file mode 100644
index 000000000..d409f1b93
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/VrmLib.asmdef
@@ -0,0 +1,12 @@
+{
+    "name": "VrmLib",
+    "references": [],
+    "optionalUnityReferences": [],
+    "includePlatforms": [],
+    "excludePlatforms": [],
+    "allowUnsafeCode": false,
+    "overrideReferences": false,
+    "precompiledReferences": [],
+    "autoReferenced": true,
+    "defineConstraints": []
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Runtime/VrmLib.asmdef.meta b/Assets/VRM10/vrmlib/Runtime/VrmLib.asmdef.meta
new file mode 100644
index 000000000..963943ae8
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Runtime/VrmLib.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 2ef84b520212e174a94668c7a0862d3b
+AssemblyDefinitionImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Tests.meta b/Assets/VRM10/vrmlib/Tests.meta
new file mode 100644
index 000000000..415161beb
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Tests.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 3a50a5aa0ee336c4eaa4c0bae409974b
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Tests/BvhTests.cs b/Assets/VRM10/vrmlib/Tests/BvhTests.cs
new file mode 100644
index 000000000..46b135893
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Tests/BvhTests.cs
@@ -0,0 +1,161 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Text;
+using NUnit.Framework;
+using VrmLib;
+using VrmLib.Bvh;
+
+namespace VrmLibTests
+{
+    public class BvhTests
+    {
+        DirectoryInfo RootPath
+        {
+            get
+            {
+                return new FileInfo(GetType().Assembly.Location).Directory.Parent.Parent;
+            }
+        }
+
+        [Test]
+        [TestCase("Assets/StreamingAssets/VRM.Samples/Motions/test.txt")]
+        public void BvhTest(string filename)
+        {
+            var path = Path.Combine(RootPath.FullName, filename);
+            var text = File.ReadAllText(path, Encoding.UTF8);
+            var bvh = BvhParser.Parse(text);
+            Assert.AreEqual(4007, bvh.FrameCount);
+
+            var model = ModelExtensionsForBvh.Load(Path.GetFileName(path), bvh);
+        }
+
+        [Test]
+        [TestCase("Assets/StreamingAssets/VRM.Samples/Motions/test.txt")]
+        public void SkeletonEstimatorTest(string filename)
+        {
+            var path = Path.Combine(RootPath.FullName, filename);
+            var text = File.ReadAllText(path, Encoding.UTF8);
+            var bvh = BvhParser.Parse(text);
+
+            var model = ModelExtensionsForBvh.CreateFromBvh(bvh.Root);
+
+            var estimated = SkeletonEstimator.Detect(model.Root);
+            Assert.AreEqual(estimated[HumanoidBones.hips].Name, "Hips");
+            Assert.AreEqual(estimated[HumanoidBones.spine].Name, "Spine");
+            Assert.AreEqual(estimated[HumanoidBones.chest].Name, "Spine1");
+            Assert.AreEqual(estimated[HumanoidBones.neck].Name, "Neck");
+            Assert.AreEqual(estimated[HumanoidBones.head].Name, "Head");
+            Assert.AreEqual(estimated[HumanoidBones.leftShoulder].Name, "LeftShoulder");
+            Assert.AreEqual(estimated[HumanoidBones.leftUpperArm].Name, "LeftArm");
+            Assert.AreEqual(estimated[HumanoidBones.leftLowerArm].Name, "LeftForeArm");
+            Assert.AreEqual(estimated[HumanoidBones.leftHand].Name, "LeftHand");
+            Assert.AreEqual(estimated[HumanoidBones.rightShoulder].Name, "RightShoulder");
+            Assert.AreEqual(estimated[HumanoidBones.rightUpperArm].Name, "RightArm");
+            Assert.AreEqual(estimated[HumanoidBones.rightLowerArm].Name, "RightForeArm");
+            Assert.AreEqual(estimated[HumanoidBones.rightHand].Name, "RightHand");
+            Assert.AreEqual(estimated[HumanoidBones.leftUpperLeg].Name, "LeftUpLeg");
+            Assert.AreEqual(estimated[HumanoidBones.leftLowerLeg].Name, "LeftLeg");
+            Assert.AreEqual(estimated[HumanoidBones.leftFoot].Name, "LeftFoot");
+            Assert.AreEqual(estimated[HumanoidBones.leftToes].Name, "LeftToeBase");
+            Assert.AreEqual(estimated[HumanoidBones.rightUpperLeg].Name, "RightUpLeg");
+            Assert.AreEqual(estimated[HumanoidBones.rightLowerLeg].Name, "RightLeg");
+            Assert.AreEqual(estimated[HumanoidBones.rightFoot].Name, "RightFoot");
+            Assert.AreEqual(estimated[HumanoidBones.rightToes].Name, "RightToeBase");
+        }
+
+        DirectoryInfo TestModelPath
+        {
+            get
+            {
+                var env = Environment.GetEnvironmentVariable("VRM_TEST_MODELS");
+                return new DirectoryInfo(env);
+            }
+        }
+
+        [Test]
+        [TestCase("Motions/bvh/liveanimation/la_bvh_sample00.bvh")]
+        [TestCase("Motions/bvh/liveanimation/la_bvh_sample01.bvh")]
+        [TestCase("Motions/bvh/liveanimation/la_bvh_sample02.bvh")]
+        [TestCase("Motions/bvh/liveanimation/la_bvh_sample03.bvh")]
+        [TestCase("Motions/bvh/liveanimation/la_bvh_sample04.bvh")]
+        [TestCase("Motions/bvh/liveanimation/la_bvh_sample05.bvh")]
+        [TestCase("Motions/bvh/liveanimation/la_bvh_sample06.bvh")]
+        [TestCase("Motions/bvh/liveanimation/la_bvh_sample07.bvh")]
+        [TestCase("Motions/bvh/liveanimation/la_bvh_sample08.bvh")]
+        [TestCase("Motions/bvh/liveanimation/la_bvh_sample09.bvh")]
+        [TestCase("Motions/bvh/liveanimation/la_bvh_sample10.bvh")]
+        [TestCase("Motions/bvh/liveanimation/la_bvh_sample11.bvh")]
+        [TestCase("Motions/bvh/liveanimation/la_bvh_sample12.bvh")]
+        public void SkeletonEstimatorTestLA(string filename)
+        {
+            var path = Path.Combine(TestModelPath.FullName, filename);
+            var text = File.ReadAllText(path, Encoding.UTF8);
+            var bvh = BvhParser.Parse(text);
+            var model = ModelExtensionsForBvh.CreateFromBvh(bvh.Root);
+            var estimated = SkeletonEstimator.Detect(model.Root);
+            Assert.AreEqual(estimated[HumanoidBones.hips].Name, "Hips");
+            Assert.AreEqual(estimated[HumanoidBones.spine].Name, "Chest");
+            Assert.AreEqual(estimated[HumanoidBones.chest].Name, "Chest2");
+            Assert.AreEqual(estimated[HumanoidBones.neck].Name, "Neck");
+            Assert.AreEqual(estimated[HumanoidBones.head].Name, "Head");
+            Assert.AreEqual(estimated[HumanoidBones.leftShoulder].Name, "LeftCollar");
+            Assert.AreEqual(estimated[HumanoidBones.leftUpperArm].Name, "LeftShoulder");
+            Assert.AreEqual(estimated[HumanoidBones.leftLowerArm].Name, "LeftElbow");
+            Assert.AreEqual(estimated[HumanoidBones.leftHand].Name, "LeftWrist");
+            Assert.AreEqual(estimated[HumanoidBones.rightShoulder].Name, "RightCollar");
+            Assert.AreEqual(estimated[HumanoidBones.rightUpperArm].Name, "RightShoulder");
+            Assert.AreEqual(estimated[HumanoidBones.rightLowerArm].Name, "RightElbow");
+            Assert.AreEqual(estimated[HumanoidBones.rightHand].Name, "RightWrist");
+            Assert.AreEqual(estimated[HumanoidBones.leftUpperLeg].Name, "LeftHip");
+            Assert.AreEqual(estimated[HumanoidBones.leftLowerLeg].Name, "LeftKnee");
+            Assert.AreEqual(estimated[HumanoidBones.leftFoot].Name, "LeftAnkle");
+            Assert.AreEqual(estimated[HumanoidBones.rightUpperLeg].Name, "RightHip");
+            Assert.AreEqual(estimated[HumanoidBones.rightLowerLeg].Name, "RightKnee");
+            Assert.AreEqual(estimated[HumanoidBones.rightFoot].Name, "RightAnkle");
+        }
+
+        [Test]
+        [TestCase("Motions/bvh/accad/eric1.bvh")]
+        [TestCase("Motions/bvh/accad/ericdog.bvh")]
+        [TestCase("Motions/bvh/accad/ericrun.bvh")]
+        [TestCase("Motions/bvh/accad/flip.bvh")]
+        [TestCase("Motions/bvh/accad/swagger.bvh")]
+        public void SkeletonEstimatorTestAccad(string filename)
+        {
+            var path = Path.Combine(TestModelPath.FullName, filename);
+            var text = File.ReadAllText(path, Encoding.UTF8);
+            var bvh = BvhParser.Parse(text);
+            var bones = bvh.Root.Traverse().ToArray();
+            foreach (var bone in bones)
+            {
+                Console.WriteLine(bone.Name);
+            }
+            var model = ModelExtensionsForBvh.CreateFromBvh(bvh.Root);
+            var estimated = SkeletonEstimator.Detect(model.Root);
+
+            Assert.AreEqual(estimated[HumanoidBones.hips].Name, "root");
+            Assert.AreEqual(estimated[HumanoidBones.spine].Name, "lowerback");
+            Assert.AreEqual(estimated[HumanoidBones.chest].Name, "upperback");
+            Assert.AreEqual(estimated[HumanoidBones.upperChest].Name, "thorax");
+            Assert.AreEqual(estimated[HumanoidBones.neck].Name, "neck");
+            Assert.AreEqual(estimated[HumanoidBones.head].Name, "head");
+            Assert.AreEqual(estimated[HumanoidBones.leftShoulder].Name, "lshoulderjoint");
+            Assert.AreEqual(estimated[HumanoidBones.leftUpperArm].Name, "lhumerus");
+            Assert.AreEqual(estimated[HumanoidBones.leftLowerArm].Name, "lradius");
+            Assert.AreEqual(estimated[HumanoidBones.leftHand].Name, "lhand");
+            Assert.AreEqual(estimated[HumanoidBones.rightShoulder].Name, "rshoulderjoint");
+            Assert.AreEqual(estimated[HumanoidBones.rightUpperArm].Name, "rhumerus");
+            Assert.AreEqual(estimated[HumanoidBones.rightLowerArm].Name, "rradius");
+            Assert.AreEqual(estimated[HumanoidBones.rightHand].Name, "rhand");
+            Assert.AreEqual(estimated[HumanoidBones.rightUpperLeg].Name, "rfemur");
+            Assert.AreEqual(estimated[HumanoidBones.rightLowerLeg].Name, "rtibia");
+            Assert.AreEqual(estimated[HumanoidBones.rightFoot].Name, "rfoot");
+            Assert.AreEqual(estimated[HumanoidBones.rightToes].Name, "rtoes");
+            Assert.AreEqual(estimated[HumanoidBones.leftUpperLeg].Name, "lfemur");
+            Assert.AreEqual(estimated[HumanoidBones.leftLowerLeg].Name, "ltibia");
+            Assert.AreEqual(estimated[HumanoidBones.leftFoot].Name, "lfoot");
+            Assert.AreEqual(estimated[HumanoidBones.leftToes].Name, "ltoes");
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Tests/BvhTests.cs.meta b/Assets/VRM10/vrmlib/Tests/BvhTests.cs.meta
new file mode 100644
index 000000000..49c5aa4ad
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Tests/BvhTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: c6a1d4e939982f84b9f3d94de009eb0e
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Tests/HumanoidTests.cs b/Assets/VRM10/vrmlib/Tests/HumanoidTests.cs
new file mode 100644
index 000000000..ce88850ab
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Tests/HumanoidTests.cs
@@ -0,0 +1,28 @@
+using System;
+using System.IO;
+using VrmLib;
+using NUnit.Framework;
+
+namespace VrmLibTests
+{
+    public class HumanoidTests
+    {
+        DirectoryInfo RootPath
+        {
+            get
+            {
+                return new FileInfo(GetType().Assembly.Location).Directory.Parent.Parent.Parent.Parent;
+            }
+        }
+
+        [Test]
+        [TestCase("Assets/UniVrm.bvh")]
+        public void HumanoidTest(string filename)
+        {
+            var humanoid = new Humanoid();
+            Assert.False(humanoid.HasRequiredBones);
+
+            Assert.Throws(() => humanoid.Add(HumanoidBones.unknown, null));
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Tests/HumanoidTests.cs.meta b/Assets/VRM10/vrmlib/Tests/HumanoidTests.cs.meta
new file mode 100644
index 000000000..2aef96a30
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Tests/HumanoidTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d23637d237cf4f947a623b9af659d1c8
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Tests/ModelTests.cs b/Assets/VRM10/vrmlib/Tests/ModelTests.cs
new file mode 100644
index 000000000..dc64c4570
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Tests/ModelTests.cs
@@ -0,0 +1,23 @@
+using System.Numerics;
+using VrmLib;
+using NUnit.Framework;
+
+namespace VrmLibTests
+{
+    public class ModelTests
+    {
+        [Test]
+        public void NodeTest()
+        {
+            var root = new Node("root");
+            var child = new Node("child")
+            {
+                LocalTranslation = Vector3.UnitX
+            };
+            root.Add(child);
+            root.LocalTranslation = Vector3.UnitX;
+
+            Assert.AreEqual(new Vector3(2, 0, 0), child.Translation);
+        }
+    }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Tests/ModelTests.cs.meta b/Assets/VRM10/vrmlib/Tests/ModelTests.cs.meta
new file mode 100644
index 000000000..0d69b8900
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Tests/ModelTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: fbfe2caf92208854ba9da33cc7868ae8
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Tests/ModifierTests.cs b/Assets/VRM10/vrmlib/Tests/ModifierTests.cs
new file mode 100644
index 000000000..a4a390b2a
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Tests/ModifierTests.cs
@@ -0,0 +1,35 @@
+using System.Numerics;
+using VrmLib;
+using NUnit.Framework;
+using System.IO;
+
+namespace VrmLibTests
+{
+    public class ModelModifierTests
+    {
+        [Test]
+        public void ModifierTest()
+        {
+            var model = new Model(Coordinates.Gltf);
+            var modifier = new ModelModifier(model);
+
+            var node0 = new Node("node0");
+            modifier.NodeAdd(node0);
+            Assert.AreEqual(1, model.Nodes.Count);
+
+            var node1 = new Node("node1");
+            modifier.NodeAdd(node1, node0);
+            Assert.AreEqual(2, model.Nodes.Count);
+
+            var node2 = new Node("node2");
+            modifier.NodeReplace(node0, node2);
+            Assert.AreEqual(2, model.Nodes.Count);
+            Assert.AreEqual(node2, model.Nodes[1]);
+            Assert.AreEqual(1, node2.Children.Count);
+
+            modifier.NodeRemove(node1);
+            Assert.AreEqual(1, model.Nodes.Count);
+            Assert.AreEqual(0, node2.Children.Count);
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Tests/ModifierTests.cs.meta b/Assets/VRM10/vrmlib/Tests/ModifierTests.cs.meta
new file mode 100644
index 000000000..6e1a5ecda
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Tests/ModifierTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a20bf106ed875aa4f9d5cf91ad104d8f
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Tests/NumericTests.cs b/Assets/VRM10/vrmlib/Tests/NumericTests.cs
new file mode 100644
index 000000000..6c1751c15
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Tests/NumericTests.cs
@@ -0,0 +1,113 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Numerics;
+using VrmLib;
+using NUnit.Framework;
+
+namespace VrmLibTests
+{
+    public class NumericTests
+    {
+        [Test]
+        [Category("Numerics")]
+        public void QuaternionTest()
+        {
+            var orgAxis = Vector3.Normalize(new Vector3(1, 2, 3));
+            var orgAngle = 90 * NumericsExtensions.TO_RAD;
+            var q = Quaternion.CreateFromAxisAngle(orgAxis, orgAngle);
+            var (axis, angle) = q.GetAxisAngle();
+            Assert.AreEqual(orgAxis, axis);
+            Assert.AreEqual(orgAngle, angle);
+        }
+
+        [Test]
+        [Category("Numerics")]
+        public void Vector4Test()
+        {
+            var bytes = new byte[4 * 4];
+            {
+                var span = SpanLike.Wrap(new ArraySegment(bytes));
+                span[0] = 1.0f;
+            }
+
+            {
+                var span = SpanLike.Wrap(new ArraySegment(bytes));
+                Assert.AreEqual(1.0f, span[0].X);
+            }
+
+            {
+                var span = SpanLike.Wrap(new ArraySegment(bytes));
+                Assert.AreEqual(1.0f, span[0].X);
+            }
+        }
+
+        [Test]
+        [Category("Numerics")]
+        public void Vector3Test()
+        {
+            var v = new Vector3(1, 2, 3);
+            var r = v.ReverseZ();
+            Assert.AreEqual(new Vector3(1, 2, -3), r);
+        }
+
+        [Test]
+        [Category("Numerics")]
+        public void UVTest()
+        {
+            var v = new Vector2(0.1f, 0.2f);
+            var r = v.UVVerticalFlip();
+            Assert.AreEqual(new Vector2(0.1f, 0.8f), r);
+        }
+
+        [Test]
+        [Category("Numerics")]
+        public void MatrixTranslationTest()
+        {
+            var t = new Vector3(1, 2, 3);
+            var m = Matrix4x4.CreateTranslation(t);
+            var ex = m.ExtractPosition();
+            Assert.AreEqual(t, ex);
+        }
+
+        [Test]
+        [Category("Numerics")]
+        public void MatrixRotationTest()
+        {
+            var orgAxis = Vector3.Normalize(new Vector3(1, 2, 3));
+            var orgAngle = 90 * NumericsExtensions.TO_RAD;
+            var q = Quaternion.CreateFromAxisAngle(orgAxis, orgAngle);
+            var m = Matrix4x4.CreateFromQuaternion(q);
+            var ex = m.ExtractRotation();
+            var (axis, angle) = ex.GetAxisAngle();
+            Assert.True(orgAxis.NearlyEqual(axis));
+            Assert.True(orgAngle.NearlyEqual(angle));
+            Assert.True(q.NearlyEqual(ex));
+        }
+
+        [Test]
+        [Category("Numerics")]
+        public void MatrixScaleTest()
+        {
+            var s = new Vector3(2, 3, 4);
+            var m = Matrix4x4.CreateScale(s);
+            var ex = m.ExtractScale();
+            Assert.AreEqual(s, ex);
+        }
+
+        [Test]
+        [Category("Numerics")]
+        public void MatrixTest()
+        {
+            var t = new Vector3(1, 2, 3);
+            var orgAxis = Vector3.Normalize(new Vector3(1, 2, 3));
+            var orgAngle = 90 * NumericsExtensions.TO_RAD;
+            var r = Quaternion.CreateFromAxisAngle(orgAxis, orgAngle);
+            var s = new Vector3(2, 3, 4);
+            var m = NumericsExtensions.FromTRS(t, r, s);
+            var (tt, rr, ss) = m.Decompose();
+            Assert.True(s.NearlyEqual(ss));
+            Assert.True(r.NearlyEqual(rr));
+            Assert.AreEqual(t, tt);
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Tests/NumericTests.cs.meta b/Assets/VRM10/vrmlib/Tests/NumericTests.cs.meta
new file mode 100644
index 000000000..af4384627
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Tests/NumericTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 534d323846a2b9949b4bb43ae75ab8f8
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Tests/SpanLikeTests.cs b/Assets/VRM10/vrmlib/Tests/SpanLikeTests.cs
new file mode 100644
index 000000000..a022e474e
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Tests/SpanLikeTests.cs
@@ -0,0 +1,42 @@
+using System;
+using VrmLib;
+using NUnit.Framework;
+
+namespace VrmLibTests
+{
+    public class SpanLikeTests
+    {
+        [Test]
+        [Category("SpanLike")]
+        public void CopyTest()
+        {
+            var src = new int[] { 255 };
+            var dst = new byte[4];
+
+            src.ToBytes(new ArraySegment(dst));
+
+            Assert.AreEqual(0xFF, dst[0]);
+            Assert.AreEqual(0, dst[1]);
+            Assert.AreEqual(0, dst[2]);
+            Assert.AreEqual(0, dst[3]);
+        }
+
+        [Test]
+        [Category("SpanLike")]
+        public void SpanLikeTest()
+        {
+            var v0 = new UnityEngine.Vector3(1, 2, 3);
+            var v1 = new UnityEngine.Vector3(4, 5, 6);
+            var positions = new UnityEngine.Vector3[]
+            {
+                v0,
+                v1,
+            };
+            var span = VrmLib.SpanLike.CopyFrom(positions);
+
+            Assert.AreEqual(2, span.Length);
+            Assert.AreEqual(v0, span[0]);
+            Assert.AreEqual(v1, span[1]);
+        }
+    }
+}
diff --git a/Assets/VRM10/vrmlib/Tests/SpanLikeTests.cs.meta b/Assets/VRM10/vrmlib/Tests/SpanLikeTests.cs.meta
new file mode 100644
index 000000000..7b5be7c9d
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Tests/SpanLikeTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 212282563b23d744dbd55e78db0f91f5
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Tests/VrmLib.Tests.asmdef b/Assets/VRM10/vrmlib/Tests/VrmLib.Tests.asmdef
new file mode 100644
index 000000000..f097d463c
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Tests/VrmLib.Tests.asmdef
@@ -0,0 +1,20 @@
+{
+    "name": "VrmLibTests",
+    "references": [
+        "VrmLib"
+    ],
+    "optionalUnityReferences": [
+        "TestAssemblies"
+    ],
+    "includePlatforms": [
+        "Editor"
+    ],
+    "excludePlatforms": [],
+    "allowUnsafeCode": false,
+    "overrideReferences": true,
+    "precompiledReferences": [],
+    "autoReferenced": false,
+    "defineConstraints": [
+        "UNITY_INCLUDE_TESTS"
+    ]
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Tests/VrmLib.Tests.asmdef.meta b/Assets/VRM10/vrmlib/Tests/VrmLib.Tests.asmdef.meta
new file mode 100644
index 000000000..2e07b9619
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Tests/VrmLib.Tests.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 399ad757077e97e4dbe45efbfa5c24c0
+AssemblyDefinitionImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/VRM10/vrmlib/Tests/package.json b/Assets/VRM10/vrmlib/Tests/package.json
new file mode 100644
index 000000000..fad6bcaec
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Tests/package.json
@@ -0,0 +1,8 @@
+{
+    "name": "com.vrmc.univrm",
+    "displayName": "UniVRM",
+    "version": "1.0.0",
+    "unity": "2019.3",
+    "description": "vrm unity utility",
+    "dependencies": {}
+}
\ No newline at end of file
diff --git a/Assets/VRM10/vrmlib/Tests/package.json.meta b/Assets/VRM10/vrmlib/Tests/package.json.meta
new file mode 100644
index 000000000..980281b5f
--- /dev/null
+++ b/Assets/VRM10/vrmlib/Tests/package.json.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 88fe227afce081945a98a3106d0d597a
+TextScriptImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: