diff --git a/Scripts/BlendShape/BlendShapeKey.cs b/Scripts/BlendShape/BlendShapeKey.cs index 67c34a404..0fda3b811 100644 --- a/Scripts/BlendShape/BlendShapeKey.cs +++ b/Scripts/BlendShape/BlendShapeKey.cs @@ -80,6 +80,10 @@ namespace VRM public static BlendShapeKey CreateFrom(BlendShapeClip clip) { + if (clip == null) + { + return default(BlendShapeKey); + } return new BlendShapeKey(clip.BlendShapeName, clip.Preset); } diff --git a/Scripts/BlendShape/BlendShapeMerger.cs b/Scripts/BlendShape/BlendShapeMerger.cs index 2e41939de..5e8cb4c24 100644 --- a/Scripts/BlendShape/BlendShapeMerger.cs +++ b/Scripts/BlendShape/BlendShapeMerger.cs @@ -74,19 +74,48 @@ namespace VRM } } - foreach(var binding in kv.Value.MaterialValues) + foreach (var binding in kv.Value.MaterialValues) { if (!m_materialSetterMap.ContainsKey(binding)) { Material target; - if(m_materialMap.TryGetValue(binding.MaterialName, out target)) + if (m_materialMap.TryGetValue(binding.MaterialName, out target)) { - m_materialSetterMap.Add(binding, x => + if (binding.ValueName.EndsWith("_ST_S")) + { + var valueName = binding.ValueName.Substring(0, binding.ValueName.Length - 2); + Action setter = value => + { + var propValue = binding.BaseValue + (binding.TargetValue - binding.BaseValue) * value; + var src = target.GetVector(valueName); + src.x = propValue.x; // horizontal only + src.z = propValue.z; // horizontal only + target.SetVector(valueName, src); + }; + m_materialSetterMap.Add(binding, setter); + } + else if(binding.ValueName.EndsWith("_ST_T")) + { + var valueName = binding.ValueName.Substring(0, binding.ValueName.Length - 2); + Action setter = value => + { + var propValue = binding.BaseValue + (binding.TargetValue - binding.BaseValue) * value; + var src = target.GetVector(valueName); + src.y = propValue.y; // vertical only + src.w = propValue.w; // vertical only + target.SetVector(valueName, src); + }; + m_materialSetterMap.Add(binding, setter); + } + else + { + Action vec4Setter = x => { - //target.SetBlendShapeWeight(binding.Index, x); var propValue = binding.BaseValue + (binding.TargetValue - binding.BaseValue) * x; target.SetColor(binding.ValueName, propValue); - }); + }; + m_materialSetterMap.Add(binding, vec4Setter); + } } else { @@ -111,17 +140,17 @@ namespace VRM foreach (var kv in m_blendShapeValueMap) { Action setter; - if(m_blendShapeSetterMap.TryGetValue(kv.Key, out setter)) + if (m_blendShapeSetterMap.TryGetValue(kv.Key, out setter)) { setter(kv.Value); } } m_blendShapeValueMap.Clear(); - foreach(var kv in m_materialValueMap) + foreach (var kv in m_materialValueMap) { Action setter; - if(m_materialSetterMap.TryGetValue(kv.Key, out setter)) + if (m_materialSetterMap.TryGetValue(kv.Key, out setter)) { setter(kv.Value); } @@ -129,6 +158,15 @@ namespace VRM m_materialValueMap.Clear(); } + public void SetValues(IEnumerable> values) + { + foreach (var kv in values) + { + SetValue(kv.Key, kv.Value, false); + } + Apply(); + } + public void SetValue(BlendShapeKey key, float value, bool replace) { m_valueMap[key] = value; @@ -145,7 +183,7 @@ namespace VRM { // 値置き換え Action setter; - if(m_blendShapeSetterMap.TryGetValue(binding, out setter)) + if (m_blendShapeSetterMap.TryGetValue(binding, out setter)) { setter(binding.Weight * value); } @@ -154,7 +192,7 @@ namespace VRM { // 積算 float acc; - if(m_blendShapeValueMap.TryGetValue(binding, out acc)) + if (m_blendShapeValueMap.TryGetValue(binding, out acc)) { m_blendShapeValueMap[binding] = acc + binding.Weight * value; } diff --git a/Scripts/BlendShape/Blinker.cs b/Scripts/BlendShape/Blinker.cs index 4d66dd34c..e1c6cf8c8 100644 --- a/Scripts/BlendShape/Blinker.cs +++ b/Scripts/BlendShape/Blinker.cs @@ -107,34 +107,7 @@ namespace VRM m_coroutine = StartCoroutine(BlinkRoutine()); } - private void Update() - { - /* - var weight = BlendShapes.GetWeightByFlags(BlendShapeFlags.Eye, m_key); - if (m_coroutine != null) - { - if (weight > 0) - { - Debug.LogFormat("[Blinker] cancel: {0}", BlendShapes.Status); - // まばたきキャンセル - StopCoroutine(m_coroutine); - m_coroutine = null; - BlendShapes.Set(m_key, 0); - } - } - else - { - if (weight == 0) - { - Debug.Log("[Blinker] start"); - // 開始 - m_coroutine = StartCoroutine(BlinkRoutine()); - } - } - */ - } - - private void OnDisable() + private void OnDestroy() { if (m_coroutine != null) { diff --git a/Scripts/BlendShape/Editor/BlendShapeAvatarEditor.cs b/Scripts/BlendShape/Editor/BlendShapeAvatarEditor.cs index ff5a20863..77d27be20 100644 --- a/Scripts/BlendShape/Editor/BlendShapeAvatarEditor.cs +++ b/Scripts/BlendShape/Editor/BlendShapeAvatarEditor.cs @@ -29,8 +29,9 @@ namespace VRM return; } path = path.ToUnityRelativePath(); - Debug.LogFormat("{0}", path); + //Debug.LogFormat("{0}", path); var clip = ScriptableObject.CreateInstance(); + clip.BlendShapeName = Path.GetFileNameWithoutExtension(path); m_target.Clips.Add(clip); clip.Prefab = AssetDatabase.LoadAssetAtPath(AssetDatabase.GetAssetPath(m_target)); AssetDatabase.CreateAsset(clip, path); @@ -47,7 +48,10 @@ namespace VRM m_currentClip = value; //ClearBlendShape(); - Bake(m_currentClip.Values, m_currentClip.MaterialValues, 1.0f); + if (m_currentClip != null) + { + Bake(m_currentClip.Values, m_currentClip.MaterialValues, 1.0f); + } } } @@ -66,6 +70,12 @@ namespace VRM base.OnEnable(); m_target = (BlendShapeAvatar)target; + // remove missing values + foreach(var x in m_target.Clips.Select((x, i) => new { i, x }).Where(x => x.x == null).Reverse()) + { + m_target.Clips.RemoveAt(x.i); + } + CurrentClip = m_target.Clips[0]; } @@ -86,9 +96,12 @@ namespace VRM { EditorGUILayout.Space(); EditorGUILayout.LabelField("Select BlendShapeClip", EditorStyles.boldLabel); - var preset = GUILayout.SelectionGrid(m_preset, m_target.Clips - .Where(x => x != null) - .Select(x => BlendShapeKey.CreateFrom(x).ToString()).ToArray(), 4); + var array = m_target.Clips + .Select(x => x != null + ? BlendShapeKey.CreateFrom(x).ToString() + : "null" + ).ToArray(); + var preset = GUILayout.SelectionGrid(m_preset, array, 4); if (preset != m_preset) { CurrentClip = m_target.Clips[preset]; @@ -116,7 +129,9 @@ namespace VRM CurrentClip.Preset = (BlendShapePreset)EditorGUILayout.Popup("Preset", (int)CurrentClip.Preset, Presets); + GUI.enabled = false; CurrentClip.BlendShapeName = EditorGUILayout.TextField("BlendShapeName", CurrentClip.BlendShapeName); + GUI.enabled = true; var key = BlendShapeKey.CreateFrom(CurrentClip); if (m_target.Clips.Where(x => key.Match(x)).Count() > 1) diff --git a/Scripts/BlendShape/Editor/BlendShapeClipEditor.cs b/Scripts/BlendShape/Editor/BlendShapeClipEditor.cs index 2c0f1578e..701f0553c 100644 --- a/Scripts/BlendShape/Editor/BlendShapeClipEditor.cs +++ b/Scripts/BlendShape/Editor/BlendShapeClipEditor.cs @@ -52,11 +52,12 @@ namespace VRM m_ValuesList = new ReorderableList(serializedObject, m_ValuesProp); m_ValuesList.elementHeight = BlendShapeBindingHeight; m_ValuesList.drawElementCallback = - (rect, index, isActive, isFocused) => { + (rect, index, isActive, isFocused) => + { var element = m_ValuesProp.GetArrayElementAtIndex(index); rect.height -= 4; rect.y += 2; - if(DrawBlendShapeBinding(rect, element, PreviewSceneManager)) + if (DrawBlendShapeBinding(rect, element, PreviewSceneManager)) { m_changed = true; } @@ -66,11 +67,12 @@ namespace VRM m_MaterialValuesList = new ReorderableList(serializedObject, m_MaterialValuesProp); m_MaterialValuesList.elementHeight = MaterialValueBindingHeight; m_MaterialValuesList.drawElementCallback = - (rect, index, isActive, isFocused) => { + (rect, index, isActive, isFocused) => + { var element = m_MaterialValuesProp.GetArrayElementAtIndex(index); rect.height -= 4; rect.y += 2; - if(DrawMaterialValueBinding(rect, element, PreviewSceneManager)) + if (DrawMaterialValueBinding(rect, element, PreviewSceneManager)) { m_changed = true; } @@ -89,8 +91,9 @@ namespace VRM m_changed = false; EditorGUILayout.Space(); - var previewSlider= EditorGUILayout.Slider("Preview Weight", m_previewSlider, 0, 1.0f); - if (previewSlider != m_previewSlider) { + var previewSlider = EditorGUILayout.Slider("Preview Weight", m_previewSlider, 0, 1.0f); + if (previewSlider != m_previewSlider) + { m_previewSlider = previewSlider; m_changed = true; } @@ -110,7 +113,7 @@ namespace VRM serializedObject.ApplyModifiedProperties(); - if (m_changed && PreviewSceneManager!=null) + if (m_changed && PreviewSceneManager != null) { PreviewSceneManager.Bake(m_target.Values, m_target.MaterialValues, m_previewSlider); } @@ -180,6 +183,8 @@ namespace VRM { y += height; rect = new Rect(position.x, y, position.width, height); + + // プロパティ名のポップアップ int propIndex; if (StringPopup(rect, property.FindPropertyRelative("ValueName"), materialItem.PropNames, out propIndex)) { @@ -188,6 +193,7 @@ namespace VRM if (propIndex >= 0) { + // 有効なプロパティ名が選択された var propItem = materialItem.PropMap[materialItem.PropNames[propIndex]]; { switch (propItem.PropertyType) @@ -205,6 +211,20 @@ namespace VRM } } break; + + case ShaderUtil.ShaderPropertyType.TexEnv: + { + property.FindPropertyRelative("BaseValue").vector4Value = propItem.DefaultValues; + + // max + y += height; + rect = new Rect(position.x, y, position.width, height); + if (OffsetProp(rect, property.FindPropertyRelative("TargetValue"))) + { + changed = true; + } + } + break; } } } @@ -285,5 +305,20 @@ namespace VRM return false; } } + + static bool OffsetProp(Rect rect, SerializedProperty prop) + { + var oldValue = prop.vector4Value; + var newValue = EditorGUI.Vector4Field(rect, prop.displayName, oldValue); + if (newValue != oldValue) + { + prop.vector4Value = newValue; + return true; + } + else + { + return false; + } + } } } diff --git a/Scripts/BlendShape/MeshPreviewItem.cs b/Scripts/BlendShape/MeshPreviewItem.cs index 8af87a874..ffcce8fd0 100644 --- a/Scripts/BlendShape/MeshPreviewItem.cs +++ b/Scripts/BlendShape/MeshPreviewItem.cs @@ -45,15 +45,28 @@ namespace VRM for (int i = 0; i < ShaderUtil.GetPropertyCount(material.shader); ++i) { var propType = ShaderUtil.GetPropertyType(material.shader, i); - if (propType == ShaderUtil.ShaderPropertyType.Color) + var name = ShaderUtil.GetPropertyName(material.shader, i); + + switch (propType) { - var name = ShaderUtil.GetPropertyName(material.shader, i); - item.PropMap.Add(name, new PropItem - { - PropertyType = propType, - DefaultValues = material.GetColor(name), - }); - propNames.Add(name); + case ShaderUtil.ShaderPropertyType.Color: + item.PropMap.Add(name, new PropItem + { + 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); + break; } } item.PropNames = propNames.ToArray(); diff --git a/Scripts/BlendShape/PreviewSceneManager.cs b/Scripts/BlendShape/PreviewSceneManager.cs index 6ad3e1942..849e1b8a6 100644 --- a/Scripts/BlendShape/PreviewSceneManager.cs +++ b/Scripts/BlendShape/PreviewSceneManager.cs @@ -42,7 +42,7 @@ namespace VRM GameObject.DestroyImmediate(x.gameObject); } - Debug.Log("new prefab. instanciate"); + //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, @@ -95,7 +95,7 @@ namespace VRM dst = new Material(src); map.Add(src, dst); - Debug.LogFormat("add material {0}", src.name); + //Debug.LogFormat("add material {0}", src.name); materialNames.Add(src.name); m_materialMap.Add(src.name, MaterialItem.Create(dst)); } diff --git a/Scripts/BlendShape/VRMBlendShapeProxy.cs b/Scripts/BlendShape/VRMBlendShapeProxy.cs index c22cb5965..7d7188d41 100644 --- a/Scripts/BlendShape/VRMBlendShapeProxy.cs +++ b/Scripts/BlendShape/VRMBlendShapeProxy.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using UnityEngine; @@ -52,13 +53,32 @@ namespace VRM { return GetValue(new BlendShapeKey(key)); } - public void SetValue(BlendShapeKey key, float value, bool apply = true) + + [Obsolete("Use SetValues")] + public void SetValue(BlendShapeKey key, float value, bool apply) { if (m_merger != null) { m_merger.SetValue(key, value, apply); } } + + public void SetValue(BlendShapeKey key, float value) + { + if (m_merger != null) + { + m_merger.SetValue(key, value, true); + } + } + + public void SetValues(IEnumerable> values) + { + if (m_merger != null) + { + m_merger.SetValues(values); + } + } + public float GetValue(BlendShapeKey key) { if (m_merger == null) diff --git a/Scripts/Editor/VRMTest.cs b/Scripts/Editor/VRMTest.cs index ffc3ef331..a0b02d8f3 100644 --- a/Scripts/Editor/VRMTest.cs +++ b/Scripts/Editor/VRMTest.cs @@ -46,7 +46,7 @@ public class VRMTest public void VRMSimpleSceneTest() { var go = CreateSimpelScene(); - var context = new VRMImporterContext(null); + var context = new VRMImporterContext(); try { @@ -59,9 +59,9 @@ public class VRMTest exporter.Export(); // import - context.ParseJson(gltf.ToJson(), new SimpleStorage()); - Debug.LogFormat("{0}", context.Json); - gltfImporter.Import(context); + context.ParseJson(gltf.ToJson(), new SimpleStorage()); + //Debug.LogFormat("{0}", context.Json); + gltfImporter.Load(context); AssertAreEqual(go.transform, context.Root.transform); } diff --git a/Scripts/FirstPerson/VRMFirstPerson.cs b/Scripts/FirstPerson/VRMFirstPerson.cs index 2deaa5724..41e7cfaf6 100644 --- a/Scripts/FirstPerson/VRMFirstPerson.cs +++ b/Scripts/FirstPerson/VRMFirstPerson.cs @@ -121,7 +121,7 @@ namespace VRM return FirstPersonFlag.Auto; } - foreach(var x in context.VRM.extensions.VRM.firstPerson.meshAnnotations) + foreach(var x in context.GLTF.extensions.VRM.firstPerson.meshAnnotations) { if (x.mesh == index) { diff --git a/Scripts/Format/Editor/VRMExportObjectEditor.cs b/Scripts/Format/Editor/VRMExportObjectEditor.cs new file mode 100644 index 000000000..6f1ddb05b --- /dev/null +++ b/Scripts/Format/Editor/VRMExportObjectEditor.cs @@ -0,0 +1,101 @@ +using System; +using UnityEditor; +using UnityEngine; + + +namespace VRM +{ + [CustomEditor(typeof(VRMExportObject))] + public class VRMExportObjectEditor : Editor + { + SerializedProperty m_settings; + VRMExportObject m_target; + + void OnEnable() + { + m_target = target as VRMExportObject; + m_settings = serializedObject.FindProperty("Settings"); + } + + public override void OnInspectorGUI() + { + // + // Editor + // + serializedObject.Update(); + + var before = m_target.Settings.Source; + + EditorGUILayout.PropertyField(m_settings, true); + serializedObject.ApplyModifiedProperties(); + + // + // + // + var after = m_target.Settings.Source; + if (before != after) + { + m_target.Settings.InitializeFrom(after as GameObject); + } + + bool canExport = m_target.Settings.Source != null; + foreach (var msg in m_target.Settings.CanExport()) + { + canExport = false; + EditorGUILayout.HelpBox(msg, MessageType.Error); + } + + if (canExport) + { + if (GUILayout.Button("Export")) + { + var path = EditorUtility.SaveFilePanel( + "Save vrm", + null,//Dir, + m_target.Settings.Source.name + ".vrm", + "vrm"); + if (!string.IsNullOrEmpty(path)) + { + var target = m_target; + EditorApplication.delayCall += () => + { + target.Settings.Export(path); + }; + } + } + } + } + + class DisposableInstance : IDisposable + { + GameObject m_go; + public GameObject GameObject + { + get + { + return m_go; + } + } + + public DisposableInstance(GameObject prefab) + { + m_go = GameObject.Instantiate(prefab); + } + + public void Dispose() + { + if (m_go != null) + { + if (Application.isPlaying) + { + GameObject.Destroy(m_go); + } + else + { + GameObject.DestroyImmediate(m_go); + } + } + } + } + } +} diff --git a/Scripts/Format/Editor/VRMExportObjectEditor.cs.meta b/Scripts/Format/Editor/VRMExportObjectEditor.cs.meta new file mode 100644 index 000000000..9a7ef2be7 --- /dev/null +++ b/Scripts/Format/Editor/VRMExportObjectEditor.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 01708ecf1aa756948be6996d987684a7 +timeCreated: 1532063961 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/Format/Editor/VRMExporterMenu.cs b/Scripts/Format/Editor/VRMExporterMenu.cs index 0d11ad553..34790f8fa 100644 --- a/Scripts/Format/Editor/VRMExporterMenu.cs +++ b/Scripts/Format/Editor/VRMExporterMenu.cs @@ -1,10 +1,6 @@ -using System.Collections.Generic; -using System.Linq; +using System.Text; using UnityEditor; using UnityEngine; -using UniGLTF; -using System.IO; -using System.Text; namespace VRM @@ -13,112 +9,37 @@ namespace VRM { const string EXTENSION = ".vrm"; - public Animator Target; VRMMeta m_meta; - public string Title; - - public string Author; - - public bool ForceTPose; - - public bool PoseFreeze; + public VRMExportSettings m_settings = new VRMExportSettings(); public static void CreateWizard() { var wiz = ScriptableWizard.DisplayWizard( "VRM Exporter", "Export"); - var go= Selection.activeObject as GameObject; - if (go != null) - { - wiz.Target = go.GetComponent(); - } + var go = Selection.activeObject as GameObject; // update checkbox - var desc = wiz.Target.GetComponent(); - if (desc == null) - { - wiz.ForceTPose = true; - wiz.PoseFreeze = true; - } - - var meta = wiz.Target.GetComponent(); - if (meta != null && meta.Meta != null) - { - wiz.Title = meta.Meta.Title; - wiz.Author = meta.Meta.Author; - } - else - { - wiz.Title = wiz.Target.name; - } + wiz.m_settings.InitializeFrom(go); wiz.OnWizardUpdate(); } - string m_dir; - string Dir - { - get - { - if (m_dir == null) - { - m_dir = Path.GetFullPath(Application.dataPath); ; - } - return m_dir; - } - set - { - m_dir = value; - } - } - void OnWizardCreate() { // save dialog - Debug.LogFormat("OnWizardCreate: {0}", Dir); var path = EditorUtility.SaveFilePanel( "Save vrm", - Dir, - Target.name + EXTENSION, + null, + m_settings.Source.name + EXTENSION, EXTENSION.Substring(1)); if (string.IsNullOrEmpty(path)) { return; } - Dir = Path.GetDirectoryName(path); // export - var target = Target.gameObject; - if (PoseFreeze) - { - Undo.RecordObjects(Target.transform.Traverse().ToArray(), "before normalize"); - var map = new Dictionary(); - target = VRM.BoneNormalizer.Execute(Target.gameObject, map, ForceTPose); - VRMHumanoidNorimalizerMenu.CopyVRMComponents(Target.gameObject, target, map); - Undo.PerformUndo(); - } - - var sw = System.Diagnostics.Stopwatch.StartNew(); - - VRMExporter.Export(target, path, gltf => { - - gltf.extensions.VRM.meta.title = Title; - gltf.extensions.VRM.meta.author = Author; - - }); - - Debug.LogFormat("Export elapsed {0}", sw.Elapsed); - - if (Target.gameObject!=target) - { - GameObject.DestroyImmediate(target); - } - - if (path.StartsWithUnityAssetPath()) - { - AssetDatabase.ImportAsset(path.ToUnityRelativePath()); - } + m_settings.Export(path); } void OnWizardUpdate() @@ -127,38 +48,10 @@ namespace VRM var helpBuilder = new StringBuilder(); var errorBuilder = new StringBuilder(); - if (Target == null) + foreach(var msg in m_settings.CanExport()) { isValid = false; - errorBuilder.Append("Require animator. "); - } - else if (Target.avatar == null) - { - isValid = false; - errorBuilder.Append("Require animator.avatar. "); - } - else if (!Target.avatar.isValid) - { - isValid = false; - errorBuilder.Append("Animator.avatar is not valid. "); - } - else if (!Target.avatar.isHuman) - { - isValid = false; - errorBuilder.Append("Animator.avatar is not humanoid. Please change model's AnimationType to humanoid. "); - } - - - if (string.IsNullOrEmpty(Title)) - { - isValid = false; - errorBuilder.Append("Require Title. "); - } - - if (string.IsNullOrEmpty(Author)) - { - isValid = false; - errorBuilder.Append("Require Author. "); + errorBuilder.Append(msg); } helpString = helpBuilder.ToString(); @@ -168,7 +61,7 @@ namespace VRM public static class VRMExporterMenu { - const string CONVERT_HUMANOID_KEY = "VRM/export humanoid"; + const string CONVERT_HUMANOID_KEY = VRMVersion.VRM_VERSION + "/export humanoid"; [MenuItem(CONVERT_HUMANOID_KEY, true, 1)] private static bool ExportValidate() diff --git a/Scripts/Format/Editor/VRMHumanoidNormalizerMenu.cs b/Scripts/Format/Editor/VRMHumanoidNormalizerMenu.cs index 8d988c774..55c7f7bc9 100644 --- a/Scripts/Format/Editor/VRMHumanoidNormalizerMenu.cs +++ b/Scripts/Format/Editor/VRMHumanoidNormalizerMenu.cs @@ -9,7 +9,7 @@ namespace VRM { public static class VRMHumanoidNorimalizerMenu { - const string MENU_KEY = "VRM/freeze T-Pose"; + const string MENU_KEY = VRMVersion.VRM_VERSION+"/freeze T-Pose"; [MenuItem(MENU_KEY, true, 1)] private static bool ExportValidate() { @@ -47,125 +47,12 @@ namespace VRM private static void ExportFromMenu() { var go = Selection.activeObject as GameObject; - Undo.RecordObjects(go.transform.Traverse().ToArray(), "before normalize"); - var map = new Dictionary(); - var normalized = VRM.BoneNormalizer.Execute(go, map, true); - CopyVRMComponents(go, normalized, map); - Undo.PerformUndo(); - } - // - // トップレベルのMonoBehaviourを移植する - // - public static void CopyVRMComponents(GameObject go, GameObject root, - Dictionary map) - { - { - // blendshape - var src = go.GetComponent(); - if (src != null) - { - var dst = root.AddComponent(); - dst.BlendShapeAvatar = src.BlendShapeAvatar; - } - } + var normalized = VRM.BoneNormalizer.Execute(go, true); + VRMExportSettings.CopyVRMComponents(go, normalized.Root, normalized.BoneMap); + Selection.activeGameObject = normalized.Root; - { - var secondary = go.transform.Find("secondary"); - if (secondary == null) - { - secondary = go.transform; - } - - var dstSecondary = root.transform.Find("secondary"); - if (dstSecondary == null) - { - dstSecondary = new GameObject("secondary").transform; - dstSecondary.SetParent(root.transform, false); - } - - // 揺れモノ - foreach (var src in go.transform.Traverse().Select(x => x.GetComponent()).Where(x => x != null)) - { - var dst = map[src.transform]; - var dstColliderGroup = dst.gameObject.AddComponent(); - dstColliderGroup.Colliders = src.Colliders.Select(y => - { - var offset = dst.worldToLocalMatrix.MultiplyPoint(src.transform.localToWorldMatrix.MultiplyPoint(y.Offset)); - return new VRMSpringBoneColliderGroup.SphereCollider - { - Offset = offset, - Radius = y.Radius - }; - }).ToArray(); - } - - foreach (var src in go.transform.Traverse().SelectMany(x => x.GetComponents())) - { - var dst = dstSecondary.gameObject.AddComponent(); - dst.m_comment = src.m_comment; - dst.RootBones = src.RootBones.Select(x => map[x]).ToList(); - if (src.ColliderGroups != null) - { - dst.ColliderGroups = src.ColliderGroups.Select(x => map[x.transform].GetComponent()).ToArray(); - } - } - } - - { - // meta(obsolete) - var src = go.GetComponent(); - if (src != null) - { - src.CopyTo(root); - } - } - { - // meta - var src = go.GetComponent(); - if (src != null) - { - var dst = root.AddComponent(); - dst.Meta = src.Meta; - } - } - - { - // firstPerson - var src = go.GetComponent(); - if (src != null) - { - src.CopyTo(root, map); - } - } - - { - // lookAt - var src = go.GetComponent(); - if (src != null) - { - src.CopyTo(root, map); - } - } - - { - // humanoid - var dst = root.AddComponent(); - var src = go.GetComponent(); - if (src != null) - { - dst.Avatar = src.Avatar; - dst.Description = src.Description; - } - else - { - var animator = go.GetComponent(); - if (animator != null) - { - dst.Avatar = animator.avatar; - } - } - } + Undo.RegisterCreatedObjectUndo(normalized.Root, "normalize"); } } } diff --git a/Scripts/Format/Editor/VRMVersionMenu.cs b/Scripts/Format/Editor/VRMVersionMenu.cs index ed794222e..6939c492f 100644 --- a/Scripts/Format/Editor/VRMVersionMenu.cs +++ b/Scripts/Format/Editor/VRMVersionMenu.cs @@ -1,6 +1,9 @@ using System.IO; +using System.Text; +using UniGLTF; +using UniJSON; using UnityEditor; - +using UnityEngine; namespace VRM { @@ -16,17 +19,14 @@ namespace VRM public const int MINOR = {1}; public const string VERSION = ""{0}.{1}""; - - public const string DecrementMenuName = ""VRM/Version({0}.{1}) Decrement""; - public const string IncrementMenuName = ""VRM/Version({0}.{1}) Increment""; }} }} "; #if VRM_DEVELOP - [MenuItem(VRMVersion.IncrementMenuName)] + [MenuItem(VRMVersion.VRM_VERSION + "/Increment")] #endif - public static void IncrementVersion() + static void IncrementVersion() { var source = string.Format(template, VRMVersion.MAJOR, VRMVersion.MINOR + 1); File.WriteAllText(path, source); @@ -34,13 +34,33 @@ namespace VRM } #if VRM_DEVELOP - [MenuItem(VRMVersion.DecrementMenuName)] + [MenuItem(VRMVersion.VRM_VERSION + "/Decrement")] #endif - public static void DecrementVersion() + static void DecrementVersion() { var source = string.Format(template, VRMVersion.MAJOR, VRMVersion.MINOR - 1); File.WriteAllText(path, source); AssetDatabase.Refresh(); } + +#if VRM_DEVELOP + [MenuItem(VRMVersion.VRM_VERSION + "/Export JsonSchema")] +#endif + static void ExportJsonSchema() + { + var dir = UnityPath.FromFullpath(Application.dataPath + "/VRM/specification/0.0/schema"); + dir.EnsureFolder(); + + var path = dir.Child("vrm.schema.json"); + + Debug.LogFormat("write SsonSchema: {0}", path.FullPath); + var schema = JsonSchema.FromType(); + var f = new JsonFormatter(); + schema.ToJson(f); + var json = f.ToString(); + File.WriteAllText(path.FullPath, json, Encoding.UTF8); + + Selection.activeObject = AssetDatabase.LoadAssetAtPath(path.Value); + } } } diff --git a/Scripts/Format/Editor/vrmAssetPostprocessor.cs b/Scripts/Format/Editor/vrmAssetPostprocessor.cs index 771e608c3..d4a118bdb 100644 --- a/Scripts/Format/Editor/vrmAssetPostprocessor.cs +++ b/Scripts/Format/Editor/vrmAssetPostprocessor.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using System.Linq; using UniGLTF; using UnityEditor; @@ -16,49 +17,31 @@ namespace VRM var ext = Path.GetExtension(path).ToLower(); if (ext == ".vrm") { - var context = new VRMImporterContext(path); - - context.ParseVrm(File.ReadAllBytes(context.Path)); - - // - // https://answers.unity.com/questions/647615/how-to-update-import-settings-for-newly-created-as.html - // - for (int i = 0; i < context.GLTF.textures.Count; ++i) - { - var x = context.GLTF.textures[i]; - var image = context.GLTF.images[x.source]; - if (string.IsNullOrEmpty(image.uri)) - { - // glb buffer - var folder = context.GetAssetFolder(".Textures").AssetPathToFullPath(); - if (!Directory.Exists(folder)) - { - UnityEditor.AssetDatabase.CreateFolder(context.GLTF.baseDir, Path.GetFileNameWithoutExtension(context.Path) + ".Textures"); - //Directory.CreateDirectory(folder); - } - - var textureName = !string.IsNullOrEmpty(image.name) ? image.name: string.Format("buffer#{0:00}", i); - var png = Path.Combine(folder, textureName + ".png"); - var byteSegment = context.GLTF.GetViewBytes(image.bufferView); - File.WriteAllBytes(png, byteSegment.ToArray()); - var assetPath = png.ToUnityRelativePath(); - //Debug.LogFormat("import asset {0}", assetPath); - UnityEditor.AssetDatabase.ImportAsset(assetPath, UnityEditor.ImportAssetOptions.ForceUpdate); - UnityEditor.AssetDatabase.Refresh(); - image.uri = assetPath.Substring(context.GLTF.baseDir.Length + 1); - } - } - - EditorApplication.delayCall += () => - { - // delay and can import png texture - VRMImporter.LoadFromBytes(context); - context.SaveAsAsset(); - context.Destroy(false); - }; + ImportVrm(UnityPath.FromUnityPath(path)); } } } + + static void ImportVrm(UnityPath path) + { + if (!path.IsUnderAssetsFolder) + { + throw new Exception(); + } + var context = new VRMImporterContext(path); + context.ParseGlb(File.ReadAllBytes(path.FullPath)); + + var prefabPath = path.Parent.Child(path.FileNameWithoutExtension + ".prefab"); + context.SaveTexturesAsPng(prefabPath); + + EditorApplication.delayCall += () => + { + // delay and can import png texture + VRMImporter.LoadFromBytes(context); + context.SaveAsAsset(prefabPath); + context.Destroy(false); + }; + } } #endif } diff --git a/Scripts/Format/VRMExporSettings.cs b/Scripts/Format/VRMExporSettings.cs new file mode 100644 index 000000000..5bfe68214 --- /dev/null +++ b/Scripts/Format/VRMExporSettings.cs @@ -0,0 +1,284 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using UniGLTF; +using System.IO; +#if UNITY_EDITOR +using UnityEditor; +#endif + + +namespace VRM +{ + [Serializable] + public class VRMExportSettings + { + public GameObject Source; + + public string Title; + + public string Author; + + public bool ForceTPose = true; + + public bool PoseFreeze = true; + + public IEnumerable CanExport() + { + if (Source == null) + { + yield return "Require source"; + yield break; + } + + var animator = Source.GetComponent(); + if (animator == null) + { + yield return "Require animator. "; + } + else if (animator.avatar == null) + { + yield return "Require animator.avatar. "; + } + else if (!animator.avatar.isValid) + { + yield return "Animator.avatar is not valid. "; + } + else if (!animator.avatar.isHuman) + { + yield return "Animator.avatar is not humanoid. Please change model's AnimationType to humanoid. "; + } + + if (string.IsNullOrEmpty(Title)) + { + yield return "Require Title. "; + } + + if (string.IsNullOrEmpty(Author)) + { + yield return "Require Author. "; + } + } + + public void InitializeFrom(GameObject go) + { + if (Source == go) return; + Source = go; + + var desc = Source == null ? null : go.GetComponent(); + if (desc == null) + { + ForceTPose = true; + PoseFreeze = true; + } + else + { + ForceTPose = false; + PoseFreeze = false; + } + + var meta = Source == null ? null : go.GetComponent(); + if (meta != null && meta.Meta != null) + { + Title = meta.Meta.Title; + Author = meta.Meta.Author; + } + else + { + Title = go.name; + //Author = ""; + } + } + + // + // トップレベルのMonoBehaviourを移植する + // + public static void CopyVRMComponents(GameObject go, GameObject root, + Dictionary map) + { + { + // blendshape + var src = go.GetComponent(); + if (src != null) + { + var dst = root.AddComponent(); + dst.BlendShapeAvatar = src.BlendShapeAvatar; + } + } + + { + var secondary = go.transform.Find("secondary"); + if (secondary == null) + { + secondary = go.transform; + } + + var dstSecondary = root.transform.Find("secondary"); + if (dstSecondary == null) + { + dstSecondary = new GameObject("secondary").transform; + dstSecondary.SetParent(root.transform, false); + } + + // 揺れモノ + foreach (var src in go.transform.Traverse().Select(x => x.GetComponent()).Where(x => x != null)) + { + var dst = map[src.transform]; + var dstColliderGroup = dst.gameObject.AddComponent(); + dstColliderGroup.Colliders = src.Colliders.Select(y => + { + var offset = dst.worldToLocalMatrix.MultiplyPoint(src.transform.localToWorldMatrix.MultiplyPoint(y.Offset)); + return new VRMSpringBoneColliderGroup.SphereCollider + { + Offset = offset, + Radius = y.Radius + }; + }).ToArray(); + } + + foreach (var src in go.transform.Traverse().SelectMany(x => x.GetComponents())) + { + var dst = dstSecondary.gameObject.AddComponent(); + dst.m_comment = src.m_comment; + dst.RootBones = src.RootBones.Select(x => map[x]).ToList(); + if (src.ColliderGroups != null) + { + dst.ColliderGroups = src.ColliderGroups.Select(x => map[x.transform].GetComponent()).ToArray(); + } + } + } + + { + // meta(obsolete) + var src = go.GetComponent(); + if (src != null) + { + src.CopyTo(root); + } + } + { + // meta + var src = go.GetComponent(); + if (src != null) + { + var dst = root.AddComponent(); + dst.Meta = src.Meta; + } + } + + { + // firstPerson + var src = go.GetComponent(); + if (src != null) + { + src.CopyTo(root, map); + } + } + + { + // lookAt + var src = go.GetComponent(); + if (src != null) + { + src.CopyTo(root, map); + } + } + + { + // humanoid + var dst = root.AddComponent(); + var src = go.GetComponent(); + if (src != null) + { + dst.Avatar = src.Avatar; + dst.Description = src.Description; + } + else + { + var animator = go.GetComponent(); + if (animator != null) + { + dst.Avatar = animator.avatar; + } + } + } + } + + public static bool IsPrefab(GameObject go) + { + return go.scene.name == null; + } + +#if UNITY_EDITOR + struct RecordDisposer : IDisposable + { + public RecordDisposer(UnityEngine.Object[] objects, string msg) + { + Undo.RecordObjects(objects, msg); + } + + public void Dispose() + { + Undo.PerformUndo(); + } + } + + public void Export(string path) + { + List destroy = new List(); + try + { + Export(path, destroy); + } + finally + { + foreach (var x in destroy) + { + Debug.LogFormat("destroy: {0}", x.name); + GameObject.DestroyImmediate(x); + } + } + } + + void Export(string path, List destroy) + { + var target = Source; + if (IsPrefab(target)) + { + using (new RecordDisposer(Source.transform.Traverse().ToArray(), "before normalize")) + { + target = GameObject.Instantiate(target); + destroy.Add(target); + } + } + if (PoseFreeze) + { + using (new RecordDisposer(target.transform.Traverse().ToArray(), "before normalize")) + { + var normalized = BoneNormalizer.Execute(target, ForceTPose); + CopyVRMComponents(target, normalized.Root, normalized.BoneMap); + target = normalized.Root; + destroy.Add(target); + } + } + + { + var sw = System.Diagnostics.Stopwatch.StartNew(); + var vrm = VRMExporter.Export(target); + vrm.extensions.VRM.meta.title = Title; + vrm.extensions.VRM.meta.author = Author; + + var bytes = vrm.ToGlbBytes(); + File.WriteAllBytes(path, bytes); + Debug.LogFormat("Export elapsed {0}", sw.Elapsed); + } + + if (path.StartsWithUnityAssetPath()) + { + AssetDatabase.ImportAsset(path.ToUnityRelativePath()); + } + } +#endif + } +} diff --git a/Scripts/Format/VRMExporSettings.cs.meta b/Scripts/Format/VRMExporSettings.cs.meta new file mode 100644 index 000000000..d80644aa3 --- /dev/null +++ b/Scripts/Format/VRMExporSettings.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: f1bcfcc2d4692ef41b0c8f0f9ec3df14 +timeCreated: 1532066746 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/Format/VRMExportObject.cs b/Scripts/Format/VRMExportObject.cs new file mode 100644 index 000000000..e701ac1e9 --- /dev/null +++ b/Scripts/Format/VRMExportObject.cs @@ -0,0 +1,12 @@ +using UnityEngine; + + +namespace VRM +{ + [CreateAssetMenu(menuName = "VRM/ExportObject")] + public class VRMExportObject : ScriptableObject + { + [SerializeField] + public VRMExportSettings Settings = new VRMExportSettings(); + } +} diff --git a/Scripts/Format/VRMExportObject.cs.meta b/Scripts/Format/VRMExportObject.cs.meta new file mode 100644 index 000000000..90b6df455 --- /dev/null +++ b/Scripts/Format/VRMExportObject.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: f8ed5cb82dd13bf43a1556b24a6d13a0 +timeCreated: 1532063757 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/Format/VRMExporter.cs b/Scripts/Format/VRMExporter.cs index 49c5e7f59..5b56fcc61 100644 --- a/Scripts/Format/VRMExporter.cs +++ b/Scripts/Format/VRMExporter.cs @@ -9,13 +9,12 @@ namespace VRM { public class VRMExporter : gltfExporter { - public VRMExporter(glTF_VRM gltf) : base(gltf) + public VRMExporter(glTF gltf) : base(gltf) { } - public static glTF Export(GameObject go, string path = null, Action callback=null) + public new static glTF Export(GameObject go) { - var gltf = new glTF_VRM(); - gltf.asset.generator = string.Format("UniVRM-{0}.{1}", VRMVersion.MAJOR, VRMVersion.MINOR); + var gltf = new glTF(); using (var exporter = new VRMExporter(gltf) { #if VRM_EXPORTER_USE_SPARSE @@ -25,22 +24,11 @@ namespace VRM }) { _Export(gltf, exporter, go); - - if (callback != null) - { - callback(gltf); - } - - if (!string.IsNullOrEmpty(path)) - { - exporter.WriteTo(path); - } } - return gltf; } - public static void _Export(glTF_VRM gltf, VRMExporter exporter, GameObject go) + public static void _Export(glTF gltf, VRMExporter exporter, GameObject go) { exporter.Prepare(go); exporter.Export(); diff --git a/Scripts/Format/VRMFormat.cs b/Scripts/Format/VRMFormat.cs index 8121179d5..6408d4d2b 100644 --- a/Scripts/Format/VRMFormat.cs +++ b/Scripts/Format/VRMFormat.cs @@ -3,6 +3,33 @@ using System.Collections.Generic; using UniGLTF; +namespace UniGLTF +{ + public partial class glTFUsedExtensions + { + [UsedExtension] + static string VRMGetUsedExtension + { + get + { + return "VRM"; + } + } + } + + public partial class glTF_extensions : ExtensionsBase + { + public VRM.glTF_VRM_extensions VRM = new VRM.glTF_VRM_extensions(); + + [JsonSerializeMembers] + void VRMSerializeMembers(GLTFJsonFormatter f) + { + f.KeyValue(() => VRM); + } + } +} + + namespace VRM { [Serializable] @@ -17,7 +44,7 @@ namespace VRM public glTF_VRM_SecondaryAnimation secondaryAnimation = new glTF_VRM_SecondaryAnimation(); public List materialProperties = new List(); - protected override void SerializeMembers(JsonFormatter f) + protected override void SerializeMembers(GLTFJsonFormatter f) { f.KeyValue(() => exporterVersion); f.KeyValue(() => meta); @@ -28,32 +55,4 @@ namespace VRM f.KeyValue(() => materialProperties); } } - - [Serializable] - public class glTF_extensions : JsonSerializableBase - { - public glTF_VRM_extensions VRM = new glTF_VRM_extensions(); - - protected override void SerializeMembers(JsonFormatter f) - { - f.KeyValue(() => VRM); - } - } - - [Serializable] - public class glTF_VRM : glTF - { - public glTF_extensions extensions = new glTF_extensions(); - public List extensionsUsed = new List - { - "VRM", - }; - - protected override void SerializeMembers(JsonFormatter f) - { - f.KeyValue(() => extensionsUsed); - f.KeyValue(() => extensions); - base.SerializeMembers(f); - } - } } diff --git a/Scripts/Format/VRMImporter.cs b/Scripts/Format/VRMImporter.cs index ee70dd364..4c87c9170 100644 --- a/Scripts/Format/VRMImporter.cs +++ b/Scripts/Format/VRMImporter.cs @@ -20,8 +20,8 @@ namespace VRM public static GameObject LoadFromPath(string path) { - var context = new VRMImporterContext(path); - context.ParseVrm(File.ReadAllBytes(path)); + var context = new VRMImporterContext(UniGLTF.UnityPath.FromFullpath(path)); + context.ParseGlb(File.ReadAllBytes(path)); LoadFromBytes(context); return context.Root; } @@ -29,37 +29,16 @@ namespace VRM public static GameObject LoadFromBytes(Byte[] bytes) { var context = new VRMImporterContext(); - context.ParseVrm(bytes); + context.ParseGlb(bytes); LoadFromBytes(context); return context.Root; } - public static void LoadFromPath(VRMImporterContext context) - { - context.ParseVrm(File.ReadAllBytes(context.Path)); - LoadFromBytes(context); - } - public static void LoadFromBytes(VRMImporterContext context) { context.CreateMaterial = VRMImporter.GetMaterialFunc(glTF_VRM_Material.Parse(context.Json)); - gltfImporter.Import(context); - if (string.IsNullOrEmpty(context.Path)) - { - if (string.IsNullOrEmpty(context.VRM.extensions.VRM.meta.title)) - { - context.Root.name = "VRM_LOADED"; - } - else - { - context.Root.name = context.VRM.extensions.VRM.meta.title; - } - } - else - { - context.Root.name = Path.GetFileNameWithoutExtension(context.Path); - } + gltfImporter.Load(context); OnLoadModel(context); @@ -82,7 +61,7 @@ namespace VRM var CreateZWrite = gltfImporter.CreateMaterialFuncFromShader(new ShaderStore("VRM/UnlitTransparentZWrite")); CreateMaterialFunc fallback = (ctx, i) => { - var vrm = ctx.GLTF as glTF_VRM; + var vrm = ctx.GLTF; if (vrm != null && vrm.materials[i].name.ToLower().Contains("zwrite")) { // 一応。不要かも @@ -184,7 +163,7 @@ namespace VRM LoadBlendShapeMaster(context); VRMSpringUtility.LoadSecondary(context.Root.transform, context.Nodes, - context.VRM.extensions.VRM.secondaryAnimation); + context.GLTF.extensions.VRM.secondaryAnimation); LoadFirstPerson(context); return Unit.Default; @@ -213,7 +192,7 @@ namespace VRM { var firstPerson = context.Root.AddComponent(); - var gltfFirstPerson = context.VRM.extensions.VRM.firstPerson; + var gltfFirstPerson = context.GLTF.extensions.VRM.firstPerson; if (gltfFirstPerson.firstPersonBone != -1) { firstPerson.FirstPersonBone = context.Nodes[gltfFirstPerson.firstPersonBone]; @@ -236,7 +215,7 @@ namespace VRM context.BlendShapeAvatar = ScriptableObject.CreateInstance(); context.BlendShapeAvatar.name = "BlendShape"; - var blendShapeList = context.VRM.extensions.VRM.blendShapeMaster.blendShapeGroups; + var blendShapeList = context.GLTF.extensions.VRM.blendShapeMaster.blendShapeGroups; if (blendShapeList != null && blendShapeList.Count > 0) { foreach (var x in blendShapeList) @@ -337,14 +316,14 @@ namespace VRM [Obsolete] private static void LoadHumanoidObsolete(VRMImporterContext context) { - var parsed = context.Json.ParseAsJson()["extensions"]["VRM"]; + var parsed = UniJSON.JsonParser.Parse(context.Json)["extensions"]["VRM"]; var skeleton = context.Root.transform.Traverse().Select(x => ToSkeletonBone(x)).ToArray(); var description = new HumanDescription { human = parsed[HUMANOID_KEY]["bones"] .ObjectItems - .Select(x => new { x.Key, Index = x.Value.GetInt32() }) + .Select(x => new { x.Key, Index = x.Value.Value.GetInt32() }) .Where(x => x.Index != -1) .Select(x => { @@ -368,7 +347,7 @@ namespace VRM }; context.HumanoidAvatar = AvatarBuilder.BuildHumanAvatar(context.Root, description); - context.HumanoidAvatar.name = Path.GetFileNameWithoutExtension(context.Path); + context.HumanoidAvatar.name = "VrmAvatar"; context.AvatarDescription = UniHumanoid.AvatarDescription.CreateFrom(description); context.AvatarDescription.name = "AvatarDescription"; @@ -386,10 +365,10 @@ namespace VRM private static void LoadHumanoid(VRMImporterContext context) { - context.AvatarDescription = context.VRM.extensions.VRM.humanoid.ToDescription(context.Nodes); + context.AvatarDescription = context.GLTF.extensions.VRM.humanoid.ToDescription(context.Nodes); context.AvatarDescription.name = "AvatarDescription"; context.HumanoidAvatar = context.AvatarDescription.CreateAvatar(context.Root.transform); - context.HumanoidAvatar.name = Path.GetFileNameWithoutExtension(context.Path); + context.HumanoidAvatar.name = "VrmAvatar"; var humanoid = context.Root.AddComponent(); humanoid.Avatar = context.HumanoidAvatar; @@ -487,63 +466,58 @@ namespace VRM #if (NET_4_6 && UNITY_2017_1_OR_NEWER) - public static Task LoadVrmAsync(string path) + public static Task LoadVrmAsync(string path, bool show=true) { var context = new VRMImporterContext(path); context.ParseVrm(File.ReadAllBytes(path)); - return LoadVrmAsyncInternal(context).ToTask(); + return LoadVrmAsyncInternal(context, show).ToTask(); } - public static Task LoadVrmAsync(Byte[] bytes) + public static Task LoadVrmAsync(Byte[] bytes, bool show=true) { var context = new VRMImporterContext(); context.ParseVrm(bytes); - return LoadVrmAsync(context); + return LoadVrmAsync(context, show); } - public static Task LoadVrmAsync(VRMImporterContext ctx) + public static Task LoadVrmAsync(VRMImporterContext ctx, bool show=true) { - return LoadVrmAsyncInternal(ctx).ToTask(); + return LoadVrmAsyncInternal(ctx, show).ToTask(); } #endif - public static void LoadVrmAsync(string path, Action onLoaded, Action onError = null) + public static void LoadVrmAsync(string path, Action onLoaded, Action onError = null, bool show = true) { - var context = new VRMImporterContext(path); - context.ParseVrm(File.ReadAllBytes(path)); - LoadVrmAsync(context, onLoaded, onError); + var context = new VRMImporterContext(UnityPath.FromFullpath(path)); + context.ParseGlb(File.ReadAllBytes(path)); + LoadVrmAsync(context, onLoaded, onError, show); } - public static void LoadVrmAsync(Byte[] bytes, Action onLoaded, Action onError = null) + public static void LoadVrmAsync(Byte[] bytes, Action onLoaded, Action onError = null, bool show = true) { var context = new VRMImporterContext(); - context.ParseVrm(bytes); - LoadVrmAsync(context, onLoaded, onError); + context.ParseGlb(bytes); + LoadVrmAsync(context, onLoaded, onError, show); } - public static void LoadVrmAsync(VRMImporterContext ctx, Action onLoaded, Action onError = null) + public static void LoadVrmAsync(VRMImporterContext ctx, Action onLoaded, Action onError = null, bool show = true) { if (onError == null) { onError = Debug.LogError; } - LoadVrmAsyncInternal(ctx) + LoadVrmAsyncInternal(ctx, show) .Subscribe(Scheduler.MainThread, onLoaded, onError); } - private static Schedulable LoadVrmAsyncInternal(VRMImporterContext ctx) + private static Schedulable LoadVrmAsyncInternal(VRMImporterContext ctx, bool show) { var schedulable = Schedulable.Create(); return schedulable .AddTask(Scheduler.ThreadPool, () => - { - ctx.GLTF.baseDir = Path.GetDirectoryName(ctx.Path); - return Unit.Default; - }) - .ContinueWith(Scheduler.CurrentThread, _ => { return glTF_VRM_Material.Parse(ctx.Json); }) @@ -588,18 +562,12 @@ namespace VRM .ContinueWith(Scheduler.CurrentThread, _ => { - /* - Debug.LogFormat("task end: {0}/{1}/{2}/{3}", - ctx.Textures.Count, - ctx.Materials.Count, - ctx.Meshes.Count, - ctx.Nodes.Count - ); - */ - ctx.Root.name = Path.GetFileNameWithoutExtension(ctx.Path); + ctx.Root.name = "VRM"; - // 非表示のメッシュを表示する - ctx.ShowMeshes(); + if (show) + { + ctx.ShowMeshes(); + } return ctx.Root; }); diff --git a/Scripts/Format/VRMImporterContext.cs b/Scripts/Format/VRMImporterContext.cs index a341fb1ee..a1c5a3506 100644 --- a/Scripts/Format/VRMImporterContext.cs +++ b/Scripts/Format/VRMImporterContext.cs @@ -7,13 +7,8 @@ namespace VRM { public class VRMImporterContext : ImporterContext { - public VRMImporterContext() + public VRMImporterContext(UnityPath gltfPath = default(UnityPath)) : base(gltfPath) { - - } - public VRMImporterContext(string path) - { - Path = path; } public UniHumanoid.AvatarDescription AvatarDescription; @@ -21,26 +16,13 @@ namespace VRM public BlendShapeAvatar BlendShapeAvatar; public VRMMetaObject Meta; - public glTF_VRM VRM + public VRMMetaObject ReadMeta(bool createThumbnail = false) { - get - { - return (glTF_VRM)GLTF; - } - } - - public void ParseVrm(byte[] bytes) - { - ParseGlb(bytes); - } - - public VRMMetaObject ReadMeta(bool createThumbnail=false) - { - var meta=ScriptableObject.CreateInstance(); + var meta = ScriptableObject.CreateInstance(); meta.name = "Meta"; - meta.ExporterVersion = VRM.extensions.VRM.exporterVersion; + meta.ExporterVersion = GLTF.extensions.VRM.exporterVersion; - var gltfMeta = VRM.extensions.VRM.meta; + var gltfMeta = GLTF.extensions.VRM.meta; meta.Version = gltfMeta.version; // model version meta.Author = gltfMeta.author; meta.ContactInformation = gltfMeta.contactInformation; @@ -55,11 +37,11 @@ namespace VRM else if (createThumbnail) { // 作成する(先行ロード用) - if(gltfMeta.texture >= 0 && gltfMeta.texture < VRM.textures.Count) + if (gltfMeta.texture >= 0 && gltfMeta.texture < GLTF.textures.Count) { - var t = new TextureItem(VRM, gltfMeta.texture); - t.Process(VRM, Storage); - meta.Thumbnail=t.Texture; + var t = new TextureItem(GLTF, gltfMeta.texture); + t.Process(GLTF, Storage); + meta.Thumbnail = t.Texture; } } diff --git a/Scripts/Format/VRMVersion.cs b/Scripts/Format/VRMVersion.cs index f356223ab..dea8ea797 100644 --- a/Scripts/Format/VRMVersion.cs +++ b/Scripts/Format/VRMVersion.cs @@ -4,11 +4,8 @@ namespace VRM public static partial class VRMVersion { public const int MAJOR = 0; - public const int MINOR = 40; + public const int MINOR = 41; - public const string VERSION = "0.40"; - - public const string DecrementMenuName = "VRM/Version(0.40) Decrement"; - public const string IncrementMenuName = "VRM/Version(0.40) Increment"; + public const string VERSION = "0.41"; } } diff --git a/Scripts/Format/VRMVersionPartial.cs b/Scripts/Format/VRMVersionPartial.cs index 623d3df01..764ff79f6 100644 --- a/Scripts/Format/VRMVersionPartial.cs +++ b/Scripts/Format/VRMVersionPartial.cs @@ -47,5 +47,7 @@ namespace VRM return false; } } + + public const string VRM_VERSION = "UniVRM-" + VERSION; } } diff --git a/Scripts/Format/glTF_VRM_BlendShape.cs b/Scripts/Format/glTF_VRM_BlendShape.cs index 2ace99d70..1026b4485 100644 --- a/Scripts/Format/glTF_VRM_BlendShape.cs +++ b/Scripts/Format/glTF_VRM_BlendShape.cs @@ -14,7 +14,7 @@ namespace VRM public string propertyName; public float[] targetValue; - protected override void SerializeMembers(JsonFormatter f) + protected override void SerializeMembers(GLTFJsonFormatter f) { f.KeyValue(() => materialName); f.KeyValue(() => propertyName); @@ -29,7 +29,7 @@ namespace VRM public int index = -1; public float weight = 0; - protected override void SerializeMembers(UniGLTF.JsonFormatter f) + protected override void SerializeMembers(GLTFJsonFormatter f) { f.KeyValue(() => mesh); f.KeyValue(() => index); @@ -90,7 +90,7 @@ namespace VRM public List binds = new List(); public List materialValues = new List(); - protected override void SerializeMembers(JsonFormatter f) + protected override void SerializeMembers(GLTFJsonFormatter f) { f.KeyValue(() => name); f.KeyValue(() => presetName); @@ -133,7 +133,7 @@ namespace VRM blendShapeGroups.Add(group); } - protected override void SerializeMembers(UniGLTF.JsonFormatter f) + protected override void SerializeMembers(GLTFJsonFormatter f) { f.KeyValue(() => blendShapeGroups); } diff --git a/Scripts/Format/glTF_VRM_FirstPerson.cs b/Scripts/Format/glTF_VRM_FirstPerson.cs index 6ce2e017d..9e7be4cf2 100644 --- a/Scripts/Format/glTF_VRM_FirstPerson.cs +++ b/Scripts/Format/glTF_VRM_FirstPerson.cs @@ -15,7 +15,7 @@ namespace VRM public float xRange = 90.0f; public float yRange = 10.0f; - protected override void SerializeMembers(JsonFormatter f) + protected override void SerializeMembers(GLTFJsonFormatter f) { if (curve != null) { @@ -48,7 +48,7 @@ namespace VRM public string firstPersonFlag; - protected override void SerializeMembers(JsonFormatter f) + protected override void SerializeMembers(GLTFJsonFormatter f) { f.KeyValue(() => mesh); f.KeyValue(() => firstPersonFlag); @@ -85,7 +85,7 @@ namespace VRM public glTF_VRM_DegreeMap lookAtVerticalDown = new glTF_VRM_DegreeMap(); public glTF_VRM_DegreeMap lookAtVerticalUp = new glTF_VRM_DegreeMap(); - protected override void SerializeMembers(JsonFormatter f) + protected override void SerializeMembers(GLTFJsonFormatter f) { f.KeyValue(() => firstPersonBone); f.KeyValue(() => firstPersonBoneOffset); diff --git a/Scripts/Format/glTF_VRM_Humanoid.cs b/Scripts/Format/glTF_VRM_Humanoid.cs index d0426934c..50a1b3853 100644 --- a/Scripts/Format/glTF_VRM_Humanoid.cs +++ b/Scripts/Format/glTF_VRM_Humanoid.cs @@ -111,7 +111,7 @@ namespace VRM public Vector3 center; public float axisLength; - protected override void SerializeMembers(JsonFormatter f) + protected override void SerializeMembers(GLTFJsonFormatter f) { f.Key("bone"); f.Value((string)bone.ToString()); f.KeyValue(() => node); @@ -158,7 +158,7 @@ namespace VRM } } - protected override void SerializeMembers(JsonFormatter f) + protected override void SerializeMembers(GLTFJsonFormatter f) { f.KeyValue(() => humanBones); f.KeyValue(() => armStretch); diff --git a/Scripts/Format/glTF_VRM_Material.cs b/Scripts/Format/glTF_VRM_Material.cs index 7e85cbdfc..de1cc87b2 100644 --- a/Scripts/Format/glTF_VRM_Material.cs +++ b/Scripts/Format/glTF_VRM_Material.cs @@ -3,19 +3,17 @@ using System.Collections.Generic; using System.Linq; using UniGLTF; using UnityEngine; -#if UNITY_EDITOR -using UnityEditor; -#endif +using UniJSON; namespace VRM { [Serializable] - public class glTF_VRM_Material : UniGLTF.JsonSerializableBase + public class glTF_VRM_Material : JsonSerializableBase { public string name; public string shader; - public int renderQueue=-1; + public int renderQueue = -1; public Dictionary floatProperties = new Dictionary(); public Dictionary vectorProperties = new Dictionary(); @@ -28,7 +26,7 @@ namespace VRM // "Queue", }; - protected override void SerializeMembers(JsonFormatter f) + protected override void SerializeMembers(GLTFJsonFormatter f) { f.KeyValue(() => name); f.KeyValue(() => renderQueue); @@ -59,7 +57,7 @@ namespace VRM } { f.Key("keywordMap"); f.BeginMap(); - foreach(var kv in keywordMap) + foreach (var kv in keywordMap) { f.Key(kv.Key); f.Value(kv.Value); } @@ -77,17 +75,17 @@ namespace VRM public static List Parse(string src) { - var json = src.ParseAsJson()["extensions"]["VRM"]["materialProperties"]; + var json = UniJSON.JsonParser.Parse(src)["extensions"]["VRM"]["materialProperties"]; var materials = json.DeserializeList(); - var jsonItems = json.ListItems.ToArray(); - for(int i=0; i x.Key, x => x.Value.GetSingle()); + materials[i].floatProperties = + jsonItems[i]["floatProperties"].ObjectItems.ToDictionary(x => x.Key, x => x.Value.Value.GetSingle()); materials[i].vectorProperties = jsonItems[i]["vectorProperties"].ObjectItems.ToDictionary(x => x.Key, x => { - return x.Value.ListItems.Select(y => y.GetSingle()).ToArray(); + return x.Value.ArrayItems.Select(y => y.Value.GetSingle()).ToArray(); }); materials[i].keywordMap = jsonItems[i]["keywordMap"].ObjectItems.ToDictionary(x => x.Key, x => x.Value.GetBoolean()); @@ -108,73 +106,87 @@ namespace VRM renderQueue = m.renderQueue, }; -#if UNITY_EDITOR - for (int i = 0; i < ShaderUtil.GetPropertyCount(m.shader); ++i) + var prop = VRMPreShaderPropExporter.GetPropsForSupportedShader(m.shader.name); + if (prop == null) { - var name = ShaderUtil.GetPropertyName(m.shader, i); - var propType = ShaderUtil.GetPropertyType(m.shader, i); - switch (propType) - { - case ShaderUtil.ShaderPropertyType.Color: - { - var value = m.GetColor(name).ToArray(); - material.vectorProperties.Add(name, value); - } - break; - - case ShaderUtil.ShaderPropertyType.Range: - case ShaderUtil.ShaderPropertyType.Float: - { - var value = m.GetFloat(name); - material.floatProperties.Add(name, value); - } - break; - - case ShaderUtil.ShaderPropertyType.TexEnv: - { - var texture = m.GetTexture(name); - if (texture != null) - { - var value = textures.IndexOf(texture); - if (value == -1) - { - Debug.LogFormat("not found {0}", texture.name); - } - else - { - material.textureProperties.Add(name, value); - } - } - - // offset & scaling - var offset = m.GetTextureOffset(name); - var scaling = m.GetTextureScale(name); - material.vectorProperties.Add(name, - new float[] { offset.x, offset.y, scaling.x, scaling.y }); - } - break; - - case ShaderUtil.ShaderPropertyType.Vector: - { - var value = m.GetVector(name).ToArray(); - material.vectorProperties.Add(name, value); - } - break; - - default: - throw new NotImplementedException(); - } - } -#else - Debug.LogWarning("cannot export material properties on runtime"); +#if UNITY_EDITOR + // fallback + Debug.LogWarningFormat("Unsupported shader: {0}", m.shader.name); + prop = VRMPreShaderPropExporter.ShaderProps.FromShader(m.shader); #endif + } + + if (prop == null) + { + Debug.LogWarningFormat("Fail to export shader: {0}", m.shader.name); + } + else + { + // get properties + //material.SetProp(prop); + foreach (var kv in prop.Properties) + { + switch (kv.Value) + { + case VRMPreShaderPropExporter.ShaderPropertyType.Color: + { + var value = m.GetColor(kv.Key).ToArray(); + material.vectorProperties.Add(kv.Key, value); + } + break; + + case VRMPreShaderPropExporter.ShaderPropertyType.Range: + case VRMPreShaderPropExporter.ShaderPropertyType.Float: + { + var value = m.GetFloat(kv.Key); + material.floatProperties.Add(kv.Key, value); + } + break; + + case VRMPreShaderPropExporter.ShaderPropertyType.TexEnv: + { + var texture = m.GetTexture(kv.Key); + if (texture != null) + { + var value = textures.IndexOf(texture); + if (value == -1) + { + Debug.LogFormat("not found {0}", texture.name); + } + else + { + material.textureProperties.Add(kv.Key, value); + } + } + + // offset & scaling + var offset = m.GetTextureOffset(kv.Key); + var scaling = m.GetTextureScale(kv.Key); + material.vectorProperties.Add(kv.Key, + new float[] { offset.x, offset.y, scaling.x, scaling.y }); + } + break; + + case VRMPreShaderPropExporter.ShaderPropertyType.Vector: + { + var value = m.GetVector(kv.Key).ToArray(); + material.vectorProperties.Add(kv.Key, value); + } + break; + + default: + throw new NotImplementedException(); + } + } + + } foreach (var keyword in m.shaderKeywords) { material.keywordMap.Add(keyword, m.IsKeywordEnabled(keyword)); } - foreach(var tag in TAGS) + foreach (var tag in TAGS) { var value = m.GetTag(tag, false); if (!String.IsNullOrEmpty(value)) diff --git a/Scripts/Format/glTF_VRM_Meta.cs b/Scripts/Format/glTF_VRM_Meta.cs index 00fc79eb4..0907ab23a 100644 --- a/Scripts/Format/glTF_VRM_Meta.cs +++ b/Scripts/Format/glTF_VRM_Meta.cs @@ -98,7 +98,7 @@ namespace VRM public string otherLicenseUrl; #endregion - protected override void SerializeMembers(JsonFormatter f) + protected override void SerializeMembers(GLTFJsonFormatter f) { f.KeyValue(() => version); diff --git a/Scripts/Format/glTF_VRM_SecondaryAnimation.cs b/Scripts/Format/glTF_VRM_SecondaryAnimation.cs index 7e8117b84..7a35b4f31 100644 --- a/Scripts/Format/glTF_VRM_SecondaryAnimation.cs +++ b/Scripts/Format/glTF_VRM_SecondaryAnimation.cs @@ -12,7 +12,7 @@ namespace VRM public Vector3 offset; public float radius; - protected override void SerializeMembers(JsonFormatter f) + protected override void SerializeMembers(GLTFJsonFormatter f) { f.KeyValue(() => offset); f.KeyValue(() => radius); @@ -25,7 +25,7 @@ namespace VRM public int node; public List colliders = new List(); - protected override void SerializeMembers(JsonFormatter f) + protected override void SerializeMembers(GLTFJsonFormatter f) { f.KeyValue(() => node); f.KeyValue(() => colliders); @@ -46,7 +46,7 @@ namespace VRM public int[] bones = new int[] { }; public int[] colliderGroups = new int[] { }; - protected override void SerializeMembers(JsonFormatter f) + protected override void SerializeMembers(GLTFJsonFormatter f) { f.KeyValue(() => comment); f.KeyValue(() => stiffiness); @@ -66,7 +66,7 @@ namespace VRM public List boneGroups = new List(); public List colliderGroups = new List(); - protected override void SerializeMembers(JsonFormatter f) + protected override void SerializeMembers(GLTFJsonFormatter f) { f.KeyValue(() => boneGroups); f.KeyValue(() => colliderGroups); diff --git a/Scripts/LookAt/VRMLookAtBlendShapeApplyer.cs b/Scripts/LookAt/VRMLookAtBlendShapeApplyer.cs index a53ba3061..07d3be922 100644 --- a/Scripts/LookAt/VRMLookAtBlendShapeApplyer.cs +++ b/Scripts/LookAt/VRMLookAtBlendShapeApplyer.cs @@ -19,7 +19,7 @@ namespace VRM public void OnImported(VRMImporterContext context) { - var gltfFirstPerson = context.VRM.extensions.VRM.firstPerson; + var gltfFirstPerson = context.GLTF.extensions.VRM.firstPerson; Horizontal.Apply(gltfFirstPerson.lookAtHorizontalOuter); VerticalDown.Apply(gltfFirstPerson.lookAtVerticalDown); VerticalUp.Apply(gltfFirstPerson.lookAtVerticalUp); @@ -45,27 +45,27 @@ namespace VRM if (yaw < 0) { // Left + m_propxy.SetValue(BlendShapePreset.LookRight, 0); // clear first m_propxy.SetValue(BlendShapePreset.LookLeft, Horizontal.Map(-yaw)); - m_propxy.SetValue(BlendShapePreset.LookRight, 0); } else { // Right - m_propxy.SetValue(BlendShapePreset.LookLeft, 0); + m_propxy.SetValue(BlendShapePreset.LookLeft, 0); // clear first m_propxy.SetValue(BlendShapePreset.LookRight, Horizontal.Map(yaw)); } if (pitch < 0) { // Down - m_propxy.SetValue(BlendShapePreset.LookUp, 0); + m_propxy.SetValue(BlendShapePreset.LookUp, 0); // clear first m_propxy.SetValue(BlendShapePreset.LookDown, VerticalDown.Map(-pitch)); } else { // Up + m_propxy.SetValue(BlendShapePreset.LookDown, 0); // clear first m_propxy.SetValue(BlendShapePreset.LookUp, VerticalUp.Map(pitch)); - m_propxy.SetValue(BlendShapePreset.LookDown, 0); } } } diff --git a/Scripts/LookAt/VRMLookAtBoneApplyer.cs b/Scripts/LookAt/VRMLookAtBoneApplyer.cs index d6588820e..9acd842a9 100644 --- a/Scripts/LookAt/VRMLookAtBoneApplyer.cs +++ b/Scripts/LookAt/VRMLookAtBoneApplyer.cs @@ -36,7 +36,7 @@ namespace VRM RightEye = OffsetOnTransform.Create(animator.GetBoneTransform(HumanBodyBones.RightEye)); } - var gltfFirstPerson = context.VRM.extensions.VRM.firstPerson; + var gltfFirstPerson = context.GLTF.extensions.VRM.firstPerson; HorizontalInner.Apply(gltfFirstPerson.lookAtHorizontalInner); HorizontalOuter.Apply(gltfFirstPerson.lookAtHorizontalOuter); VerticalDown.Apply(gltfFirstPerson.lookAtVerticalDown); diff --git a/Scripts/LookAt/VRMLookAtHead.cs b/Scripts/LookAt/VRMLookAtHead.cs index 82bfc983a..2888ba870 100644 --- a/Scripts/LookAt/VRMLookAtHead.cs +++ b/Scripts/LookAt/VRMLookAtHead.cs @@ -90,7 +90,7 @@ namespace VRM Head = OffsetOnTransform.Create(head); - var gltfFirstPerson = context.VRM.extensions.VRM.firstPerson; + var gltfFirstPerson = context.GLTF.extensions.VRM.firstPerson; switch (gltfFirstPerson.lookAtType) { case LookAtType.Bone: diff --git a/Scripts/PreExportShaderProps.meta b/Scripts/PreExportShaderProps.meta new file mode 100644 index 000000000..c49cf8b4c --- /dev/null +++ b/Scripts/PreExportShaderProps.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 3fa98374d2ce76443981cc61ab80e6ce +folderAsset: yes +timeCreated: 1533035745 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/PreExportShaderProps/VRMPreShaderPropExporter.cs b/Scripts/PreExportShaderProps/VRMPreShaderPropExporter.cs new file mode 100644 index 000000000..7cff9cdb6 --- /dev/null +++ b/Scripts/PreExportShaderProps/VRMPreShaderPropExporter.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using UniGLTF; +using UnityEngine; +#if UNITY_EDITOR +using UnityEditor; +#endif + + +namespace VRM +{ + public class VRMPreExportShaderAttribute : Attribute { } + + public static partial class VRMPreShaderPropExporter + { + public static string[] SupportedShaders = new string[] + { + "VRM/MToon", + "VRM/UnlitTexture", + "VRM/UnlitCutout", + "VRM/UnlitTransparent", + "VRM/UnlitTransparentZWrite", + }; + +#if UNITY_EDITOR + [MenuItem(VRMVersion.VRM_VERSION + "/PreExport ShaderProps")] + static void PreExport() + { + foreach (var shaderName in SupportedShaders) + { + var shader = Shader.Find(shaderName); + PreExport(shader); + } + } + + static string EscapeShaderName(string name) + { + return name.Replace("/", "_").Replace(" ", "_"); + } + + const string EXPORT_DIR = "Assets/VRM/Scripts/PreExportShaderProps/"; + static void PreExport(Shader shader) + { + var path = UnityPath.FromUnityPath(EXPORT_DIR + EscapeShaderName(shader.name) + ".cs"); + Debug.LogFormat("PreExport: {0}", path.FullPath); + + var props = ShaderProps.FromShader(shader); + + File.WriteAllText(path.FullPath, props.ToString(shader.name)); + } +#endif + + public enum ShaderPropertyType + { + TexEnv, + Color, + Range, + Float, + Vector, + } + + public class ShaderProps + { + public KeyValuePair[] Properties; + +#if UNITY_EDITOR + static ShaderPropertyType ConvType(ShaderUtil.ShaderPropertyType src) + { + switch (src) + { + case ShaderUtil.ShaderPropertyType.TexEnv: return ShaderPropertyType.TexEnv; + case ShaderUtil.ShaderPropertyType.Color: return ShaderPropertyType.Color; + case ShaderUtil.ShaderPropertyType.Float: return ShaderPropertyType.Float; + case ShaderUtil.ShaderPropertyType.Range: return ShaderPropertyType.Range; + case ShaderUtil.ShaderPropertyType.Vector: return ShaderPropertyType.Vector; + default: throw new NotImplementedException(); + } + } + + public static ShaderProps FromShader(Shader shader) + { + var properties = new List>(); + for (int i = 0; i < ShaderUtil.GetPropertyCount(shader); ++i) + { + var name = ShaderUtil.GetPropertyName(shader, i); + var propType = ShaderUtil.GetPropertyType(shader, i); + properties.Add(new KeyValuePair(name, ConvType(propType))); + } + + return new ShaderProps + { + Properties = properties.ToArray(), + }; + } + + public string ToString(string shaderName) + { + var list = new List(); + foreach(var kv in Properties) + { + list.Add(string.Format("new KeyValuePair(\"{0}\", ShaderPropertyType.{1})\r\n", kv.Key, kv.Value)); + } + + return string.Format(@"using System.Collections.Generic; + + +namespace VRM +{{ + public static partial class VRMPreShaderPropExporter + {{ + [VRMPreExportShader] + static KeyValuePair {0} + {{ + get + {{ + return new KeyValuePair( + ""{1}"", + new ShaderProps + {{ + Properties = new KeyValuePair[]{{ +{2} + }} + }} + ); + }} + }} + }} +}} +" +, EscapeShaderName(shaderName) +, shaderName +, String.Join(",", list.ToArray())); + } +#endif + } + + #region Runtime + static Dictionary m_shaderPropMap; + + public static ShaderProps GetPropsForSupportedShader(string shaderName) + { + if (m_shaderPropMap == null) + { + m_shaderPropMap = new Dictionary(); + foreach (var prop in typeof(VRMPreShaderPropExporter).GetProperties(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) + { + if (prop.GetCustomAttributes(typeof(VRMPreExportShaderAttribute), true).Any()) + { + var kv = (KeyValuePair)prop.GetValue(null, null); + m_shaderPropMap.Add(kv.Key, kv.Value); + } + } + } + + ShaderProps props; + if (!m_shaderPropMap.TryGetValue(shaderName, out props)) + { + return null; + } + + return props; + } + #endregion + } +} diff --git a/Scripts/PreExportShaderProps/VRMPreShaderPropExporter.cs.meta b/Scripts/PreExportShaderProps/VRMPreShaderPropExporter.cs.meta new file mode 100644 index 000000000..431e9cca2 --- /dev/null +++ b/Scripts/PreExportShaderProps/VRMPreShaderPropExporter.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 50935dd2f9f3fa445a687f30d4dd663b +timeCreated: 1533035131 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/PreExportShaderProps/VRM_MToon.cs b/Scripts/PreExportShaderProps/VRM_MToon.cs new file mode 100644 index 000000000..4bfc0f263 --- /dev/null +++ b/Scripts/PreExportShaderProps/VRM_MToon.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; + + +namespace VRM +{ + public static partial class VRMPreShaderPropExporter + { + [VRMPreExportShader] + static KeyValuePair VRM_MToon + { + get + { + return new KeyValuePair( + "VRM/MToon", + new ShaderProps + { + Properties = new KeyValuePair[]{ +new KeyValuePair("_Cutoff", ShaderPropertyType.Range) +,new KeyValuePair("_Color", ShaderPropertyType.Color) +,new KeyValuePair("_ShadeColor", ShaderPropertyType.Color) +,new KeyValuePair("_MainTex", ShaderPropertyType.TexEnv) +,new KeyValuePair("_ShadeTexture", ShaderPropertyType.TexEnv) +,new KeyValuePair("_BumpScale", ShaderPropertyType.Float) +,new KeyValuePair("_BumpMap", ShaderPropertyType.TexEnv) +,new KeyValuePair("_ReceiveShadowRate", ShaderPropertyType.Range) +,new KeyValuePair("_ReceiveShadowTexture", ShaderPropertyType.TexEnv) +,new KeyValuePair("_ShadeShift", ShaderPropertyType.Range) +,new KeyValuePair("_ShadeToony", ShaderPropertyType.Range) +,new KeyValuePair("_LightColorAttenuation", ShaderPropertyType.Range) +,new KeyValuePair("_SphereAdd", ShaderPropertyType.TexEnv) +,new KeyValuePair("_EmissionColor", ShaderPropertyType.Color) +,new KeyValuePair("_EmissionMap", ShaderPropertyType.TexEnv) +,new KeyValuePair("_OutlineWidthTexture", ShaderPropertyType.TexEnv) +,new KeyValuePair("_OutlineWidth", ShaderPropertyType.Range) +,new KeyValuePair("_OutlineScaledMaxDistance", ShaderPropertyType.Range) +,new KeyValuePair("_OutlineColor", ShaderPropertyType.Color) +,new KeyValuePair("_OutlineLightingMix", ShaderPropertyType.Range) +,new KeyValuePair("_DebugMode", ShaderPropertyType.Float) +,new KeyValuePair("_BlendMode", ShaderPropertyType.Float) +,new KeyValuePair("_OutlineWidthMode", ShaderPropertyType.Float) +,new KeyValuePair("_OutlineColorMode", ShaderPropertyType.Float) +,new KeyValuePair("_CullMode", ShaderPropertyType.Float) +,new KeyValuePair("_OutlineCullMode", ShaderPropertyType.Float) +,new KeyValuePair("_SrcBlend", ShaderPropertyType.Float) +,new KeyValuePair("_DstBlend", ShaderPropertyType.Float) +,new KeyValuePair("_ZWrite", ShaderPropertyType.Float) +,new KeyValuePair("_IsFirstSetup", ShaderPropertyType.Float) + + } + } + ); + } + } + } +} diff --git a/Scripts/PreExportShaderProps/VRM_MToon.cs.meta b/Scripts/PreExportShaderProps/VRM_MToon.cs.meta new file mode 100644 index 000000000..fd900fccd --- /dev/null +++ b/Scripts/PreExportShaderProps/VRM_MToon.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 4629d794c8969c141a4724e182af082e +timeCreated: 1533041756 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/PreExportShaderProps/VRM_UnlitCutout.cs b/Scripts/PreExportShaderProps/VRM_UnlitCutout.cs new file mode 100644 index 000000000..dedfa93bf --- /dev/null +++ b/Scripts/PreExportShaderProps/VRM_UnlitCutout.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; + + +namespace VRM +{ + public static partial class VRMPreShaderPropExporter + { + [VRMPreExportShader] + static KeyValuePair VRM_UnlitCutout + { + get + { + return new KeyValuePair( + "VRM/UnlitCutout", + new ShaderProps + { + Properties = new KeyValuePair[]{ +new KeyValuePair("_MainTex", ShaderPropertyType.TexEnv) +,new KeyValuePair("_Cutoff", ShaderPropertyType.Range) + + } + } + ); + } + } + } +} diff --git a/Scripts/PreExportShaderProps/VRM_UnlitCutout.cs.meta b/Scripts/PreExportShaderProps/VRM_UnlitCutout.cs.meta new file mode 100644 index 000000000..e8189be1c --- /dev/null +++ b/Scripts/PreExportShaderProps/VRM_UnlitCutout.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 611b546ea471ad34cb7d94740c63b558 +timeCreated: 1533041756 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/PreExportShaderProps/VRM_UnlitTexture.cs b/Scripts/PreExportShaderProps/VRM_UnlitTexture.cs new file mode 100644 index 000000000..b5ba277e6 --- /dev/null +++ b/Scripts/PreExportShaderProps/VRM_UnlitTexture.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + + +namespace VRM +{ + public static partial class VRMPreShaderPropExporter + { + [VRMPreExportShader] + static KeyValuePair VRM_UnlitTexture + { + get + { + return new KeyValuePair( + "VRM/UnlitTexture", + new ShaderProps + { + Properties = new KeyValuePair[]{ +new KeyValuePair("_MainTex", ShaderPropertyType.TexEnv) + + } + } + ); + } + } + } +} diff --git a/Scripts/PreExportShaderProps/VRM_UnlitTexture.cs.meta b/Scripts/PreExportShaderProps/VRM_UnlitTexture.cs.meta new file mode 100644 index 000000000..66b977400 --- /dev/null +++ b/Scripts/PreExportShaderProps/VRM_UnlitTexture.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 22a8083880389b3498f421e6a5c340d5 +timeCreated: 1533041756 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/PreExportShaderProps/VRM_UnlitTransparent.cs b/Scripts/PreExportShaderProps/VRM_UnlitTransparent.cs new file mode 100644 index 000000000..ef5d8fe7a --- /dev/null +++ b/Scripts/PreExportShaderProps/VRM_UnlitTransparent.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + + +namespace VRM +{ + public static partial class VRMPreShaderPropExporter + { + [VRMPreExportShader] + static KeyValuePair VRM_UnlitTransparent + { + get + { + return new KeyValuePair( + "VRM/UnlitTransparent", + new ShaderProps + { + Properties = new KeyValuePair[]{ +new KeyValuePair("_MainTex", ShaderPropertyType.TexEnv) + + } + } + ); + } + } + } +} diff --git a/Scripts/PreExportShaderProps/VRM_UnlitTransparent.cs.meta b/Scripts/PreExportShaderProps/VRM_UnlitTransparent.cs.meta new file mode 100644 index 000000000..c86b4cc0f --- /dev/null +++ b/Scripts/PreExportShaderProps/VRM_UnlitTransparent.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 140d6538826e0eb448929d3e4bb2f1cd +timeCreated: 1533041756 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/PreExportShaderProps/VRM_UnlitTransparentZWrite.cs b/Scripts/PreExportShaderProps/VRM_UnlitTransparentZWrite.cs new file mode 100644 index 000000000..68dcd5f7d --- /dev/null +++ b/Scripts/PreExportShaderProps/VRM_UnlitTransparentZWrite.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + + +namespace VRM +{ + public static partial class VRMPreShaderPropExporter + { + [VRMPreExportShader] + static KeyValuePair VRM_UnlitTransparentZWrite + { + get + { + return new KeyValuePair( + "VRM/UnlitTransparentZWrite", + new ShaderProps + { + Properties = new KeyValuePair[]{ +new KeyValuePair("_MainTex", ShaderPropertyType.TexEnv) + + } + } + ); + } + } + } +} diff --git a/Scripts/PreExportShaderProps/VRM_UnlitTransparentZWrite.cs.meta b/Scripts/PreExportShaderProps/VRM_UnlitTransparentZWrite.cs.meta new file mode 100644 index 000000000..559b57d77 --- /dev/null +++ b/Scripts/PreExportShaderProps/VRM_UnlitTransparentZWrite.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 165ec79b7aac1564a850fb3d3d19396e +timeCreated: 1533041756 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/SkinnedMeshUtility/BoneNormalizer.cs b/Scripts/SkinnedMeshUtility/BoneNormalizer.cs new file mode 100644 index 000000000..e5c77acdb --- /dev/null +++ b/Scripts/SkinnedMeshUtility/BoneNormalizer.cs @@ -0,0 +1,392 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UniHumanoid; +using UnityEngine; + + +namespace VRM +{ + public static class BoneNormalizer + { + /// + /// 回転とスケールを除去したヒエラルキーをコピーする + /// + /// + /// + static void CopyAndBuild(Transform src, Transform dst, Dictionary boneMap) + { + boneMap[src] = dst; + + foreach (Transform child in src) + { + if (child.gameObject.activeSelf) + { + var dstChild = new GameObject(child.name); + dstChild.transform.SetParent(dst); + dstChild.transform.position = child.position; // copy position only + + CopyAndBuild(child, dstChild.transform, boneMap); + } + } + } + + static IEnumerable Traverse(this Transform t) + { + yield return t; + foreach (Transform child in t) + { + foreach (var x in child.Traverse()) + { + yield return x; + } + } + } + + static void EnforceTPose(GameObject go) + { + var animator = go.GetComponent(); + if (animator == null) + { + throw new ArgumentException("Animator with avatar is required"); + } + + var avatar = animator.avatar; + if (avatar == null) + { + throw new ArgumentException("avatar is required"); + } + + if (!avatar.isValid) + { + throw new ArgumentException("invalid avatar"); + } + + if (!avatar.isHuman) + { + throw new ArgumentException("avatar is not human"); + } + + HumanPoseTransfer.SetTPose(avatar, go.transform); + } + + static GameObject NormalizeHierarchy(GameObject go, Dictionary boneMap) + { + // + // 回転・スケールの無いヒエラルキーをコピーする + // + var normalized = new GameObject(go.name + "(normalized)"); + normalized.transform.position = go.transform.position; + CopyAndBuild(go.transform, normalized.transform, boneMap); + + // + // 新しいヒエラルキーからAvatarを作る + // + { + var src = go.GetComponent(); + + var map = Enum.GetValues(typeof(HumanBodyBones)) + .Cast() + .Where(x => x != HumanBodyBones.LastBone) + .Select(x => new { Key = x, Value = src.GetBoneTransform(x) }) + .Where(x => x.Value != null) + .ToDictionary(x => x.Key, x => boneMap[x.Value]) + ; + + var animator = normalized.AddComponent(); + var vrmHuman = go.GetComponent(); + var avatarDescription = AvatarDescription.Create(); + if (vrmHuman != null && vrmHuman.Description != null) + { + avatarDescription.armStretch = vrmHuman.Description.armStretch; + avatarDescription.legStretch = vrmHuman.Description.legStretch; + avatarDescription.upperArmTwist = vrmHuman.Description.upperArmTwist; + avatarDescription.lowerArmTwist = vrmHuman.Description.lowerArmTwist; + avatarDescription.upperLegTwist = vrmHuman.Description.upperLegTwist; + avatarDescription.lowerLegTwist = vrmHuman.Description.lowerLegTwist; + avatarDescription.feetSpacing = vrmHuman.Description.feetSpacing; + avatarDescription.hasTranslationDoF = vrmHuman.Description.hasTranslationDoF; + } + avatarDescription.SetHumanBones(map); + var avatar = avatarDescription.CreateAvatar(normalized.transform); + + avatar.name = go.name + ".normalized"; + animator.avatar = avatar; + + var humanPoseTransfer = normalized.AddComponent(); + humanPoseTransfer.Avatar = avatar; + } + + return normalized; + } + + /// + /// srcのSkinnedMeshRendererを正規化して、dstにアタッチする + /// + /// 正規化前のSkinnedMeshRendererのTransform + /// 正規化後のSkinnedMeshRendererのTransform + /// 正規化前のボーンから正規化後のボーンを得る + static void NormalizeSkinnedMesh(Transform src, Transform dst, Dictionary boneMap) + { + var srcRenderer = src.GetComponent(); + if (srcRenderer == null + || !srcRenderer.enabled + || srcRenderer.sharedMesh == null + || srcRenderer.sharedMesh.vertexCount == 0) + { + // 有効なSkinnedMeshRendererが無かった + return; + } + + // clear blendShape + var srcMesh = srcRenderer.sharedMesh; + var originalSrcMesh = srcMesh; + for (int i = 0; i < srcMesh.blendShapeCount; ++i) + { + srcRenderer.SetBlendShapeWeight(i, 0); + } + + var bones = srcRenderer.bones.Select(x => boneMap[x]).ToArray(); + var hasBoneWeight = srcRenderer.bones!=null && srcRenderer.bones.Length > 0; + if (!hasBoneWeight) + { + // Before bake, bind no weight bones + //Debug.LogFormat("no weight: {0}", srcMesh.name); + + srcMesh = srcMesh.Copy(); + var bw = new BoneWeight + { + boneIndex0 = 0, + boneIndex1 = 0, + boneIndex2 = 0, + boneIndex3 = 0, + weight0 = 1.0f, + weight1 = 0.0f, + weight2 = 0.0f, + weight3 = 0.0f, + }; + srcMesh.boneWeights = Enumerable.Range(0, srcMesh.vertexCount).Select(x => bw).ToArray(); + srcMesh.bindposes = new Matrix4x4[] { Matrix4x4.identity }; + + srcRenderer.rootBone = srcRenderer.transform; + bones = new[] { boneMap[srcRenderer.transform] }; + srcRenderer.bones = new[] { srcRenderer.transform }; + srcRenderer.sharedMesh = srcMesh; + } + + // BakeMesh + var mesh = srcMesh.Copy(); + mesh.name = srcMesh.name + ".baked"; + srcRenderer.BakeMesh(mesh); + mesh.boneWeights = srcMesh.boneWeights; // restore weights. clear when BakeMesh + // recalc bindposes + mesh.bindposes = bones.Select(x => x.worldToLocalMatrix * dst.transform.localToWorldMatrix).ToArray(); + + //var m = src.localToWorldMatrix; // include scaling + var m = default(Matrix4x4); + m.SetTRS(Vector3.zero, src.rotation, Vector3.one); // rotation only + mesh.ApplyMatrix(m); + + // + // BlendShapes + // + var meshVertices = mesh.vertices; + var meshNormals = mesh.normals; + var meshTangents = mesh.tangents.Select(x => (Vector3)x).ToArray(); + + var _meshVertices = new Vector3[meshVertices.Length]; + var _meshNormals = new Vector3[meshVertices.Length]; + var _meshTangents = new Vector3[meshVertices.Length]; + + var blendShapeMesh = new Mesh(); + for (int i = 0; i < srcMesh.blendShapeCount; ++i) + { + // check blendShape + srcRenderer.sharedMesh.GetBlendShapeFrameVertices(i, 0, _meshVertices, _meshNormals, _meshTangents); + var hasVertices = !_meshVertices.All(x => x == Vector3.zero); + var hasNormals = !_meshNormals.All(x => x == Vector3.zero); + var hasTangents = !_meshTangents.All(x => x == Vector3.zero); + + srcRenderer.SetBlendShapeWeight(i, 100.0f); + srcRenderer.BakeMesh(blendShapeMesh); + if (blendShapeMesh.vertices.Length != mesh.vertices.Length) + { + throw new Exception("diffrent vertex count"); + } + srcRenderer.SetBlendShapeWeight(i, 0); + + Vector3[] vertices = null; + if (hasVertices) + { + vertices = blendShapeMesh.vertices; + // to delta + for (int j = 0; j < vertices.Length; ++j) + { + vertices[j] = m.MultiplyPoint(vertices[j]) - meshVertices[j]; + } + } + else + { + vertices = new Vector3[mesh.vertexCount]; + } + + Vector3[] normals = null; + if (hasNormals) + { + normals = blendShapeMesh.normals; + // to delta + for (int j = 0; j < normals.Length; ++j) + { + normals[j] = m.MultiplyVector(normals[j]) - meshNormals[j]; + } + } + else + { + normals = new Vector3[mesh.vertexCount]; + } + + Vector3[] tangents = null; + if (hasTangents) + { + tangents = blendShapeMesh.tangents.Select(x => (Vector3)x).ToArray(); + // to delta + for (int j = 0; j < tangents.Length; ++j) + { + tangents[j] = m.MultiplyVector(tangents[j]) - meshTangents[j]; + } + } + else + { + tangents = new Vector3[mesh.vertexCount]; + } + + var name = srcMesh.GetBlendShapeName(i); + if (string.IsNullOrEmpty(name)) + { + name = String.Format("{0}", i); + } + + var weight = srcMesh.GetBlendShapeFrameWeight(i, 0); + + try + { + mesh.AddBlendShapeFrame(name, + weight, + vertices, + normals, + tangents + ); + } + catch (Exception) + { + Debug.LogErrorFormat("fail to mesh.AddBlendShapeFrame {0}.{1}", + mesh.name, + srcMesh.GetBlendShapeName(i) + ); + throw; + } + } + + var dstRenderer = dst.gameObject.AddComponent(); + dstRenderer.sharedMaterials = srcRenderer.sharedMaterials; + if (srcRenderer.rootBone != null) + { + dstRenderer.rootBone = boneMap[srcRenderer.rootBone]; + } + dstRenderer.bones = bones; + dstRenderer.sharedMesh = mesh; + + if (!hasBoneWeight) + { + // restore bones + srcRenderer.bones = new Transform[] { }; + srcRenderer.sharedMesh = originalSrcMesh; + } + } + + /// + /// + /// + /// + /// + static void NormalizeNoneSkinnedMesh(Transform src, Transform dst) + { + var srcFilter = src.GetComponent(); + if (srcFilter == null + || srcFilter.sharedMesh == null + || srcFilter.sharedMesh.vertexCount == 0) + { + return; + } + + var srcRenderer = src.GetComponent(); + if (srcRenderer == null || !srcRenderer.enabled) + { + return; + } + + // Meshに乗っているボーンの姿勢を適用する + var dstFilter = dst.gameObject.AddComponent(); + + var dstMesh = srcFilter.sharedMesh.Copy(); + dstMesh.ApplyRotationAndScale(src.localToWorldMatrix); + dstFilter.sharedMesh = dstMesh; + + // Materialをコピー + var dstRenderer = dst.gameObject.AddComponent(); + dstRenderer.sharedMaterials = srcRenderer.sharedMaterials; + } + + public struct NormalizedResult + { + public GameObject Root; + public Dictionary BoneMap; + } + + /// + /// モデルの正規化を実行する + /// + /// 対象モデルのルート + /// 強制的にT-Pose化するか + /// 正規化済みのモデル + public static NormalizedResult Execute(GameObject go, bool forceTPose) + { + Dictionary boneMap = new Dictionary(); + + // + // T-Poseにする + // + if (forceTPose) + { + EnforceTPose(go); + } + + // + // 正規化されたヒエラルキーを作る + // + var normalized = NormalizeHierarchy(go, boneMap); + + // + // 各メッシュから回転・スケールを取り除いてBinding行列を再計算する + // + foreach (var src in go.transform.Traverse()) + { + Transform dst; + if (!boneMap.TryGetValue(src, out dst)) + { + continue; + } + + NormalizeSkinnedMesh(src, dst, boneMap); + + NormalizeNoneSkinnedMesh(src, dst); + } + + return new NormalizedResult + { + Root = normalized, + BoneMap = boneMap + }; + } + } +} diff --git a/Scripts/SkinnedMeshUtility/Editor/BoneNormalizer.cs.meta b/Scripts/SkinnedMeshUtility/BoneNormalizer.cs.meta similarity index 100% rename from Scripts/SkinnedMeshUtility/Editor/BoneNormalizer.cs.meta rename to Scripts/SkinnedMeshUtility/BoneNormalizer.cs.meta diff --git a/Scripts/SkinnedMeshUtility/Editor/BoneNormalizer.cs b/Scripts/SkinnedMeshUtility/Editor/BoneNormalizer.cs deleted file mode 100644 index ad297ebf8..000000000 --- a/Scripts/SkinnedMeshUtility/Editor/BoneNormalizer.cs +++ /dev/null @@ -1,374 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using UniHumanoid; -using UnityEngine; - - -namespace VRM -{ - public static class BoneNormalizer - { - /// - /// 回転とスケールを除去したヒエラルキーをコピーする - /// - /// - /// - static void CopyAndBuild(Transform src, Transform dst, Dictionary boneMap) - { - boneMap[src] = dst; - - foreach (Transform child in src) - { - if (child.gameObject.activeSelf) - { - var dstChild = new GameObject(child.name); - dstChild.transform.SetParent(dst); - dstChild.transform.position = child.position; // copy position only - - CopyAndBuild(child, dstChild.transform, boneMap); - } - } - } - - static IEnumerable Traverse(this Transform t) - { - yield return t; - foreach (Transform child in t) - { - foreach (var x in child.Traverse()) - { - yield return x; - } - } - } - - public static GameObject Execute(GameObject go, Dictionary boneMap, bool forceTPose) - { - // - // T-Poseにする - // - if(forceTPose) - { - var animator = go.GetComponent(); - if (animator == null) - { - throw new ArgumentException("Animator with avatar is required"); - } - - var avatar = animator.avatar; - if (avatar == null) - { - throw new ArgumentException("avatar is required"); - } - - if (!avatar.isValid) - { - throw new ArgumentException("invalid avatar"); - } - - if (!avatar.isHuman) - { - throw new ArgumentException("avatar is not human"); - } - - HumanPoseTransfer.SetTPose(avatar, go.transform); - } - - // - // 回転・スケールの無いヒエラルキーをコピーする - // - var normalized = new GameObject(go.name + "(normalized)"); - normalized.transform.position = go.transform.position; - - CopyAndBuild(go.transform, normalized.transform, boneMap); - - // - // 新しいヒエラルキーからAvatarを作る - // - { - var src = go.GetComponent(); - - var map = Enum.GetValues(typeof(HumanBodyBones)) - .Cast() - .Where(x => x != HumanBodyBones.LastBone) - .Select(x => new { Key = x, Value = src.GetBoneTransform(x) }) - .Where(x => x.Value != null) - .ToDictionary(x => x.Key, x => boneMap[x.Value]) - ; - - var animator = normalized.AddComponent(); - var vrmHuman = go.GetComponent(); - var avatarDescription = AvatarDescription.Create(); - if (vrmHuman != null && vrmHuman.Description != null) - { - avatarDescription.armStretch = vrmHuman.Description.armStretch; - avatarDescription.legStretch = vrmHuman.Description.legStretch; - avatarDescription.upperArmTwist = vrmHuman.Description.upperArmTwist; - avatarDescription.lowerArmTwist = vrmHuman.Description.lowerArmTwist; - avatarDescription.upperLegTwist = vrmHuman.Description.upperLegTwist; - avatarDescription.lowerLegTwist = vrmHuman.Description.lowerLegTwist; - avatarDescription.feetSpacing = vrmHuman.Description.feetSpacing; - avatarDescription.hasTranslationDoF = vrmHuman.Description.hasTranslationDoF; - } - avatarDescription.SetHumanBones(map); - var avatar = avatarDescription.CreateAvatar(normalized.transform); - - avatar.name = go.name + ".normalized"; - animator.avatar = avatar; - - var humanPoseTransfer = normalized.AddComponent(); - humanPoseTransfer.Avatar = avatar; - } - - // - // 各メッシュから回転・スケールを取り除いてBinding行列を再計算する - // - foreach (var src in go.transform.Traverse()) - { - Transform dst; - if(!boneMap.TryGetValue(src, out dst)) - { - continue; - } - - { - // - // SkinnedMesh - // - var srcRenderer = src.GetComponent(); - if (srcRenderer != null && srcRenderer.enabled - && srcRenderer.sharedMesh!=null - && srcRenderer.sharedMesh.vertexCount>0) - { - // clear blendShape - var srcMesh = srcRenderer.sharedMesh; - for (int i = 0; i < srcMesh.blendShapeCount; ++i) - { - srcRenderer.SetBlendShapeWeight(i, 0); - } - - var mesh = new Mesh(); - mesh.name = srcMesh.name + ".baked"; -#if UNITY_2017_3_OR_NEWER - mesh.indexFormat = srcMesh.indexFormat; -#endif - - srcRenderer.BakeMesh(mesh); - - //var m = src.localToWorldMatrix; - var m = default(Matrix4x4); - m.SetTRS(Vector3.zero, src.rotation, Vector3.one); - - mesh.vertices = mesh.vertices.Select(x => m.MultiplyPoint(x)).ToArray(); - mesh.normals = mesh.normals.Select(x => m.MultiplyVector(x).normalized).ToArray(); - - mesh.uv = srcMesh.uv; - mesh.tangents = srcMesh.tangents; - mesh.subMeshCount = srcMesh.subMeshCount; - for (int i = 0; i < srcMesh.subMeshCount; ++i) - { - mesh.SetIndices(srcMesh.GetIndices(i), srcMesh.GetTopology(i), i); - } - - // boneweights - var bones = srcRenderer.bones.Select(x => boneMap[x]).ToArray(); - if (bones.Length > 0) - { - mesh.boneWeights = srcMesh.boneWeights; - } - else - { - Debug.LogFormat("no weight: {0}", srcMesh.name); - bones = new[] { boneMap[srcRenderer.transform] }; - var bw = new BoneWeight - { - boneIndex0 = 0, - boneIndex1 = 0, - boneIndex2 = 0, - boneIndex3 = 0, - weight0 = 1.0f, - weight1 = 0.0f, - weight2 = 0.0f, - weight3 = 0.0f, - }; - mesh.boneWeights = Enumerable.Range(0, srcMesh.vertexCount).Select(x => bw).ToArray(); - } - - var meshVertices = mesh.vertices; - var meshNormals = mesh.normals; - var meshTangents = mesh.tangents.Select(x => (Vector3)x).ToArray(); - - var _meshVertices = new Vector3[meshVertices.Length]; - var _meshNormals = new Vector3[meshVertices.Length]; - var _meshTangents = new Vector3[meshVertices.Length]; - - var blendShapeMesh = new Mesh(); - for (int i = 0; i < srcMesh.blendShapeCount; ++i) - { - // check blendShape - srcRenderer.sharedMesh.GetBlendShapeFrameVertices(i, 0, _meshVertices, _meshNormals, _meshTangents); - var hasVertices = !_meshVertices.All(x => x == Vector3.zero); - var hasNormals = !_meshNormals.All(x => x == Vector3.zero); - var hasTangents = !_meshTangents.All(x => x == Vector3.zero); - - srcRenderer.SetBlendShapeWeight(i, 100.0f); - srcRenderer.BakeMesh(blendShapeMesh); - if (blendShapeMesh.vertices.Length != mesh.vertices.Length) - { - throw new Exception("diffrent vertex count"); - } - srcRenderer.SetBlendShapeWeight(i, 0); - - Vector3[] vertices = null; - if (hasVertices) - { - vertices = blendShapeMesh.vertices; - // to delta - for (int j = 0; j < vertices.Length; ++j) - { - vertices[j] = m.MultiplyPoint(vertices[j]) - meshVertices[j]; - } - } - else - { - vertices = new Vector3[mesh.vertexCount]; - } - - Vector3[] normals = null; - if (hasNormals) - { - normals = blendShapeMesh.normals; - // to delta - for (int j = 0; j < normals.Length; ++j) - { - normals[j] = m.MultiplyVector(normals[j]) - meshNormals[j]; - } - } - else - { - normals = new Vector3[mesh.vertexCount]; - } - - Vector3[] tangents = null; - if (hasTangents) - { - tangents = blendShapeMesh.tangents.Select(x => (Vector3)x).ToArray(); - // to delta - for (int j = 0; j < tangents.Length; ++j) - { - tangents[j] = m.MultiplyVector(tangents[j]) - meshTangents[j]; - } - } - else - { - tangents = new Vector3[mesh.vertexCount]; - } - - var name = srcMesh.GetBlendShapeName(i); - if (string.IsNullOrEmpty(name)) - { - name = String.Format("{0}", i); - } - - var weight = srcMesh.GetBlendShapeFrameWeight(i, 0); - - try - { - mesh.AddBlendShapeFrame(name, - weight, - vertices, - normals, - tangents - ); - } - catch (Exception) - { - Debug.LogErrorFormat("fail to mesh.AddBlendShapeFrame {0}.{1}", - mesh.name, - srcMesh.GetBlendShapeName(i) - ); - throw; - } - } - - // recalc bindposes - mesh.bindposes = bones.Select(x => - x.worldToLocalMatrix * dst.transform.localToWorldMatrix).ToArray(); - - mesh.RecalculateBounds(); - - var dstRenderer = dst.gameObject.AddComponent(); - dstRenderer.sharedMaterials = srcRenderer.sharedMaterials; - dstRenderer.sharedMesh = mesh; - dstRenderer.bones = bones; - if (srcRenderer.rootBone != null) - { - dstRenderer.rootBone = boneMap[srcRenderer.rootBone]; - } - } - } - - { - // - // not SkinnedMesh - // - var srcFilter = src.GetComponent(); - if (srcFilter != null - && srcFilter.sharedMesh!=null - && srcFilter.sharedMesh.vertexCount>0) - { - var srcRenderer = src.GetComponent(); - if (srcRenderer!=null && srcRenderer.enabled) - { - var dstFilter = dst.gameObject.AddComponent(); - dstFilter.sharedMesh = TransformMesh(srcFilter.sharedMesh, src.localToWorldMatrix); - - var dstRenderer = dst.gameObject.AddComponent(); - dstRenderer.sharedMaterials = srcRenderer.sharedMaterials; - } - } - } - } - - return normalized; - } - - static Mesh TransformMesh(Mesh src, Matrix4x4 m) - { - m.SetColumn(3, new Vector4(0, 0, 0, 1)); - - var mesh = new Mesh(); - mesh.name = src.name + "(transformed)"; - - mesh.vertices = src.vertices.Select(x => m.MultiplyPoint(x)).ToArray(); - if (src.normals != null && src.normals.Length > 0) - { - mesh.normals = src.normals.Select(x => m.MultiplyVector(x)).ToArray(); - } - if (src.tangents != null && src.tangents.Length > 0) - { - mesh.tangents = src.tangents.Select(x => - { - var t = m.MultiplyVector((Vector3)x); - return new Vector4(t.x, t.y, t.z, x.w); - }).ToArray(); - } - - if (src.colors != null && src.colors.Length > 0) mesh.colors = src.colors; - if (src.uv != null && src.uv.Length > 0) mesh.uv = src.uv; - if (src.uv2 != null && src.uv2.Length > 0) mesh.uv2 = src.uv2; - if (src.uv3 != null && src.uv3.Length > 0) mesh.uv3 = src.uv3; - if (src.uv4 != null && src.uv4.Length > 0) mesh.uv4 = src.uv4; - - mesh.subMeshCount = src.subMeshCount; - for(int i=0; i m.MultiplyPoint(x)).ToArray(); + if (src.normals != null && src.normals.Length > 0) + { + src.normals = src.normals.Select(x => m.MultiplyVector(x)).ToArray(); + } + if (src.tangents != null && src.tangents.Length > 0) + { + src.tangents = src.tangents.Select(x => + { + var t = m.MultiplyVector((Vector3)x); + return new Vector4(t.x, t.y, t.z, x.w); + }).ToArray(); + } + } + } +} diff --git a/Scripts/SkinnedMeshUtility/MeshExtensions.cs.meta b/Scripts/SkinnedMeshUtility/MeshExtensions.cs.meta new file mode 100644 index 000000000..7485178c0 --- /dev/null +++ b/Scripts/SkinnedMeshUtility/MeshExtensions.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 4181f0b5e9a271b45b3e995a38202780 +timeCreated: 1532506262 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/SpringBone/VRMSpringBone.cs b/Scripts/SpringBone/VRMSpringBone.cs index 210bd3915..473605031 100644 --- a/Scripts/SpringBone/VRMSpringBone.cs +++ b/Scripts/SpringBone/VRMSpringBone.cs @@ -286,13 +286,16 @@ namespace VRM { foreach (var group in ColliderGroups) { - foreach(var collider in group.Colliders) + if (group != null) { - m_colliderList.Add(new SphereCollider + foreach (var collider in group.Colliders) { - Position = group.transform.TransformPoint(collider.Offset), - Radius = collider.Radius, - }); + m_colliderList.Add(new SphereCollider + { + Position = group.transform.TransformPoint(collider.Offset), + Radius = collider.Radius, + }); + } } } } diff --git a/Scripts/SpringBone/VRMSpringUtility.cs b/Scripts/SpringBone/VRMSpringUtility.cs index a22677dcb..75a1b7a1c 100644 --- a/Scripts/SpringBone/VRMSpringUtility.cs +++ b/Scripts/SpringBone/VRMSpringUtility.cs @@ -16,7 +16,7 @@ namespace VRM { #if UNITY_EDITOR #region save - [MenuItem("VRM/SaveSpringBoneToJSON", validate = true)] + [MenuItem(VRMVersion.VRM_VERSION + "/SaveSpringBoneToJSON", validate = true)] static bool SaveSpringBoneToJSONIsEnable() { var root = Selection.activeObject as GameObject; @@ -34,7 +34,7 @@ namespace VRM return true; } - [MenuItem("VRM/SaveSpringBoneToJSON")] + [MenuItem(VRMVersion.VRM_VERSION + "/SaveSpringBoneToJSON")] static void SaveSpringBoneToJSON() { var path = EditorUtility.SaveFilePanel( @@ -62,7 +62,7 @@ namespace VRM #endregion #region load - [MenuItem("VRM/LoadSpringBoneFromJSON", true)] + [MenuItem(VRMVersion.VRM_VERSION + "/LoadSpringBoneFromJSON", true)] static bool LoadSpringBoneFromJSONIsEnable() { var root = Selection.activeObject as GameObject; @@ -80,7 +80,7 @@ namespace VRM return true; } - [MenuItem("VRM/LoadSpringBoneFromJSON")] + [MenuItem(VRMVersion.VRM_VERSION + "/LoadSpringBoneFromJSON")] static void LoadSpringBoneFromJSON() { var path = EditorUtility.OpenFilePanel( @@ -147,15 +147,8 @@ namespace VRM stiffiness = spring.m_stiffnessForce, hitRadius = spring.m_hitRadius, colliderGroups = spring.ColliderGroups - .Select(x => - { - var index = colliders.IndexOf(x); - if (index == -1) - { - throw new IndexOutOfRangeException(); - } - return index; - }) + .Select(x => colliders.IndexOf(x)) + .Where(x => x != -1) .ToArray(), bones = spring.RootBones.Select(x => nodes.IndexOf(x)).ToArray(), }); @@ -177,7 +170,7 @@ namespace VRM .SelectMany(x => x.GetComponents()) .Where(x => x is VRMSpringBone || x is VRMSpringBoneColliderGroup) .ToArray(); - foreach(var x in remove) + foreach (var x in remove) { if (Application.isPlaying) { diff --git a/UniGLTF b/UniGLTF index 1d41ff08c..e2d60e6ed 160000 --- a/UniGLTF +++ b/UniGLTF @@ -1 +1 @@ -Subproject commit 1d41ff08c25707e9b789997368bac2971f2cd78e +Subproject commit e2d60e6ed73f549fe2e350d06a71762bfec9b95e diff --git a/specification.meta b/specification.meta new file mode 100644 index 000000000..1c8364219 --- /dev/null +++ b/specification.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: c41aa6d87463552448f681369e3ea7c3 +folderAsset: yes +timeCreated: 1532940981 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/specification/0.0.meta b/specification/0.0.meta new file mode 100644 index 000000000..de2a05dc8 --- /dev/null +++ b/specification/0.0.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 4aa7da6ed040f4f40bd5f8bdb752a04d +folderAsset: yes +timeCreated: 1532940981 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/specification/0.0/schema.meta b/specification/0.0/schema.meta new file mode 100644 index 000000000..5e3149de5 --- /dev/null +++ b/specification/0.0/schema.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 51d82765b48dd464b8ee62daec6d6692 +folderAsset: yes +timeCreated: 1532940981 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/specification/0.0/schema/vrm.schema.json b/specification/0.0/schema/vrm.schema.json new file mode 100644 index 000000000..9ce7a54ba --- /dev/null +++ b/specification/0.0/schema/vrm.schema.json @@ -0,0 +1 @@ +{"title":"glTF_VRM_extensions","type":"object","properties":{"exporterVersion":{"title":"String","type":"string"},"meta":{"title":"glTF_VRM_Meta","type":"object","properties":{"title":{"title":"String","type":"string"},"version":{"title":"String","type":"string"},"author":{"title":"String","type":"string"},"contactInformation":{"title":"String","type":"string"},"reference":{"title":"String","type":"string"},"texture":{"title":"Int32","type":"integer"},"allowedUserName":{"title":"String","type":"string"},"violentUssageName":{"title":"String","type":"string"},"sexualUssageName":{"title":"String","type":"string"},"commercialUssageName":{"title":"String","type":"string"},"otherPermissionUrl":{"title":"String","type":"string"},"licenseName":{"title":"String","type":"string"},"otherLicenseUrl":{"title":"String","type":"string"}}},"humanoid":{"title":"glTF_VRM_Humanoid","type":"object","properties":{"humanBones":{"title":"List`1","type":"array"},"armStretch":{"title":"Single","type":"number"},"legStretch":{"title":"Single","type":"number"},"upperArmTwist":{"title":"Single","type":"number"},"lowerArmTwist":{"title":"Single","type":"number"},"upperLegTwist":{"title":"Single","type":"number"},"lowerLegTwist":{"title":"Single","type":"number"},"feetSpacing":{"title":"Single","type":"number"},"hasTranslationDoF":{"title":"Boolean","type":"bool"}}},"firstPerson":{"title":"glTF_VRM_Firstperson","type":"object","properties":{"firstPersonBone":{"title":"Int32","type":"integer"},"firstPersonBoneOffset":{"title":"Vector3","type":"array"},"meshAnnotations":{"title":"List`1","type":"array"},"lookAtTypeName":{"title":"String","type":"string"},"lookAtHorizontalInner":{"title":"glTF_VRM_DegreeMap","type":"object","properties":{"curve":{"title":"Single[]","type":"array"},"xRange":{"title":"Single","type":"number"},"yRange":{"title":"Single","type":"number"}}},"lookAtHorizontalOuter":{"title":"glTF_VRM_DegreeMap","type":"object","properties":{"curve":{"title":"Single[]","type":"array"},"xRange":{"title":"Single","type":"number"},"yRange":{"title":"Single","type":"number"}}},"lookAtVerticalDown":{"title":"glTF_VRM_DegreeMap","type":"object","properties":{"curve":{"title":"Single[]","type":"array"},"xRange":{"title":"Single","type":"number"},"yRange":{"title":"Single","type":"number"}}},"lookAtVerticalUp":{"title":"glTF_VRM_DegreeMap","type":"object","properties":{"curve":{"title":"Single[]","type":"array"},"xRange":{"title":"Single","type":"number"},"yRange":{"title":"Single","type":"number"}}}}},"blendShapeMaster":{"title":"glTF_VRM_BlendShapeMaster","type":"object","properties":{"blendShapeGroups":{"title":"List`1","type":"array"}}},"secondaryAnimation":{"title":"glTF_VRM_SecondaryAnimation","type":"object","properties":{"boneGroups":{"title":"List`1","type":"array"},"colliderGroups":{"title":"List`1","type":"array"}}},"materialProperties":{"title":"List`1","type":"array"}}} \ No newline at end of file diff --git a/specification/0.0/schema/vrm.schema.json.meta b/specification/0.0/schema/vrm.schema.json.meta new file mode 100644 index 000000000..ba49f7373 --- /dev/null +++ b/specification/0.0/schema/vrm.schema.json.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f6d375d2085ef2e4392d858d5959330c +timeCreated: 1532941026 +licenseType: Free +TextScriptImporter: + userData: + assetBundleName: + assetBundleVariant: