diff --git a/MToon b/MToon index 15dd8816d..bf08610b3 160000 --- a/MToon +++ b/MToon @@ -1 +1 @@ -Subproject commit 15dd8816dc1775871e5ca02802a561e6448e5792 +Subproject commit bf08610b38e27ae915b0613ee4dfb574bc8e381b diff --git a/Scripts/BlendShape/BlendShapeAvatar.cs b/Scripts/BlendShape/BlendShapeAvatar.cs index cc2cb36e6..5606b0fce 100644 --- a/Scripts/BlendShape/BlendShapeAvatar.cs +++ b/Scripts/BlendShape/BlendShapeAvatar.cs @@ -3,6 +3,10 @@ using System.Linq; using System; using System.Collections.Generic; using UniGLTF; +using System.IO; +#if UNITY_EDITOR +using UnityEditor; +#endif namespace VRM { @@ -12,6 +16,24 @@ namespace VRM [SerializeField] public List Clips = new List(); + /// + /// NullのClipを削除して詰める + /// + public void RemoveNullClip() + { + if (Clips == null) + { + return; + } + for (int i = Clips.Count - 1; i >= 0; --i) + { + if (Clips[i] == null) + { + Clips.RemoveAt(i); + } + } + } + #if UNITY_EDITOR [ContextMenu("Restore")] void Restore() @@ -37,6 +59,19 @@ namespace VRM } Clips = Clips.OrderBy(x => BlendShapeKey.CreateFrom(x)).ToList(); } + + static public BlendShapeClip CreateBlendShapeClip(string path) + { + //Debug.LogFormat("{0}", path); + var clip = ScriptableObject.CreateInstance(); + clip.BlendShapeName = Path.GetFileNameWithoutExtension(path); + AssetDatabase.CreateAsset(clip, path); + AssetDatabase.ImportAsset(path); + return clip; + //Clips.Add(clip); + //EditorUtility.SetDirty(this); + //AssetDatabase.SaveAssets(); + } #endif /// diff --git a/Scripts/BlendShape/BlendShapeBindingMerger.cs b/Scripts/BlendShape/BlendShapeBindingMerger.cs new file mode 100644 index 000000000..e2126878d --- /dev/null +++ b/Scripts/BlendShape/BlendShapeBindingMerger.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace VRM +{ + /// + /// A.Value * A.Weight + B.Value * B.Weight ... + /// + class BlendShapeBindingMerger + { + /// + /// BlendShapeの適用値を蓄積する + /// + /// + /// + /// + Dictionary m_blendShapeValueMap = new Dictionary(); + + /// + /// + /// + /// + Dictionary> m_blendShapeSetterMap = new Dictionary>(); + + public BlendShapeBindingMerger(Dictionary clipMap, Transform root) + { + foreach (var kv in clipMap) + { + foreach (var binding in kv.Value.Values) + { + if (!m_blendShapeSetterMap.ContainsKey(binding)) + { + var _target = root.Find(binding.RelativePath); + SkinnedMeshRenderer target = null; + if (_target != null) + { + target = _target.GetComponent(); + } + if (target != null) + { + if (binding.Index >= 0 && binding.Index < target.sharedMesh.blendShapeCount) + { + m_blendShapeSetterMap.Add(binding, x => + { + target.SetBlendShapeWeight(binding.Index, x); + }); + } + else + { + Debug.LogWarningFormat("Invalid blendshape binding: {0}: {1}", target.name, binding); + } + + } + else + { + Debug.LogWarningFormat("SkinnedMeshRenderer: {0} not found", binding.RelativePath); + } + } + } + } + } + + public void ImmediatelySetValue(BlendShapeClip clip, float value) + { + foreach (var binding in clip.Values) + { + Action setter; + if (m_blendShapeSetterMap.TryGetValue(binding, out setter)) + { + setter(binding.Weight * value); + } + } + } + + public void AccumulateValue(BlendShapeClip clip, float value) + { + foreach (var binding in clip.Values) + { + float acc; + if (m_blendShapeValueMap.TryGetValue(binding, out acc)) + { + m_blendShapeValueMap[binding] = acc + binding.Weight * value; + } + else + { + m_blendShapeValueMap[binding] = binding.Weight * value; + } + } + } + + public void Apply() + { + foreach (var kv in m_blendShapeValueMap) + { + Action setter; + if (m_blendShapeSetterMap.TryGetValue(kv.Key, out setter)) + { + setter(kv.Value); + } + } + m_blendShapeValueMap.Clear(); + } + } +} \ No newline at end of file diff --git a/Scripts/BlendShape/BlendShapeBindingMerger.cs.meta b/Scripts/BlendShape/BlendShapeBindingMerger.cs.meta new file mode 100644 index 000000000..29c3d8446 --- /dev/null +++ b/Scripts/BlendShape/BlendShapeBindingMerger.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: ca4c8446451eeed46b1598db9e08bb73 +timeCreated: 1541229189 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/BlendShape/BlendShapeClip.cs b/Scripts/BlendShape/BlendShapeClip.cs index 99b28f35c..4b883fdc4 100644 --- a/Scripts/BlendShape/BlendShapeClip.cs +++ b/Scripts/BlendShape/BlendShapeClip.cs @@ -73,7 +73,7 @@ namespace VRM public BlendShapePreset Preset; /// - /// BlendShapeに対する参照(indexベース) + /// BlendShapeに対する参照(index ベース) /// /// [SerializeField] @@ -91,5 +91,8 @@ namespace VRM /// [SerializeField] public bool IsBinary; + + // [SerializeField] + // public Texture2D Thumbnail; } } diff --git a/Scripts/BlendShape/BlendShapeMerger.cs b/Scripts/BlendShape/BlendShapeMerger.cs index 8a8c514a3..9a7738cc8 100644 --- a/Scripts/BlendShape/BlendShapeMerger.cs +++ b/Scripts/BlendShape/BlendShapeMerger.cs @@ -7,133 +7,38 @@ using UnityEngine; namespace VRM { + /// /// ブレンドシェイプを蓄えてまとめて適用するクラス /// class BlendShapeMerger { + /// + /// Key からBlendShapeClipを得る + /// Dictionary m_clipMap; + + /// + /// BlendShape のWeightを記録する + /// Dictionary m_valueMap; - Dictionary m_materialMap; + BlendShapeBindingMerger m_blendShapeBindingMerger; - Dictionary m_blendShapeValueMap = new Dictionary(); - Dictionary> m_blendShapeSetterMap = new Dictionary>(); + MaterialValueBindingMerger m_materialValueBindingMerger; - Dictionary m_materialValueMap = new Dictionary(); - Dictionary> m_materialSetterMap = new Dictionary>(); public BlendShapeMerger(IEnumerable clips, Transform root) { - m_materialMap = new Dictionary(); - foreach (var x in root.Traverse()) - { - var renderer = x.GetComponent(); - if (renderer != null) - { - foreach (var y in renderer.sharedMaterials.Where(y => y != null)) - { - if (!string.IsNullOrEmpty(y.name)) - { - if (!m_materialMap.ContainsKey(y.name)) - { - m_materialMap.Add(y.name, y); - } - } - } - } - } - m_clipMap = clips.ToDictionary(x => BlendShapeKey.CreateFrom(x), x => x); m_valueMap = new Dictionary(); - foreach (var kv in m_clipMap) - { - foreach (var binding in kv.Value.Values) - { - if (!m_blendShapeSetterMap.ContainsKey(binding)) - { - var _target = root.Find(binding.RelativePath); - SkinnedMeshRenderer target = null; - if (_target != null) - { - target = _target.GetComponent(); - } - if (target != null) - { - if (binding.Index >= 0 && binding.Index < target.sharedMesh.blendShapeCount) - { - m_blendShapeSetterMap.Add(binding, x => - { - target.SetBlendShapeWeight(binding.Index, x); - }); - } - else - { - Debug.LogWarningFormat("Invalid blendshape binding: {0}: {1}", target.name, binding); - } - - } - else - { - Debug.LogWarningFormat("SkinnedMeshRenderer: {0} not found", binding.RelativePath); - } - } - } - - foreach (var binding in kv.Value.MaterialValues) - { - if (!m_materialSetterMap.ContainsKey(binding)) - { - Material target; - if (m_materialMap.TryGetValue(binding.MaterialName, out target)) - { - 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 => - { - var propValue = binding.BaseValue + (binding.TargetValue - binding.BaseValue) * x; - target.SetColor(binding.ValueName, propValue); - }; - m_materialSetterMap.Add(binding, vec4Setter); - } - } - else - { - Debug.LogWarningFormat("material: {0} not found", binding.MaterialName); - } - } - } - } + m_blendShapeBindingMerger = new BlendShapeBindingMerger(m_clipMap, root); + m_materialValueBindingMerger = new MaterialValueBindingMerger(m_clipMap, root); } + /* public void Clear() { foreach (var kv in m_valueMap.ToArray()) @@ -142,40 +47,36 @@ namespace VRM } Apply(); } + */ + /// + /// 蓄積した値を適用する + /// public void Apply() { - foreach (var kv in m_blendShapeValueMap) - { - Action setter; - if (m_blendShapeSetterMap.TryGetValue(kv.Key, out setter)) - { - setter(kv.Value); - } - } - m_blendShapeValueMap.Clear(); - - foreach (var kv in m_materialValueMap) - { - Action setter; - if (m_materialSetterMap.TryGetValue(kv.Key, out setter)) - { - setter(kv.Value); - } - } - m_materialValueMap.Clear(); + m_blendShapeBindingMerger.Apply(); + m_materialValueBindingMerger.Apply(); } + /// + /// まとめて反映する。1フレームに1回呼び出されることを想定 + /// + /// public void SetValues(IEnumerable> values) { foreach (var kv in values) { - SetValue(kv.Key, kv.Value, false); + AccumulateValue(kv.Key, kv.Value); } Apply(); } - public void SetValue(BlendShapeKey key, float value, bool replace) + /// + /// 即時に反映しない。後にApplyによって反映する + /// + /// + /// + public void AccumulateValue(BlendShapeKey key, float value) { m_valueMap[key] = value; @@ -185,57 +86,48 @@ namespace VRM return; } - foreach (var binding in clip.Values) + if (clip.IsBinary) { - if (replace) - { - // 値置き換え - Action setter; - if (m_blendShapeSetterMap.TryGetValue(binding, out setter)) - { - setter(binding.Weight * value); - } - } - else - { - // 積算 - float acc; - if (m_blendShapeValueMap.TryGetValue(binding, out acc)) - { - m_blendShapeValueMap[binding] = acc + binding.Weight * value; - } - else - { - m_blendShapeValueMap[binding] = binding.Weight * value; - } - } + value = Mathf.Round(value); } - // materialの更新 - foreach (var binding in clip.MaterialValues) + m_blendShapeBindingMerger.AccumulateValue(clip, value); + m_materialValueBindingMerger.AccumulateValue(clip, value); + } + + /// + /// 即時に反映する + /// + /// + /// + public void ImmediatelySetValue(BlendShapeKey key, float value) + { + m_valueMap[key] = value; + + BlendShapeClip clip; + if (!m_clipMap.TryGetValue(key, out clip)) { - if (replace) - { - // 値置き換え - Action setter; - if (m_materialSetterMap.TryGetValue(binding, out setter)) - { - setter(value); - } - } - else - { - // 積算 - float acc; - if (m_materialValueMap.TryGetValue(binding, out acc)) - { - m_materialValueMap[binding] = acc + value; - } - else - { - m_materialValueMap[binding] = value; - } - } + return; + } + + if (clip.IsBinary) + { + value = Mathf.Round(value); + } + + m_blendShapeBindingMerger.ImmediatelySetValue(clip, value); + m_materialValueBindingMerger.ImmediatelySetValue(clip, value); + } + + public void SetValue(BlendShapeKey key, float value, bool immediately) + { + if (immediately) + { + ImmediatelySetValue(key, value); + } + else + { + AccumulateValue(key, value); } } @@ -251,25 +143,7 @@ namespace VRM public void RestoreMaterialInitialValues(IEnumerable clips) { - if (m_materialMap != null) - { - foreach (var x in clips) - { - foreach (var y in x.MaterialValues) - { - // restore values - Material material; - if(m_materialMap.TryGetValue(y.MaterialName, out material)) - { - material.SetColor(y.ValueName, y.BaseValue); - } - else - { - Debug.LogWarningFormat("{0} not found", y.MaterialName); - } - } - } - } + m_materialValueBindingMerger.RestoreMaterialInitialValues(clips); } } } diff --git a/Scripts/BlendShape/Editor/BlendShapeAvatarEditor.cs b/Scripts/BlendShape/Editor/BlendShapeAvatarEditor.cs index f3657d42f..bc1054254 100644 --- a/Scripts/BlendShape/Editor/BlendShapeAvatarEditor.cs +++ b/Scripts/BlendShape/Editor/BlendShapeAvatarEditor.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using UniGLTF; using UnityEditor; +using UnityEditorInternal; using UnityEngine; @@ -12,260 +13,147 @@ namespace VRM [CustomEditor(typeof(BlendShapeAvatar))] public class BlendShapeAvatarEditor : PreviewEditor { - static String[] Presets = ((BlendShapePreset[])Enum.GetValues(typeof(BlendShapePreset))) - .Select(x => x.ToString()).ToArray(); + ReorderableList m_clipList; - BlendShapeAvatar m_target; - void AddBlendShapeClip() + BlendShapeClipSelector m_selector; + + SerializedBlendShapeEditor m_clipEditor; + + protected override PreviewSceneManager.BakeValue GetBakeValue() { - var dir = Path.GetDirectoryName(AssetDatabase.GetAssetPath(m_target)); - var path = EditorUtility.SaveFilePanel( - "Create BlendShapeClip", - dir, - string.Format("BlendShapeClip#{0}.asset", m_target.Clips.Count), - "asset"); - if (string.IsNullOrEmpty(path)) + var clip = m_selector.Selected; + var value = new PreviewSceneManager.BakeValue(); + if (clip != null) { - return; + value.BlendShapeBindings = clip.Values; + value.MaterialValueBindings = clip.MaterialValues; + value.Weight = 1.0f; } - path = path.ToUnityRelativePath(); - //Debug.LogFormat("{0}", path); - var clip = ScriptableObject.CreateInstance(); - clip.BlendShapeName = Path.GetFileNameWithoutExtension(path); - clip.Prefab = AssetDatabase.LoadAssetAtPath(AssetDatabase.GetAssetPath(m_target)); - AssetDatabase.CreateAsset(clip, path); - AssetDatabase.ImportAsset(path); - - m_target.Clips.Add(clip); - EditorUtility.SetDirty(m_target); - AssetDatabase.SaveAssets(); + return value; } - BlendShapeClip m_currentClip; - BlendShapeClip CurrentClip + void OnSelected(BlendShapeClip clip) { - get { return m_currentClip; } - set + if (PreviewSceneManager == null) { - if (m_currentClip == value) return; - - m_currentClip = value; - //ClearBlendShape(); - if (m_currentClip != null) + m_clipEditor = null; + } + else if (clip != null) + { + m_clipEditor = new SerializedBlendShapeEditor(clip, PreviewSceneManager); + PreviewSceneManager.Bake(new PreviewSceneManager.BakeValue { - Bake(m_currentClip.Values, m_currentClip.MaterialValues, 1.0f); - } + BlendShapeBindings = clip.Values, + MaterialValueBindings = clip.MaterialValues, + Weight = 1.0f + }); } - } - - void OnPrefabChanged() - { - if (m_currentClip != null) + else { - Bake(m_currentClip.Values, m_currentClip.MaterialValues, 1.0f); + m_clipEditor = null; + PreviewSceneManager.Bake(new PreviewSceneManager.BakeValue()); } } protected override void OnEnable() { - PrefabChanged += OnPrefabChanged; + m_selector = new BlendShapeClipSelector((BlendShapeAvatar)target, OnSelected); + var prop = serializedObject.FindProperty("Clips"); + m_clipList = new ReorderableList(serializedObject, prop); + + m_clipList.drawHeaderCallback = (rect) => + EditorGUI.LabelField(rect, "BlendShapeClips"); + + m_clipList.elementHeight = BlendShapeClipDrawer.Height; + m_clipList.drawElementCallback = (rect, index, isActive, isFocused) => + { + var element = prop.GetArrayElementAtIndex(index); + rect.height -= 4; + rect.y += 2; + EditorGUI.PropertyField(rect, element); + }; + + m_clipList.onAddCallback += (list) => + { + // Add slot + prop.arraySize++; + // select last item + list.index = prop.arraySize - 1; + // get last item + var element = prop.GetArrayElementAtIndex(list.index); + element.objectReferenceValue = null; + + var dir = Path.GetDirectoryName(AssetDatabase.GetAssetPath(target)); + var path = EditorUtility.SaveFilePanel( + "Create BlendShapeClip", + dir, + string.Format("BlendShapeClip#{0}.asset", list.count), + "asset"); + if (!string.IsNullOrEmpty(path)) + { + var clip = BlendShapeAvatar.CreateBlendShapeClip(path.ToUnityRelativePath()); + //clip.Prefab = AssetDatabase.LoadAssetAtPath(AssetDatabase.GetAssetPath(target)); + + element.objectReferenceValue = clip; + } + }; + + m_clipList.onSelectCallback += (list) => + { + var a = list.serializedProperty; + var selected = a.GetArrayElementAtIndex(list.index); + OnSelected((BlendShapeClip)selected.objectReferenceValue); + }; + + //m_clipList.onCanRemoveCallback += list => true; 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); - } - - - if(m_target.Clips.Count > 0) - { - CurrentClip = m_target.Clips[0]; - } + OnSelected(m_selector.Selected); } - protected override void OnDisable() - { - base.OnDisable(); - PrefabChanged -= OnPrefabChanged; - } + int m_mode; + static readonly string[] MODES = new string[]{ + "Editor", + "List" + }; - List m_meshFolds = new List(); - int m_preset; public override void OnInspectorGUI() { + serializedObject.Update(); + base.OnInspectorGUI(); - // buttons - if (m_target.Clips != null) + m_mode = GUILayout.Toolbar(m_mode, MODES); + switch (m_mode) { - EditorGUILayout.Space(); - EditorGUILayout.LabelField("Select BlendShapeClip", EditorStyles.boldLabel); - 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]; - m_preset = preset; - } - } - - // Add - if (GUILayout.Button("Add BlendShapeClip")) - { - AddBlendShapeClip(); - } - - if (CurrentClip != null) - { - // clip - EditorGUILayout.Space(); - EditorGUILayout.LabelField("CurrentClip", EditorStyles.boldLabel); - - /*var loadClip = (BlendShapeClip)*/ - GUI.enabled = false; - EditorGUILayout.ObjectField("Current clip", - CurrentClip, typeof(BlendShapeClip), false); - GUI.enabled = true; - - 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) - { - EditorGUILayout.HelpBox("duplicate clip", MessageType.Error); - } - - EditorGUILayout.Space(); - EditorGUILayout.LabelField("BlendShapeValues", EditorStyles.boldLabel); - - EditorGUILayout.BeginHorizontal(); - if (GUILayout.Button("Clear")) - { - ClearBlendShape(); - } - - if (CurrentClip != null && GUILayout.Button("Apply")) - { - string maxWeightString; - CurrentClip.Values = GetBindings(out maxWeightString); - EditorUtility.SetDirty(CurrentClip); - } - EditorGUILayout.EndHorizontal(); - - // sliders - bool changed = false; - int foldIndex = 0; - if (PreviewSceneManager != null) - { - foreach (var item in PreviewSceneManager.EnumRenderItems.Where(x => x.SkinnedMeshRenderer != null)) + case 0: + m_selector.SelectGUI(); + if (m_clipEditor != null) { - var mesh = item.SkinnedMeshRenderer.sharedMesh; - if (mesh != null && mesh.blendShapeCount > 0) + Separator(); + var result = m_clipEditor.Draw(); + if (result.Changed) { - //var relativePath = UniGLTF.UnityExtensions.RelativePathFrom(renderer.transform, m_target.transform); - //EditorGUILayout.LabelField(m_target.name + "/" + item.Path); - - if (foldIndex >= m_meshFolds.Count) + PreviewSceneManager.Bake(new PreviewSceneManager.BakeValue { - m_meshFolds.Add(false); - } - m_meshFolds[foldIndex] = EditorGUILayout.Foldout(m_meshFolds[foldIndex], item.SkinnedMeshRenderer.name); - if (m_meshFolds[foldIndex]) - { - //EditorGUI.indentLevel += 1; - for (int i = 0; i < mesh.blendShapeCount; ++i) - { - var src = item.SkinnedMeshRenderer.GetBlendShapeWeight(i); - var dst = EditorGUILayout.Slider(mesh.GetBlendShapeName(i), src, 0, 100.0f); - if (dst != src) - { - item.SkinnedMeshRenderer.SetBlendShapeWeight(i, dst); - changed = true; - } - } - //EditorGUI.indentLevel -= 1; - } - ++foldIndex; + BlendShapeBindings = result.BlendShapeBindings, + MaterialValueBindings = result.MaterialValueBindings, + Weight = 1.0f, + }); } } + break; - if (changed) - { - PreviewSceneManager.Bake(); - } - } + case 1: + m_clipList.DoLayoutList(); + break; + + default: + throw new NotImplementedException(); } - } - BlendShapeBinding[] GetBindings(out string _maxWeightName) - { - var maxWeight = 0.0f; - var maxWeightName = ""; - // weightのついたblendShapeを集める - var values = PreviewSceneManager.EnumRenderItems - .Where(x => x.SkinnedMeshRenderer!=null) - .SelectMany(x => - { - var mesh = x.SkinnedMeshRenderer.sharedMesh; - - var relativePath = x.Path; - - var list = new List(); - if (mesh != null) - { - for (int i = 0; i < mesh.blendShapeCount; ++i) - { - var weight = x.SkinnedMeshRenderer.GetBlendShapeWeight(i); - if (weight == 0) - { - continue; - } - var name = mesh.GetBlendShapeName(i); - if (weight > maxWeight) - { - maxWeightName = name; - maxWeight = weight; - } - list.Add(new BlendShapeBinding - { - Index = i, - RelativePath = relativePath, - Weight = weight - }); - } - } - return list; - }).ToArray() - ; - _maxWeightName = maxWeightName; - return values; - } - - private void ClearBlendShape() - { - foreach (var item in PreviewSceneManager.EnumRenderItems.Where(x => x.SkinnedMeshRenderer!=null)) - { - var renderer = item.SkinnedMeshRenderer; - var mesh = renderer.sharedMesh; - if (mesh != null) - { - for (int i = 0; i < mesh.blendShapeCount; ++i) - { - renderer.SetBlendShapeWeight(i, 0); - } - } - } + serializedObject.ApplyModifiedProperties(); } } } diff --git a/Scripts/BlendShape/Editor/BlendShapeClipDrawer.cs b/Scripts/BlendShape/Editor/BlendShapeClipDrawer.cs new file mode 100644 index 000000000..822a5a09f --- /dev/null +++ b/Scripts/BlendShape/Editor/BlendShapeClipDrawer.cs @@ -0,0 +1,63 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + + +namespace VRM +{ + + [CustomPropertyDrawer(typeof(BlendShapeClip))] + public class BlendShapeClipDrawer : PropertyDrawer + { + //public const int Height = 132; + + //public const int ThumbnailSize = 128; + + public const int Height = 80; + public const int ThumbnailSize = 0; + + public override void OnGUI(Rect position, + SerializedProperty property, GUIContent label) + { + using (new EditorGUI.PropertyScope(position, label, property)) + { + //EditorGUIUtility.labelWidth = 80; + + position.height = EditorGUIUtility.singleLineHeight; + + var halfWidth = position.width * 0.5f; + + var rect = new Rect(position.x + ThumbnailSize, position.y, position.width - ThumbnailSize, position.height); + EditorGUI.PropertyField(rect, property); + + var clip = property.objectReferenceValue as BlendShapeClip; + if (clip != null) + { + var clipObj = new SerializedObject(clip); + //var thumbnail = clipObj.FindProperty("Thumbnail"); + var blendShapeName = clipObj.FindProperty("BlendShapeName"); + var preset = clipObj.FindProperty("Preset"); + var isBinary = clipObj.FindProperty("IsBinary"); + + /* + EditorGUI.ObjectField(new Rect(position) + { + width = ThumbnailSize, + height = ThumbnailSize + }, thumbnail.objectReferenceValue, typeof(Texture), false); + */ + + rect.y += (EditorGUIUtility.singleLineHeight + 2); + EditorGUI.PropertyField(rect, blendShapeName); + rect.y += (EditorGUIUtility.singleLineHeight + 2); + EditorGUI.PropertyField(rect, preset); + rect.y += (EditorGUIUtility.singleLineHeight + 2); + EditorGUI.PropertyField(rect, isBinary); + + clipObj.ApplyModifiedProperties(); + } + } + } + } +} \ No newline at end of file diff --git a/Scripts/BlendShape/Editor/BlendShapeClipDrawer.cs.meta b/Scripts/BlendShape/Editor/BlendShapeClipDrawer.cs.meta new file mode 100644 index 000000000..b5ef0081e --- /dev/null +++ b/Scripts/BlendShape/Editor/BlendShapeClipDrawer.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 6a31667cfcf461c47b3b384211e2d9ff +timeCreated: 1541179390 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/BlendShape/Editor/BlendShapeClipEditor.cs b/Scripts/BlendShape/Editor/BlendShapeClipEditor.cs index 701f0553c..5af9097f5 100644 --- a/Scripts/BlendShape/Editor/BlendShapeClipEditor.cs +++ b/Scripts/BlendShape/Editor/BlendShapeClipEditor.cs @@ -1,4 +1,6 @@ using System; +using System.IO; +using UniGLTF; using UnityEditor; using UnityEditorInternal; using UnityEngine; @@ -9,113 +11,154 @@ namespace VRM [CustomEditor(typeof(BlendShapeClip))] public class BlendShapeClipEditor : PreviewEditor { - float m_previewSlider; - - #region for Editor - SerializedProperty m_BlendShapeNameProp; - SerializedProperty m_PresetProp; - SerializedProperty m_ValuesProp; - ReorderableList m_ValuesList; - SerializedProperty m_MaterialValuesProp; - ReorderableList m_MaterialValuesList; - #endregion + SerializedBlendShapeEditor m_serializedEditor; BlendShapeClip m_target; - bool m_changed; + protected override PreviewSceneManager.BakeValue GetBakeValue() + { + return new PreviewSceneManager.BakeValue + { + BlendShapeBindings = m_target.Values, + MaterialValueBindings = m_target.MaterialValues, + Weight = 1.0f, + }; + } + + //SerializedProperty m_thumbnailProp; + SerializedProperty m_isBinaryProp; protected override GameObject GetPrefab() { return m_target.Prefab; } - void OnPrefabChanged() - { - m_target.Prefab = Prefab; - Bake(m_target.Values, m_target.MaterialValues, 1.0f); - } - protected override void OnEnable() { m_target = (BlendShapeClip)target; - PrefabChanged += OnPrefabChanged; base.OnEnable(); - - m_previewSlider = 1.0f; - - Bake(m_target.Values, m_target.MaterialValues, m_previewSlider); - - m_BlendShapeNameProp = serializedObject.FindProperty("BlendShapeName"); - m_PresetProp = serializedObject.FindProperty("Preset"); - m_ValuesProp = serializedObject.FindProperty("Values"); - - m_ValuesList = new ReorderableList(serializedObject, m_ValuesProp); - m_ValuesList.elementHeight = BlendShapeBindingHeight; - m_ValuesList.drawElementCallback = - (rect, index, isActive, isFocused) => - { - var element = m_ValuesProp.GetArrayElementAtIndex(index); - rect.height -= 4; - rect.y += 2; - if (DrawBlendShapeBinding(rect, element, PreviewSceneManager)) - { - m_changed = true; - } - }; - - m_MaterialValuesProp = serializedObject.FindProperty("MaterialValues"); - m_MaterialValuesList = new ReorderableList(serializedObject, m_MaterialValuesProp); - m_MaterialValuesList.elementHeight = MaterialValueBindingHeight; - m_MaterialValuesList.drawElementCallback = - (rect, index, isActive, isFocused) => - { - var element = m_MaterialValuesProp.GetArrayElementAtIndex(index); - rect.height -= 4; - rect.y += 2; - if (DrawMaterialValueBinding(rect, element, PreviewSceneManager)) - { - m_changed = true; - } - }; } - protected override void OnDisable() + float m_previewSlider = 1.0f; + + static Texture2D SaveResizedImage(RenderTexture rt, UnityPath path, int size) { - base.OnDisable(); - PrefabChanged -= OnPrefabChanged; + var tex = new Texture2D(rt.width, rt.height, TextureFormat.RGB24, false); + RenderTexture.active = rt; + tex.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0); + tex.Apply(); + + //TextureScale.Scale(tex, size, size); + tex = TextureScale.GetResized(tex, size, size); + + byte[] bytes; + switch (path.Extension.ToLower()) + { + case ".png": + bytes = tex.EncodeToPNG(); + break; + + case ".jpg": + bytes = tex.EncodeToJPG(); + break; + + default: + throw new Exception(); + } + + if (Application.isPlaying) + { + UnityEngine.Object.Destroy(tex); + } + else + { + UnityEngine.Object.DestroyImmediate(tex); + } + File.WriteAllBytes(path.FullPath, bytes); + + path.ImportAsset(); + return path.LoadAsset(); } public override void OnInspectorGUI() { - base.OnInspectorGUI(); - m_changed = false; + if (PreviewSceneManager == null) + { + return; + } + serializedObject.Update(); - EditorGUILayout.Space(); - var previewSlider = EditorGUILayout.Slider("Preview Weight", m_previewSlider, 0, 1.0f); + if (m_serializedEditor == null) + { + m_serializedEditor = new SerializedBlendShapeEditor(serializedObject, PreviewSceneManager); + //m_thumbnailProp = serializedObject.FindProperty("Thumbnail"); + m_isBinaryProp = serializedObject.FindProperty("IsBinary"); + } + + int thumbnailSize = 96; + EditorGUILayout.BeginHorizontal(); + /* + var objectReferenceValue = EditorGUILayout.ObjectField(m_thumbnailProp.objectReferenceValue, typeof(Texture), false, + GUILayout.Width(thumbnailSize), GUILayout.Height(thumbnailSize)); + if (m_thumbnailProp.objectReferenceValue != objectReferenceValue) + { + m_thumbnailProp.objectReferenceValue = objectReferenceValue; + serializedObject.ApplyModifiedProperties(); + } + */ + + var changed = false; + EditorGUILayout.BeginVertical(); + base.OnInspectorGUI(); + EditorGUILayout.LabelField("Preview Weight"); + var previewSlider = EditorGUILayout.Slider(m_previewSlider, 0, 1.0f); + GUI.enabled = PreviewTexture != null; + /* + if (GUILayout.Button("save thumbnail")) + { + //var ext = "jpg"; + var ext = "png"; + var asset = UnityPath.FromAsset(target); + var path = EditorUtility.SaveFilePanel( + "save thumbnail", + asset.Parent.FullPath, + string.Format("{0}.{1}", asset.FileNameWithoutExtension, ext), + ext); + if (!string.IsNullOrEmpty(path)) + { + var thumbnail = SaveResizedImage(PreviewTexture, UnityPath.FromFullpath(path), + BlendShapeClipDrawer.ThumbnailSize); + m_thumbnailProp.objectReferenceValue = thumbnail; + serializedObject.ApplyModifiedProperties(); + } + } + */ + GUI.enabled = true; + EditorGUILayout.EndVertical(); + + if (m_isBinaryProp.boolValue) + { + previewSlider = Mathf.Round(previewSlider); + } if (previewSlider != m_previewSlider) { m_previewSlider = previewSlider; - m_changed = true; + changed = true; } + EditorGUILayout.EndHorizontal(); + Separator(); EditorGUILayout.Space(); - serializedObject.Update(); - - EditorGUILayout.PropertyField(m_BlendShapeNameProp, true); - EditorGUILayout.PropertyField(m_PresetProp, true); - - EditorGUILayout.LabelField("BlendShapeBindings", EditorStyles.boldLabel); - m_ValuesList.DoLayoutList(); - - EditorGUILayout.LabelField("MaterialValueBindings", EditorStyles.boldLabel); - m_MaterialValuesList.DoLayoutList(); - - serializedObject.ApplyModifiedProperties(); - - if (m_changed && PreviewSceneManager != null) + var result = m_serializedEditor.Draw(); + if ((changed || result.Changed) && PreviewSceneManager != null) { - PreviewSceneManager.Bake(m_target.Values, m_target.MaterialValues, m_previewSlider); + PreviewSceneManager.Bake(new PreviewSceneManager.BakeValue + { + BlendShapeBindings = result.BlendShapeBindings, + MaterialValueBindings = result.MaterialValueBindings, + Weight = m_previewSlider + }); } } @@ -123,202 +166,5 @@ namespace VRM { return BlendShapeKey.CreateFrom((BlendShapeClip)target).ToString(); } - - public static int BlendShapeBindingHeight = 60; - public static bool DrawBlendShapeBinding(Rect position, SerializedProperty property, - PreviewSceneManager scene) - { - bool changed = false; - if (scene != null) - { - var height = 16; - - var y = position.y; - var rect = new Rect(position.x, y, position.width, height); - int pathIndex; - if (StringPopup(rect, property.FindPropertyRelative("RelativePath"), scene.SkinnedMeshRendererPathList, out pathIndex)) - { - changed = true; - } - - y += height; - rect = new Rect(position.x, y, position.width, height); - int blendShapeIndex; - if (IntPopup(rect, property.FindPropertyRelative("Index"), scene.GetBlendShapeNames(pathIndex), out blendShapeIndex)) - { - changed = true; - } - - y += height; - rect = new Rect(position.x, y, position.width, height); - if (FloatSlider(rect, property.FindPropertyRelative("Weight"), 100)) - { - changed = true; - } - } - return changed; - } - - public static int MaterialValueBindingHeight = 90; - public static bool DrawMaterialValueBinding(Rect position, SerializedProperty property, - PreviewSceneManager scene) - { - bool changed = false; - if (scene != null) - { - var height = 16; - - var y = position.y; - var rect = new Rect(position.x, y, position.width, height); - int materialIndex; - if (StringPopup(rect, property.FindPropertyRelative("MaterialName"), scene.MaterialNames, out materialIndex)) - { - changed = true; - } - - if (materialIndex >= 0) - { - var materialItem = scene.GetMaterialItem(scene.MaterialNames[materialIndex]); - if (materialItem != null) - { - y += height; - rect = new Rect(position.x, y, position.width, height); - - // プロパティ名のポップアップ - int propIndex; - if (StringPopup(rect, property.FindPropertyRelative("ValueName"), materialItem.PropNames, out propIndex)) - { - changed = true; - } - - if (propIndex >= 0) - { - // 有効なプロパティ名が選択された - var propItem = materialItem.PropMap[materialItem.PropNames[propIndex]]; - { - switch (propItem.PropertyType) - { - case ShaderUtil.ShaderPropertyType.Color: - { - property.FindPropertyRelative("BaseValue").vector4Value = propItem.DefaultValues; - - // max - y += height; - rect = new Rect(position.x, y, position.width, height); - if (ColorProp(rect, property.FindPropertyRelative("TargetValue"))) - { - changed = true; - } - } - 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; - } - } - } - } - } - } - return changed; - } - - static bool StringPopup(Rect rect, SerializedProperty prop, string[] options, out int newIndex) - { - if (options == null) - { - newIndex = -1; - return false; - } - - var oldIndex = Array.IndexOf(options, prop.stringValue); - newIndex = EditorGUI.Popup(rect, oldIndex, options); - if (newIndex != oldIndex && newIndex >= 0 && newIndex < options.Length) - { - prop.stringValue = options[newIndex]; - return true; - } - else - { - return false; - } - } - - static bool IntPopup(Rect rect, SerializedProperty prop, string[] options, out int newIndex) - { - if (options == null) - { - newIndex = -1; - return false; - } - - var oldIndex = prop.intValue; - newIndex = EditorGUI.Popup(rect, oldIndex, options); - if (newIndex != oldIndex && newIndex >= 0 && newIndex < options.Length) - { - prop.intValue = newIndex; - return true; - } - else - { - return false; - } - } - - static bool FloatSlider(Rect rect, SerializedProperty prop, float maxValue) - { - var oldValue = prop.floatValue; - var newValue = EditorGUI.Slider(rect, prop.floatValue, 0, 100f); - if (newValue != oldValue) - { - prop.floatValue = newValue; - return true; - } - else - { - return false; - } - } - - static bool ColorProp(Rect rect, SerializedProperty prop) - { - var oldValue = (Color)prop.vector4Value; - var newValue = EditorGUI.ColorField(rect, prop.displayName, oldValue); - if (newValue != oldValue) - { - prop.vector4Value = newValue; - return true; - } - else - { - return false; - } - } - - 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/Editor/BlendShapeClipEditorHelper.cs b/Scripts/BlendShape/Editor/BlendShapeClipEditorHelper.cs new file mode 100644 index 000000000..cc7eec3dc --- /dev/null +++ b/Scripts/BlendShape/Editor/BlendShapeClipEditorHelper.cs @@ -0,0 +1,341 @@ +using System; +using UnityEditor; +using UnityEngine; + + +namespace VRM +{ + public static class BlendShapeClipEditorHelper + { + /// + /// BlendShape List のElement描画 + /// + public static bool DrawBlendShapeBinding(Rect position, SerializedProperty property, + PreviewSceneManager scene) + { + bool changed = false; + if (scene != null) + { + var height = 16; + + var y = position.y; + var rect = new Rect(position.x, y, position.width, height); + int pathIndex; + if (StringPopup(rect, property.FindPropertyRelative("RelativePath"), scene.SkinnedMeshRendererPathList, out pathIndex)) + { + changed = true; + } + + y += height; + rect = new Rect(position.x, y, position.width, height); + int blendShapeIndex; + if (IntPopup(rect, property.FindPropertyRelative("Index"), scene.GetBlendShapeNames(pathIndex), out blendShapeIndex)) + { + changed = true; + } + + y += height; + rect = new Rect(position.x, y, position.width, height); + if (FloatSlider(rect, property.FindPropertyRelative("Weight"), 100)) + { + changed = true; + } + } + return changed; + } + + /// + /// Material List のElement描画 + /// + public static bool DrawMaterialValueBinding(Rect position, SerializedProperty property, + PreviewSceneManager scene) + { + bool changed = false; + if (scene != null) + { + var height = 16; + + var y = position.y; + var rect = new Rect(position.x, y, position.width, height); + int materialIndex; + if (StringPopup(rect, property.FindPropertyRelative("MaterialName"), scene.MaterialNames, out materialIndex)) + { + changed = true; + } + + if (materialIndex >= 0) + { + var materialItem = scene.GetMaterialItem(scene.MaterialNames[materialIndex]); + if (materialItem != null) + { + y += height; + rect = new Rect(position.x, y, position.width, height); + + // プロパティ名のポップアップ + int propIndex; + if (StringPopup(rect, property.FindPropertyRelative("ValueName"), materialItem.PropNames, out propIndex)) + { + changed = true; + } + + if (propIndex >= 0) + { + // 有効なプロパティ名が選択された + var propItem = materialItem.PropMap[materialItem.PropNames[propIndex]]; + { + switch (propItem.PropertyType) + { + case ShaderUtil.ShaderPropertyType.Color: + { + property.FindPropertyRelative("BaseValue").vector4Value = propItem.DefaultValues; + + // max + y += height; + rect = new Rect(position.x, y, position.width, height); + if (ColorProp(rect, property.FindPropertyRelative("TargetValue"))) + { + changed = true; + } + } + 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; + } + } + } + } + } + } + return changed; + } + + #region Private + static bool StringPopup(Rect rect, SerializedProperty prop, string[] options, out int newIndex) + { + if (options == null) + { + newIndex = -1; + return false; + } + + var oldIndex = Array.IndexOf(options, prop.stringValue); + newIndex = EditorGUI.Popup(rect, oldIndex, options); + if (newIndex != oldIndex && newIndex >= 0 && newIndex < options.Length) + { + prop.stringValue = options[newIndex]; + return true; + } + else + { + return false; + } + } + + static bool IntPopup(Rect rect, SerializedProperty prop, string[] options, out int newIndex) + { + if (options == null) + { + newIndex = -1; + return false; + } + + var oldIndex = prop.intValue; + newIndex = EditorGUI.Popup(rect, oldIndex, options); + if (newIndex != oldIndex && newIndex >= 0 && newIndex < options.Length) + { + prop.intValue = newIndex; + return true; + } + else + { + return false; + } + } + + static bool FloatSlider(Rect rect, SerializedProperty prop, float maxValue) + { + var oldValue = prop.floatValue; + var newValue = EditorGUI.Slider(rect, prop.floatValue, 0, 100f); + if (newValue != oldValue) + { + prop.floatValue = newValue; + return true; + } + else + { + return false; + } + } + + static bool ColorProp(Rect rect, SerializedProperty prop) + { + var oldValue = (Color)prop.vector4Value; + var newValue = EditorGUI.ColorField(rect, prop.displayName, oldValue); + if (newValue != oldValue) + { + prop.vector4Value = newValue; + return true; + } + else + { + return false; + } + } + + static Rect AdvanceRect(ref float x, float y, float w, float h) + { + var rect = new Rect(x, y, w, h); + x += w; + return rect; + } + + static float[] v2 = new float[2]; + static GUIContent[] l2 = new GUIContent[]{ + new GUIContent("x"), + new GUIContent("y") + }; + static Vector4 TilingOffset(Rect rect, string label, Vector4 src) + { + /* + var style = new GUIStyle() + { + alignment = TextAnchor.MiddleRight, + }; + */ + + var quad = (rect.width - 56); + var x = rect.x; + //EditorGUIUtility.labelWidth = 18; + + EditorGUI.LabelField(AdvanceRect(ref x, rect.y, 40, rect.height), "Tiling"); + v2[0] = src.x; + v2[1] = src.y; + EditorGUI.MultiFloatField(AdvanceRect(ref x, rect.y, quad, rect.height), l2, v2); + src.x = v2[0]; + src.y = v2[1]; + + //EditorGUI.LabelField(AdvanceRect(ref x, rect.y, quad, rect.height), "Y", style); + //src.y = EditorGUI.FloatField(AdvanceRect(ref x, rect.y, quad, rect.height), "Y", src.y); + + rect.y += EditorGUIUtility.singleLineHeight; + x = rect.x; + EditorGUI.LabelField(AdvanceRect(ref x, rect.y, 40, rect.height), "Offset"); + v2[0] = src.z; + v2[1] = src.w; + EditorGUI.MultiFloatField(AdvanceRect(ref x, rect.y, quad, rect.height), l2, v2); + src.z = v2[0]; + src.w = v2[1]; + + //EditorGUI.LabelField(AdvanceRect(ref x, rect.y, quad * 2, rect.height), "Offset X", style); + //src.z = EditorGUI.FloatField(AdvanceRect(ref x, rect.y, quad, rect.height), "X", src.z); + + //EditorGUI.LabelField(AdvanceRect(ref x, rect.y, quad, rect.height), "Y", style); + //src.w = EditorGUI.FloatField(AdvanceRect(ref x, rect.y, quad, rect.height), "Y", src.w); + + return src; + } + + static bool OffsetProp(Rect rect, SerializedProperty prop) + { + var oldValue = prop.vector4Value; + //var newValue = EditorGUI.Vector4Field(rect, prop.displayName, oldValue); + var newValue = TilingOffset(rect, prop.displayName, oldValue); + if (newValue != oldValue) + { + prop.vector4Value = newValue; + return true; + } + else + { + return false; + } + } + #endregion + } + + /// https://gist.github.com/gszauer/7799899 + public class TextureScale + { + private static Color[] texColors; + private static Color[] newColors; + private static int w; + private static float ratioX; + private static float ratioY; + private static int w2; + + public static void Scale(Texture2D tex, int newWidth, int newHeight) + { + texColors = tex.GetPixels(); + newColors = new Color[newWidth * newHeight]; + ratioX = 1.0f / ((float)newWidth / (tex.width - 1)); + ratioY = 1.0f / ((float)newHeight / (tex.height - 1)); + w = tex.width; + w2 = newWidth; + + BilinearScale(0, newHeight); + + tex.Resize(newWidth, newHeight); + tex.SetPixels(newColors); + tex.Apply(); + } + + private static void BilinearScale(int start, int end) + { + for (var y = start; y < end; y++) + { + int yFloor = (int)Mathf.Floor(y * ratioY); + var y1 = yFloor * w; + var y2 = (yFloor + 1) * w; + var yw = y * w2; + + for (var x = 0; x < w2; x++) + { + int xFloor = (int)Mathf.Floor(x * ratioX); + var xLerp = x * ratioX - xFloor; + newColors[yw + x] = ColorLerpUnclamped(ColorLerpUnclamped(texColors[y1 + xFloor], texColors[y1 + xFloor + 1], xLerp), + ColorLerpUnclamped(texColors[y2 + xFloor], texColors[y2 + xFloor + 1], xLerp), + y * ratioY - yFloor); + } + } + } + + private static Color ColorLerpUnclamped(Color c1, Color c2, float value) + { + return new Color(c1.r + (c2.r - c1.r) * value, + c1.g + (c2.g - c1.g) * value, + c1.b + (c2.b - c1.b) * value, + c1.a + (c2.a - c1.a) * value); + } + + /// http://light11.hatenadiary.com/entry/2018/04/19/194015 + public static Texture2D GetResized(Texture2D texture, int width, int height) + { + // リサイズ後のサイズを持つRenderTextureを作成して書き込む + var rt = RenderTexture.GetTemporary(width, height); + Graphics.Blit(texture, rt); + + // リサイズ後のサイズを持つTexture2Dを作成してRenderTextureから書き込む + var preRT = RenderTexture.active; + RenderTexture.active = rt; + var ret = new Texture2D(width, height); + ret.ReadPixels(new Rect(0, 0, width, height), 0, 0); + ret.Apply(); + RenderTexture.active = preRT; + + RenderTexture.ReleaseTemporary(rt); + return ret; + } + } +} diff --git a/Scripts/BlendShape/Editor/BlendShapeClipEditorHelper.cs.meta b/Scripts/BlendShape/Editor/BlendShapeClipEditorHelper.cs.meta new file mode 100644 index 000000000..173dc5c1c --- /dev/null +++ b/Scripts/BlendShape/Editor/BlendShapeClipEditorHelper.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: fcb56a7eb1db73c4cb9ea1689635b246 +timeCreated: 1541144400 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/BlendShape/Editor/BlendShapeClipSelector.cs b/Scripts/BlendShape/Editor/BlendShapeClipSelector.cs new file mode 100644 index 000000000..c3853c35e --- /dev/null +++ b/Scripts/BlendShape/Editor/BlendShapeClipSelector.cs @@ -0,0 +1,99 @@ +using System; +using System.Linq; +using UnityEngine; +using UnityEditor; +using System.IO; +using UniGLTF; + +namespace VRM +{ + class BlendShapeClipSelector + { + BlendShapeAvatar m_avatar; + + public BlendShapeClip Selected + { + get + { + if (m_avatar == null || m_avatar.Clips == null) + { + return null; + } + if (m_selectedIndex < 0 || m_selectedIndex >= m_avatar.Clips.Count) + { + return null; + } + return m_avatar.Clips[m_selectedIndex]; + } + } + + int m_selectedIndex; + int SelectedIndex + { + get { return m_selectedIndex; } + set + { + if (m_selectedIndex == value) return; + m_selectedIndex = value; + if (m_onSelected != null) + { + m_onSelected(Selected); + } + } + } + + Action m_onSelected; + + public BlendShapeClipSelector(BlendShapeAvatar avatar, Action onSelected) + { + avatar.RemoveNullClip(); + + m_avatar = avatar; + m_onSelected = onSelected; + + onSelected(Selected); + } + + public void SelectGUI() + { + if (m_avatar != null && m_avatar.Clips != null) + { + EditorGUILayout.Space(); + EditorGUILayout.LabelField("Select BlendShapeClip", EditorStyles.boldLabel); + var array = m_avatar.Clips + .Select(x => x != null + ? BlendShapeKey.CreateFrom(x).ToString() + : "null" + ).ToArray(); + SelectedIndex = GUILayout.SelectionGrid(SelectedIndex, array, 4); + } + + if (GUILayout.Button("Add BlendShapeClip")) + { + var dir = Path.GetDirectoryName(AssetDatabase.GetAssetPath(m_avatar)); + var path = EditorUtility.SaveFilePanel( + "Create BlendShapeClip", + dir, + string.Format("BlendShapeClip#{0}.asset", m_avatar.Clips.Count), + "asset"); + if (!string.IsNullOrEmpty(path)) + { + var clip = BlendShapeAvatar.CreateBlendShapeClip(path.ToUnityRelativePath()); + //clip.Prefab = AssetDatabase.LoadAssetAtPath(AssetDatabase.GetAssetPath(target)); + + m_avatar.Clips.Add(clip); + } + } + } + + public void DuplicateWarn() + { + var key = BlendShapeKey.CreateFrom(Selected); + if (m_avatar.Clips.Where(x => key.Match(x)).Count() > 1) + { + EditorGUILayout.HelpBox("duplicate clip: " + key, MessageType.Error); + } + } + } + +} \ No newline at end of file diff --git a/Scripts/BlendShape/Editor/BlendShapeClipSelector.cs.meta b/Scripts/BlendShape/Editor/BlendShapeClipSelector.cs.meta new file mode 100644 index 000000000..b5985862a --- /dev/null +++ b/Scripts/BlendShape/Editor/BlendShapeClipSelector.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 7842ed7a65c676740aa02678316dd625 +timeCreated: 1541138009 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/BlendShape/Editor/PreviewEditor.cs b/Scripts/BlendShape/Editor/PreviewEditor.cs index 637a70f6c..b1615a1cb 100644 --- a/Scripts/BlendShape/Editor/PreviewEditor.cs +++ b/Scripts/BlendShape/Editor/PreviewEditor.cs @@ -2,6 +2,8 @@ using UnityEngine; using UnityEditorInternal; using System; +using System.Linq; +using System.Collections.Generic; namespace VRM { @@ -11,7 +13,7 @@ namespace VRM /// * https://github.com/Unity-Technologies/UnityCsReference/blob/11bcfd801fccd2a52b09bb6fd636c1ddcc9f1705/Editor/Mono/Inspector/ModelInspector.cs /// /// - public class PreviewEditor : Editor + public abstract class PreviewEditor : Editor { /// /// PreviewRenderUtilityを管理する。 @@ -46,6 +48,8 @@ namespace VRM private set { if (m_prefab == value) return; + + //Debug.LogFormat("Prefab = {0}", value); m_prefab = value; if (m_scene != null) @@ -62,30 +66,23 @@ namespace VRM { m_scene.gameObject.SetActive(false); } - RaisePrefabChanged(); + + Bake(); } } } - protected event Action PrefabChanged; - void RaisePrefabChanged() - { - var handler = PrefabChanged; - if (handler == null) return; - handler(); - } + + protected abstract PreviewSceneManager.BakeValue GetBakeValue(); /// - /// シーンにBlendShapeとMaterialMorphを適用する + /// Preview シーンに BlendShape と MaterialValue を適用する /// - /// - /// - /// - protected void Bake(BlendShapeBinding[] values, MaterialValueBinding[] materialValues, float weight) + protected void Bake() { if (m_scene != null) { //Debug.Log("Bake"); - m_scene.Bake(values, materialValues, weight); + m_scene.Bake(GetBakeValue()); } } @@ -110,6 +107,7 @@ namespace VRM protected virtual void OnEnable() { m_renderer = new PreviewFaceRenderer(); + Prefab = GetPrefab(); } @@ -133,20 +131,35 @@ namespace VRM } } + protected static void Separator() + { + EditorGUILayout.Space(); + EditorGUILayout.BeginHorizontal(); + //GUILayout.Space(); + GUILayout.Box("", GUILayout.ExpandWidth(true), GUILayout.Height(1)); + EditorGUILayout.EndHorizontal(); + EditorGUILayout.Space(); + } + public override void OnInspectorGUI() { //base.OnInspectorGUI(); - Prefab = (GameObject)EditorGUILayout.ObjectField("prefab", Prefab, typeof(GameObject), false); + Prefab = (GameObject)EditorGUILayout.ObjectField("Preview Prefab", Prefab, typeof(GameObject), false); + + //Separator(); } private static int sliderHash = "Slider".GetHashCode(); - Vector2 m_previewDir; - float m_distance = 1.0f; + float m_yaw = 180.0f; + float m_pitch; + Vector3 m_position = new Vector3(0, 0, -0.8f); // very important to override this, it tells Unity to render an ObjectPreview at the bottom of the inspector public override bool HasPreviewGUI() { return true; } + public RenderTexture PreviewTexture; + // the main ObjectPreview function... it's called constantly, like other IMGUI On*GUI() functions public override void OnPreviewGUI(Rect r, GUIStyle background) { @@ -155,23 +168,31 @@ namespace VRM { if (Event.current.type == EventType.Repaint) { - EditorGUI.DropShadowLabel(new Rect(r.x, r.y, r.width, 40f), + EditorGUI.DropShadowLabel(new Rect(r.x, r.y, r.width, 40f), "Mesh preview requires\nrender texture support"); } return; } + var src = r; + + var min = Mathf.Min(r.width, r.height); + r.width = min; + r.height = min; + r.x = src.x + (src.width - min) / 2; + r.y = src.y + (src.height - min) / 2; + //previewDir = Drag2D(previewDir, r); { int controlId = GUIUtility.GetControlID(sliderHash, FocusType.Passive); - Event current = Event.current; - switch (current.GetTypeForControl(controlId)) + Event e = Event.current; + switch (e.GetTypeForControl(controlId)) { case EventType.MouseDown: - if (r.Contains(current.mousePosition) && (double)r.width > 50.0) + if (r.Contains(e.mousePosition) && (double)r.width > 50.0) { GUIUtility.hotControl = controlId; - current.Use(); + e.Use(); EditorGUIUtility.SetWantsMouseJumping(1); break; } @@ -186,25 +207,41 @@ namespace VRM case EventType.MouseDrag: if (GUIUtility.hotControl == controlId) { - m_previewDir -= current.delta * (!current.shift ? 1f : 3f) / Mathf.Min(r.width, r.height) * 140f; - m_previewDir.y = Mathf.Clamp(m_previewDir.y, -90f, 90f); - current.Use(); - GUI.changed = true; + if (e.button == 2) + { + var shift = e.delta * (!e.shift ? 1f : 3f) / Mathf.Min(r.width, r.height); + m_position.x -= shift.x; + m_position.y += shift.y; + e.Use(); + GUI.changed = true; + } + else if ( + e.button == 0 || + e.button == 1) + { + var shift = e.delta * (!e.shift ? 1f : 3f) / Mathf.Min(r.width, r.height) * 140f; + m_yaw += shift.x; + m_pitch += shift.y; + m_pitch = Mathf.Clamp(m_pitch, -90f, 90f); + e.Use(); + GUI.changed = true; + } break; } break; case EventType.ScrollWheel: //Debug.LogFormat("wheel: {0}", current.delta); - if (r.Contains(current.mousePosition)){ - if (current.delta.y > 0) + if (r.Contains(e.mousePosition)) + { + if (e.delta.y > 0) { - m_distance *= 1.1f; + m_position.z *= 1.1f; Repaint(); } - else if (current.delta.y < 0) + else if (e.delta.y < 0) { - m_distance *= 0.9f; + m_position.z *= 0.9f; Repaint(); } } @@ -222,11 +259,11 @@ namespace VRM if (m_renderer != null && m_scene != null) { - var texture = m_renderer.Render(r, background, m_scene, m_previewDir, m_distance); - if (texture != null) + PreviewTexture = m_renderer.Render(r, background, m_scene, m_yaw, m_pitch, m_position) as RenderTexture; + if (PreviewTexture != null) { // draw the RenderTexture in the ObjectPreview pane - GUI.DrawTexture(r, texture, ScaleMode.StretchToFill, false); + GUI.DrawTexture(r, PreviewTexture, ScaleMode.StretchToFill, false); } } } diff --git a/Scripts/BlendShape/Editor/PreviewFaceRenderer.cs b/Scripts/BlendShape/Editor/PreviewFaceRenderer.cs index 17ad0efc4..504fbcfc0 100644 --- a/Scripts/BlendShape/Editor/PreviewFaceRenderer.cs +++ b/Scripts/BlendShape/Editor/PreviewFaceRenderer.cs @@ -81,7 +81,8 @@ namespace VRM //const float FACTOR = 0.1f; - public Texture Render(Rect r, GUIStyle background, PreviewSceneManager scene, Vector2 drag, float distance) + public Texture Render(Rect r, GUIStyle background, PreviewSceneManager scene, + float yaw, float pitch, Vector3 position) { if (scene == null) return null; @@ -90,7 +91,7 @@ namespace VRM m_previewUtility.BeginPreview(r, background); // set up the PreviewRenderUtility's mini internal scene // setup the ObjectPreview's camera - scene.SetupCamera(PreviewCamera, scene.TargetPosition, -drag.x, drag.y, distance); + scene.SetupCamera(PreviewCamera, scene.TargetPosition, yaw, pitch, position); foreach (var item in scene.EnumRenderItems) { @@ -115,7 +116,7 @@ namespace VRM } } -#region IDisposable Support + #region IDisposable Support private bool disposedValue = false; // 重複する呼び出しを検出するには protected virtual void Dispose(bool disposing) @@ -153,6 +154,6 @@ namespace VRM // TODO: 上のファイナライザーがオーバーライドされる場合は、次の行のコメントを解除してください。 // GC.SuppressFinalize(this); } -#endregion + #endregion } } diff --git a/Scripts/BlendShape/Editor/SerializedBlendShapeClipEditor.cs b/Scripts/BlendShape/Editor/SerializedBlendShapeClipEditor.cs new file mode 100644 index 000000000..7d21c8341 --- /dev/null +++ b/Scripts/BlendShape/Editor/SerializedBlendShapeClipEditor.cs @@ -0,0 +1,309 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; + +namespace VRM +{ + public class SerializedBlendShapeEditor + { + BlendShapeClip m_targetObject; + + SerializedObject m_serializedObject; + + #region Properties + SerializedProperty m_thumbnail; + SerializedProperty m_blendShapeNameProp; + SerializedProperty m_presetProp; + + SerializedProperty m_isBinaryProp; + #endregion + + #region BlendShapeBind + public static int BlendShapeBindingHeight = 60; + ReorderableList m_ValuesList; + + SerializedProperty m_valuesProp; + #endregion + + #region MaterialValueBind + const int MaterialValueBindingHeight = 90; + ReorderableList m_MaterialValuesList; + + SerializedProperty m_materialsProp; + #endregion + + #region Editor values + float m_previewSlider = 1.0f; + + bool m_changed; + + int m_mode; + static string[] MODES = new[]{ + "BlendShape", + "BlendShape List", + "Material List" + }; + + MeshPreviewItem[] m_items; + #endregion + + public SerializedBlendShapeEditor(SerializedObject serializedObject, + PreviewSceneManager previewSceneManager) : this( + serializedObject, (BlendShapeClip)serializedObject.targetObject, previewSceneManager) + { } + + public SerializedBlendShapeEditor(BlendShapeClip blendShapeClip, + PreviewSceneManager previewSceneManager) : this( + new SerializedObject(blendShapeClip), blendShapeClip, previewSceneManager) + { } + + public SerializedBlendShapeEditor(SerializedObject serializedObject, BlendShapeClip targetObject, + PreviewSceneManager previewSceneManager) + { + this.m_serializedObject = serializedObject; + this.m_targetObject = targetObject; + + //m_thumbnail = serializedObject.FindProperty("Thumbnail"); + m_blendShapeNameProp = serializedObject.FindProperty("BlendShapeName"); + m_presetProp = serializedObject.FindProperty("Preset"); + m_isBinaryProp = serializedObject.FindProperty("IsBinary"); + + m_valuesProp = serializedObject.FindProperty("Values"); + + m_ValuesList = new ReorderableList(serializedObject, m_valuesProp); + m_ValuesList.elementHeight = BlendShapeBindingHeight; + m_ValuesList.drawElementCallback = + (rect, index, isActive, isFocused) => + { + var element = m_valuesProp.GetArrayElementAtIndex(index); + rect.height -= 4; + rect.y += 2; + if (BlendShapeClipEditorHelper.DrawBlendShapeBinding(rect, element, previewSceneManager)) + { + m_changed = true; + } + }; + + m_materialsProp = serializedObject.FindProperty("MaterialValues"); + m_MaterialValuesList = new ReorderableList(serializedObject, m_materialsProp); + m_MaterialValuesList.elementHeight = MaterialValueBindingHeight; + m_MaterialValuesList.drawElementCallback = + (rect, index, isActive, isFocused) => + { + var element = m_materialsProp.GetArrayElementAtIndex(index); + rect.height -= 4; + rect.y += 2; + if (BlendShapeClipEditorHelper.DrawMaterialValueBinding(rect, element, previewSceneManager)) + { + m_changed = true; + } + }; + + m_items = previewSceneManager.EnumRenderItems + .Where(x => x.SkinnedMeshRenderer != null) + .ToArray(); + } + + public struct DrawResult + { + public bool Changed; + + public BlendShapeBinding[] BlendShapeBindings; + + public MaterialValueBinding[] MaterialValueBindings; + } + + public DrawResult Draw() + { + m_changed = false; + + m_serializedObject.Update(); + + // Readonly のBlendShapeClip参照 + GUI.enabled = false; + EditorGUILayout.ObjectField("Current clip", + m_targetObject, typeof(BlendShapeClip), false); + GUI.enabled = true; + + EditorGUILayout.PropertyField(m_blendShapeNameProp, true); + EditorGUILayout.PropertyField(m_presetProp, true); + + // v0.45 Added. Binary flag + EditorGUILayout.PropertyField(m_isBinaryProp, true); + + EditorGUILayout.Space(); + //m_mode = EditorGUILayout.Popup("SourceType", m_mode, MODES); + m_mode = GUILayout.Toolbar(m_mode, MODES); + switch (m_mode) + { + case 0: + { + ClipGUI(); + } + break; + + case 1: + { + if (GUILayout.Button("Clear")) + { + m_changed = true; + m_valuesProp.arraySize = 0; + } + m_ValuesList.DoLayoutList(); + } + break; + + case 2: + { + if (GUILayout.Button("Clear")) + { + m_changed = true; + m_materialsProp.arraySize = 0; + } + m_MaterialValuesList.DoLayoutList(); + } + break; + } + + m_serializedObject.ApplyModifiedProperties(); + + return new DrawResult + { + Changed = m_changed, + BlendShapeBindings = m_targetObject.Values, + MaterialValueBindings = m_targetObject.MaterialValues + }; + } + + void ClipGUI() + { + var changed = BlendShapeBindsGUI(); + if (changed) + { + string maxWeightName; + var bindings = GetBindings(out maxWeightName); + m_valuesProp.ClearArray(); + m_valuesProp.arraySize = bindings.Length; + for (int i = 0; i < bindings.Length; ++i) + { + var item = m_valuesProp.GetArrayElementAtIndex(i); + + var endProperty = item.GetEndProperty(); + while (item.NextVisible(true)) + { + if (SerializedProperty.EqualContents(item, endProperty)) + { + break; + } + + switch (item.name) + { + case "RelativePath": + item.stringValue = bindings[i].RelativePath; + break; + + case "Index": + item.intValue = bindings[i].Index; + break; + + case "Weight": + item.floatValue = bindings[i].Weight; + break; + + default: + throw new Exception(); + } + } + } + + m_changed = true; + } + } + + List m_meshFolds = new List(); + bool BlendShapeBindsGUI() + { + bool changed = false; + int foldIndex = 0; + // すべてのSkinnedMeshRendererを列挙する + foreach (var renderer in m_items.Select(x => x.SkinnedMeshRenderer)) + { + var mesh = renderer.sharedMesh; + if (mesh != null && mesh.blendShapeCount > 0) + { + //var relativePath = UniGLTF.UnityExtensions.RelativePathFrom(renderer.transform, m_target.transform); + //EditorGUILayout.LabelField(m_target.name + "/" + item.Path); + + if (foldIndex >= m_meshFolds.Count) + { + m_meshFolds.Add(false); + } + m_meshFolds[foldIndex] = EditorGUILayout.Foldout(m_meshFolds[foldIndex], renderer.name); + if (m_meshFolds[foldIndex]) + { + //EditorGUI.indentLevel += 1; + for (int i = 0; i < mesh.blendShapeCount; ++i) + { + var src = renderer.GetBlendShapeWeight(i); + var dst = EditorGUILayout.Slider(mesh.GetBlendShapeName(i), src, 0, 100.0f); + if (dst != src) + { + renderer.SetBlendShapeWeight(i, dst); + changed = true; + } + } + //EditorGUI.indentLevel -= 1; + } + ++foldIndex; + } + } + return changed; + } + + BlendShapeBinding[] GetBindings(out string _maxWeightName) + { + var maxWeight = 0.0f; + var maxWeightName = ""; + // weightのついたblendShapeを集める + var values = m_items + .SelectMany(x => + { + var mesh = x.SkinnedMeshRenderer.sharedMesh; + + var relativePath = x.Path; + + var list = new List(); + if (mesh != null) + { + for (int i = 0; i < mesh.blendShapeCount; ++i) + { + var weight = x.SkinnedMeshRenderer.GetBlendShapeWeight(i); + if (weight == 0) + { + continue; + } + var name = mesh.GetBlendShapeName(i); + if (weight > maxWeight) + { + maxWeightName = name; + maxWeight = weight; + } + list.Add(new BlendShapeBinding + { + Index = i, + RelativePath = relativePath, + Weight = weight + }); + } + } + return list; + }).ToArray() + ; + _maxWeightName = maxWeightName; + return values; + } + } +} diff --git a/Scripts/BlendShape/Editor/SerializedBlendShapeClipEditor.cs.meta b/Scripts/BlendShape/Editor/SerializedBlendShapeClipEditor.cs.meta new file mode 100644 index 000000000..209e1f5b7 --- /dev/null +++ b/Scripts/BlendShape/Editor/SerializedBlendShapeClipEditor.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: eaafa3ff7bf991642b922e6af7ecbbc0 +timeCreated: 1541081003 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/BlendShape/Editor/VRMBlnedShapeProxyEditor.cs b/Scripts/BlendShape/Editor/VRMBlnedShapeProxyEditor.cs index 622d67328..153ddaff6 100644 --- a/Scripts/BlendShape/Editor/VRMBlnedShapeProxyEditor.cs +++ b/Scripts/BlendShape/Editor/VRMBlnedShapeProxyEditor.cs @@ -23,22 +23,14 @@ namespace VRM m_key = key; } - public void Slider() + public KeyValuePair Slider() { - if (m_target.BlendShapeAvatar == null) - { - return; - } - var oldValue = m_target.GetValue(m_key); var enable = GUI.enabled; GUI.enabled = Application.isPlaying; var newValue = EditorGUILayout.Slider(m_key.ToString(), oldValue, 0, 1.0f); GUI.enabled = enable; - if (Application.isPlaying && oldValue != newValue) - { - m_target.SetValue(m_key, newValue); - } + return new KeyValuePair(m_key, newValue); } } List m_sliders; @@ -65,12 +57,14 @@ namespace VRM EditorGUILayout.HelpBox("Enable when playing", MessageType.Info); } + if (m_target.BlendShapeAvatar == null) + { + return; + } + if (m_sliders != null) { - foreach (var slider in m_sliders) - { - slider.Slider(); - } + m_target.SetValues(m_sliders.Select(x => x.Slider())); } } } diff --git a/Scripts/BlendShape/MaterialValueBindingMerger.cs b/Scripts/BlendShape/MaterialValueBindingMerger.cs new file mode 100644 index 000000000..447795bb2 --- /dev/null +++ b/Scripts/BlendShape/MaterialValueBindingMerger.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using UniGLTF; + +namespace VRM +{ + /// + /// Base + (A.Target - Base) * A.Weight + (B.Target - Base) * B.Weight ... + /// + class MaterialValueBindingMerger + { + /// + /// 名前とmaterialのマッピング + /// + Dictionary m_materialMap = new Dictionary(); + + delegate void Setter(float value, bool firstValue); + + /// + /// MaterialValueの適用値を蓄積する + /// + /// + /// + /// + Dictionary m_materialValueMap = new Dictionary(); + + Dictionary m_materialSetterMap = new Dictionary(); + + public MaterialValueBindingMerger(Dictionary clipMap, Transform root) + { + foreach (var x in root.Traverse()) + { + var renderer = x.GetComponent(); + if (renderer != null) + { + foreach (var y in renderer.sharedMaterials.Where(y => y != null)) + { + if (!string.IsNullOrEmpty(y.name)) + { + if (!m_materialMap.ContainsKey(y.name)) + { + m_materialMap.Add(y.name, y); + } + } + } + } + } + + foreach (var kv in clipMap) + { + foreach (var binding in kv.Value.MaterialValues) + { + if (!m_materialSetterMap.ContainsKey(binding)) + { + Material target; + if (m_materialMap.TryGetValue(binding.MaterialName, out target)) + { + if (binding.ValueName.EndsWith("_ST_S")) + { + var valueName = binding.ValueName.Substring(0, binding.ValueName.Length - 2); + Setter setter = (value, firstValue) => + { + var propValue = firstValue + ? (binding.BaseValue + (binding.TargetValue - binding.BaseValue) * value) + : (target.GetVector(binding.ValueName) + (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); + Setter setter = (value, firstValue) => + { + var propValue = firstValue + ? (binding.BaseValue + (binding.TargetValue - binding.BaseValue) * value) + : (target.GetVector(binding.ValueName) + (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 + { + Setter vec4Setter = (value, firstValue) => + { + var propValue = firstValue + ? (binding.BaseValue + (binding.TargetValue - binding.BaseValue) * value) + : (target.GetVector(binding.ValueName) + (binding.TargetValue - binding.BaseValue) * value) + ; + target.SetColor(binding.ValueName, propValue); + }; + m_materialSetterMap.Add(binding, vec4Setter); + } + } + else + { + Debug.LogWarningFormat("material: {0} not found", binding.MaterialName); + } + } + } + } + } + + public void RestoreMaterialInitialValues(IEnumerable clips) + { + if (m_materialMap != null) + { + foreach (var x in clips) + { + foreach (var y in x.MaterialValues) + { + // restore values + Material material; + if (m_materialMap.TryGetValue(y.MaterialName, out material)) + { + material.SetColor(y.ValueName, y.BaseValue); + } + else + { + Debug.LogWarningFormat("{0} not found", y.MaterialName); + } + } + } + } + } + + public void ImmediatelySetValue(BlendShapeClip clip, float value) + { + foreach (var binding in clip.MaterialValues) + { + Setter setter; + if (m_materialSetterMap.TryGetValue(binding, out setter)) + { + setter(value, true); + } + } + } + + public void AccumulateValue(BlendShapeClip clip, float value) + { + foreach (var binding in clip.MaterialValues) + { + // 積算 + float acc; + if (m_materialValueMap.TryGetValue(binding, out acc)) + { + m_materialValueMap[binding] = acc + value; + } + else + { + m_materialValueMap[binding] = value; + } + } + } + + Dictionary m_used = new Dictionary(); + + public void Apply() + { + m_used.Clear(); + + // (binding.Value-Base) * weight を足す + foreach (var kv in m_materialValueMap) + { + Setter setter; + if (m_materialSetterMap.TryGetValue(kv.Key, out setter)) + { + int count; + if (m_used.TryGetValue(kv.Key.MaterialName, out count)) + { + m_used[kv.Key.MaterialName] += 1; + } + else + { + m_used.Add(kv.Key.MaterialName, 1); + } + + setter(kv.Value, count == 0); + } + } + m_materialValueMap.Clear(); + } + } +} diff --git a/Scripts/BlendShape/MaterialValueBindingMerger.cs.meta b/Scripts/BlendShape/MaterialValueBindingMerger.cs.meta new file mode 100644 index 000000000..53929f073 --- /dev/null +++ b/Scripts/BlendShape/MaterialValueBindingMerger.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 4ccc88d77d5d1e74499d053083ade08d +timeCreated: 1541229189 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/BlendShape/MeshPreviewItem.cs b/Scripts/BlendShape/MeshPreviewItem.cs index be9293aa1..0280f185b 100644 --- a/Scripts/BlendShape/MeshPreviewItem.cs +++ b/Scripts/BlendShape/MeshPreviewItem.cs @@ -50,6 +50,7 @@ namespace VRM switch (propType) { case ShaderUtil.ShaderPropertyType.Color: + // 色 item.PropMap.Add(name, new PropItem { PropertyType = propType, @@ -59,13 +60,35 @@ namespace VRM break; case ShaderUtil.ShaderPropertyType.TexEnv: - name += "_ST"; - item.PropMap.Add(name, new PropItem + // テクスチャ { - PropertyType = propType, - DefaultValues = material.GetVector(name), - }); - propNames.Add(name); + name += "_ST"; + item.PropMap.Add(name, new PropItem + { + PropertyType = propType, + DefaultValues = material.GetVector(name), + }); + propNames.Add(name); + } + // 縦横分離用 + { + var st_name = name + "_S"; + item.PropMap.Add(st_name, new PropItem + { + PropertyType = propType, + DefaultValues = material.GetVector(name), + }); + propNames.Add(st_name); + } + { + var st_name = name + "_T"; + item.PropMap.Add(st_name, new PropItem + { + PropertyType = propType, + DefaultValues = material.GetVector(name), + }); + propNames.Add(st_name); + } break; } } @@ -130,7 +153,7 @@ namespace VRM Materials = materials; } - public void Bake(BlendShapeBinding[] values, float weight) + public void Bake(IEnumerable values, float weight) { if (SkinnedMeshRenderer == null) return; @@ -147,15 +170,15 @@ namespace VRM { if (x.RelativePath == Path) { - if(x.Index>=0 && x.Index < SkinnedMeshRenderer.sharedMesh.blendShapeCount) + if (x.Index >= 0 && x.Index < SkinnedMeshRenderer.sharedMesh.blendShapeCount) { SkinnedMeshRenderer.SetBlendShapeWeight(x.Index, x.Weight * weight); } else { - Debug.LogWarningFormat("Out of range {0}: 0 <= {1} < {2}", - SkinnedMeshRenderer.name, - x.Index, + Debug.LogWarningFormat("Out of range {0}: 0 <= {1} < {2}", + SkinnedMeshRenderer.name, + x.Index, SkinnedMeshRenderer.sharedMesh.blendShapeCount); } } diff --git a/Scripts/BlendShape/PreviewSceneManager.cs b/Scripts/BlendShape/PreviewSceneManager.cs index 849e1b8a6..0ed21272f 100644 --- a/Scripts/BlendShape/PreviewSceneManager.cs +++ b/Scripts/BlendShape/PreviewSceneManager.cs @@ -80,6 +80,7 @@ namespace VRM private void Initialize(GameObject prefab) { + //Debug.LogFormat("[PreviewSceneManager.Initialize] {0}", prefab); Prefab = prefab; var materialNames = new List(); @@ -90,7 +91,7 @@ namespace VRM if (string.IsNullOrEmpty(src.name)) return null; // ! Material dst; - if(!map.TryGetValue(src, out dst)) + if (!map.TryGetValue(src, out dst)) { dst = new Material(src); map.Add(src, dst); @@ -111,7 +112,7 @@ namespace VRM m_blendShapeMeshes = m_meshes .Where(x => x.SkinnedMeshRenderer != null - && x.SkinnedMeshRenderer.sharedMesh.blendShapeCount>0) + && x.SkinnedMeshRenderer.sharedMesh.blendShapeCount > 0) .ToArray(); //Bake(values, materialValues); @@ -183,7 +184,8 @@ namespace VRM public MaterialItem GetMaterialItem(string materialName) { MaterialItem item; - if(!m_materialMap.TryGetValue(materialName, out item)){ + if (!m_materialMap.TryGetValue(materialName, out item)) + { return null; } @@ -204,22 +206,34 @@ namespace VRM } #if UNITY_EDITOR - Bounds m_bounds; - public void Bake(BlendShapeBinding[] values=null, MaterialValueBinding[] materialValues=null, float weight=1.0f) + + public struct BakeValue { - //Debug.LogFormat("Bake"); + public IEnumerable BlendShapeBindings; + public IEnumerable MaterialValueBindings; + public float Weight; + } + + Bounds m_bounds; + public void Bake(BakeValue bake) + { + // + // Bake BlendShape + // m_bounds = default(Bounds); if (m_meshes != null) { foreach (var x in m_meshes) { - x.Bake(values, weight); + x.Bake(bake.BlendShapeBindings, bake.Weight); m_bounds.Expand(x.Mesh.bounds.size); } } - // Udpate Material - if (materialValues != null && m_materialMap != null) + // + // Update Material + // + if (bake.MaterialValueBindings != null && m_materialMap != null) { // clear //Debug.LogFormat("clear material"); @@ -231,7 +245,7 @@ namespace VRM } } - foreach (var x in materialValues) + foreach (var x in bake.MaterialValueBindings) { MaterialItem item; if (m_materialMap.TryGetValue(x.MaterialName, out item)) @@ -240,28 +254,29 @@ namespace VRM PropItem prop; if (item.PropMap.TryGetValue(x.ValueName, out prop)) { - var value = x.BaseValue + (x.TargetValue - x.BaseValue) * weight; - item.Material.SetColor(x.ValueName, value); + var valueName = x.ValueName; + if (valueName.EndsWith("_ST_S") + || valueName.EndsWith("_ST_T")) + { + valueName = valueName.Substring(0, valueName.Length - 2); + } + + var value = item.Material.GetVector(valueName); + Debug.LogFormat("{0} => {1}", valueName, x.TargetValue); + value += ((x.TargetValue - x.BaseValue) * bake.Weight); + item.Material.SetColor(valueName, value); } } } } - } #endif - /* - int PreviewLayer - { - get; - set; - } - */ /// /// カメラパラメーターを決める /// /// - public void SetupCamera(Camera camera, Vector3 target, float yaw, float pitch, float distance) + public void SetupCamera(Camera camera, Vector3 target, float yaw, float pitch, Vector3 position) { camera.backgroundColor = Color.gray; camera.clearFlags = CameraClearFlags.Color; @@ -273,16 +288,16 @@ namespace VRM camera.fieldOfView = 27f; camera.nearClipPlane = 0.3f; - camera.farClipPlane = distance /*+ magnitude*/ * 2.1f; + camera.farClipPlane = -position.z /*+ magnitude*/ * 2.1f; -#if false - // this used to be "-Vector3.forward * num" but I hardcoded my camera position instead - camera.transform.position = new Vector3(0f, 1.4f, distance); - camera.transform.rotation = Quaternion.Euler(0, 180f, 0); -#else - camera.transform.position = target + Quaternion.Euler(pitch, yaw, 0) * Vector3.forward * distance; - camera.transform.LookAt(target); -#endif + var t = Matrix4x4.Translate(position); + var r = Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(pitch, yaw, 0), Vector3.one); + // 回転してから移動 + var m = r * t; + + camera.transform.position = target + m.ExtractPosition(); + camera.transform.rotation = m.ExtractRotation(); + //camera.transform.LookAt(target); //previewLayer のみ表示する //camera.cullingMask = 1 << PreviewLayer; diff --git a/Scripts/BlendShape/VRMBlendShapeProxy.cs b/Scripts/BlendShape/VRMBlendShapeProxy.cs index 9d07bd519..9725b0347 100644 --- a/Scripts/BlendShape/VRMBlendShapeProxy.cs +++ b/Scripts/BlendShape/VRMBlendShapeProxy.cs @@ -159,6 +159,7 @@ namespace VRM } } + /* /// /// Clear all blendShape values /// @@ -169,6 +170,7 @@ namespace VRM m_merger.Clear(); } } + */ /// /// Apply blendShape values that use SetValue apply=false diff --git a/Scripts/Format/VRMImporterContext.cs b/Scripts/Format/VRMImporterContext.cs index 4be74a047..e73bcb11f 100644 --- a/Scripts/Format/VRMImporterContext.cs +++ b/Scripts/Format/VRMImporterContext.cs @@ -152,6 +152,7 @@ namespace VRM { asset.BlendShapeName = groupName; asset.Preset = EnumUtil.TryParseOrDefault(group.presetName); + asset.IsBinary = group.isBinary; if (asset.Preset == BlendShapePreset.Unknown) { // fallback @@ -250,7 +251,7 @@ namespace VRM AvatarDescription = GLTF.extensions.VRM.humanoid.ToDescription(Nodes); AvatarDescription.name = "AvatarDescription"; HumanoidAvatar = AvatarDescription.CreateAvatar(Root.transform); - if(!HumanoidAvatar.isValid || !HumanoidAvatar.isHuman) + if (!HumanoidAvatar.isValid || !HumanoidAvatar.isHuman) { throw new Exception("fail to create avatar"); } @@ -289,7 +290,7 @@ namespace VRM meta.Title = gltfMeta.title; var thumbnail = GetTexture(gltfMeta.texture); - if (thumbnail!=null) + if (thumbnail != null) { // ロード済み meta.Thumbnail = thumbnail.Texture; diff --git a/Scripts/Format/glTF_VRM_BlendShape.cs b/Scripts/Format/glTF_VRM_BlendShape.cs index 522051d6a..7338872c2 100644 --- a/Scripts/Format/glTF_VRM_BlendShape.cs +++ b/Scripts/Format/glTF_VRM_BlendShape.cs @@ -91,7 +91,7 @@ namespace VRM [JsonSchema(Description = "Expression name")] public string name; - [JsonSchema(Description = "Predefined Expression name", EnumValues =new object[] { + [JsonSchema(Description = "Predefined Expression name", EnumValues = new object[] { "unknown", "neutral", "a", @@ -119,10 +119,14 @@ namespace VRM [JsonSchema(Description = "Material animation references.")] public List materialValues = new List(); + [JsonSchema(Description = "0 or 1. Do not allow an intermediate value. Value should rounded")] + public bool isBinary; + protected override void SerializeMembers(GLTFJsonFormatter f) { f.KeyValue(() => name); f.KeyValue(() => presetName); + f.KeyValue(() => isBinary); f.KeyValue(() => binds); f.KeyValue(() => materialValues); } @@ -157,6 +161,7 @@ namespace VRM { name = clip.BlendShapeName, presetName = clip.Preset.ToString().ToLower(), + isBinary = clip.IsBinary, binds = list, materialValues = materialList, }; diff --git a/Scripts/Meta/Editor/VRMMetaEditor.cs b/Scripts/Meta/Editor/VRMMetaEditor.cs index b5e861d22..6e90bb5db 100644 --- a/Scripts/Meta/Editor/VRMMetaEditor.cs +++ b/Scripts/Meta/Editor/VRMMetaEditor.cs @@ -70,12 +70,12 @@ namespace VRM } bool m_foldoutInfo = true; - bool m_foldoutPersmission=true; - bool m_foldoutDistribution=true; + bool m_foldoutPersmission = true; + bool m_foldoutDistribution = true; void VRMMetaObjectGUI(SerializedObject so) { InitMap(so); - if (m_propMap == null || m_propMap.Count==0) return; + if (m_propMap == null || m_propMap.Count == 0) return; so.Update(); @@ -95,8 +95,8 @@ namespace VRM EditorGUILayout.PropertyField(m_propMap["Author"]); EditorGUILayout.PropertyField(m_propMap["ContactInformation"]); EditorGUILayout.PropertyField(m_propMap["Reference"]); - var thumbnail = m_propMap["Thumbnail"]; - thumbnail.objectReferenceValue = TextureField("", (Texture2D)thumbnail.objectReferenceValue, 100); + //var thumbnail = m_propMap["Thumbnail"]; + //thumbnail.objectReferenceValue = TextureField("", (Texture2D)thumbnail.objectReferenceValue, 100); } EditorGUILayout.LabelField("License ", EditorStyles.boldLabel); @@ -121,7 +121,7 @@ namespace VRM EditorGUILayout.PropertyField(m_propMap["OtherLicenseUrl"]); } } - + so.ApplyModifiedProperties(); }