diff --git a/Assets/VRM10/Editor/Components/Constraint/VRM10RotationConstraintEditor.cs b/Assets/VRM10/Editor/Components/Constraint/VRM10RotationConstraintEditor.cs index 870cb2f286..0b8140aec4 100644 --- a/Assets/VRM10/Editor/Components/Constraint/VRM10RotationConstraintEditor.cs +++ b/Assets/VRM10/Editor/Components/Constraint/VRM10RotationConstraintEditor.cs @@ -1,6 +1,4 @@ -using System.Text; using UnityEditor; -using UnityEngine; namespace UniVRM10 { diff --git a/Assets/VRM10/Editor/Components/Expression/ExpressionSlider.cs b/Assets/VRM10/Editor/Components/Expression/ExpressionSlider.cs new file mode 100644 index 0000000000..d84d385d64 --- /dev/null +++ b/Assets/VRM10/Editor/Components/Expression/ExpressionSlider.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace UniVRM10 +{ + public class ExpressionSlider + { + Dictionary m_expressionKeys; + ExpressionKey m_key; + + public ExpressionSlider(Dictionary expressionKeys, ExpressionKey key) + { + m_expressionKeys = expressionKeys; + m_key = key; + } + + public KeyValuePair Slider() + { + var oldValue = m_expressionKeys[m_key]; + var enable = GUI.enabled; + GUI.enabled = Application.isPlaying; + var newValue = EditorGUILayout.Slider(m_key.ToString(), oldValue, 0, 1.0f); + GUI.enabled = enable; + return new KeyValuePair(m_key, newValue); + } + } +} diff --git a/Assets/VRM10/Runtime/Components/SpringBone/VRM10SpringBone.cs.meta b/Assets/VRM10/Editor/Components/Expression/ExpressionSlider.cs.meta similarity index 83% rename from Assets/VRM10/Runtime/Components/SpringBone/VRM10SpringBone.cs.meta rename to Assets/VRM10/Editor/Components/Expression/ExpressionSlider.cs.meta index 3e62418a8e..878fc07fd9 100644 --- a/Assets/VRM10/Runtime/Components/SpringBone/VRM10SpringBone.cs.meta +++ b/Assets/VRM10/Editor/Components/Expression/ExpressionSlider.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 1f440a6e91549e542bda6d6d741b1409 +guid: d1d6be45aa67f5445884833ae12546bb MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/VRM10/Editor/Components/Expression/VRM10ControllerEditorExpression.cs b/Assets/VRM10/Editor/Components/Expression/VRM10ControllerEditorExpression.cs new file mode 100644 index 0000000000..4cd6b6b6c5 --- /dev/null +++ b/Assets/VRM10/Editor/Components/Expression/VRM10ControllerEditorExpression.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace UniVRM10 +{ + // + // Expression 向けの Inspector + // + // Runtime に Expression 操作用の Slider を表示する + // + class VRM10ControllerEditorExpression + { + VRM10Controller m_target; + Dictionary m_expressionKeyWeights = new Dictionary(); + List m_sliders; + + public VRM10ControllerEditorExpression(VRM10Controller target) + { + m_target = target; + + m_expressionKeyWeights = m_target.Expression.Clips.ToDictionary(x => ExpressionKey.CreateFromClip(x), x => 0.0f); + m_sliders = m_target.Expression.Clips + .Where(x => x != null) + .Select(x => new ExpressionSlider(m_expressionKeyWeights, ExpressionKey.CreateFromClip(x))) + .ToList() + ; + } + + public void OnGUI() + { + EditorGUILayout.Space(); + + if (!Application.isPlaying) + { + EditorGUILayout.HelpBox("Enable when playing", MessageType.Info); + } + + if (m_sliders != null) + { + EditorGUILayout.Space(); + EditorGUILayout.LabelField("Expression Weights", EditorStyles.boldLabel); + + var sliders = m_sliders.Select(x => x.Slider()); + foreach (var slider in sliders) + { + m_expressionKeyWeights[slider.Key] = slider.Value; + } + m_target.Expression.SetWeights(m_expressionKeyWeights); + } + + EditorGUILayout.Space(); + EditorGUILayout.LabelField("Override rates", EditorStyles.boldLabel); + EditorGUI.BeginDisabledGroup(true); + { + EditorGUILayout.Slider("Blink override rate", m_target.Expression.BlinkOverrideRate, 0f, 1f); + EditorGUILayout.Slider("LookAt override rate", m_target.Expression.LookAtOverrideRate, 0f, 1f); + EditorGUILayout.Slider("Mouth override rate", m_target.Expression.MouthOverrideRate, 0f, 1f); + } + EditorGUI.EndDisabledGroup(); + } + } +} diff --git a/Assets/VRM10/Editor/Components/SpringBone/VRM10SelectorWindow.cs.meta b/Assets/VRM10/Editor/Components/Expression/VRM10ControllerEditorExpression.cs.meta similarity index 83% rename from Assets/VRM10/Editor/Components/SpringBone/VRM10SelectorWindow.cs.meta rename to Assets/VRM10/Editor/Components/Expression/VRM10ControllerEditorExpression.cs.meta index 581179e2bd..0871358fcf 100644 --- a/Assets/VRM10/Editor/Components/SpringBone/VRM10SelectorWindow.cs.meta +++ b/Assets/VRM10/Editor/Components/Expression/VRM10ControllerEditorExpression.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 96fdd3a9c7c5ca44b977ef564a2cf15b +guid: 3387f1e8cba522b44a78609529674819 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/VRM10/Editor/Components/LookAt.meta b/Assets/VRM10/Editor/Components/LookAt.meta new file mode 100644 index 0000000000..a9e2d423a3 --- /dev/null +++ b/Assets/VRM10/Editor/Components/LookAt.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8823eaceb68051c43be15ea92028d412 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/Components/LookAt/LookAtEditor.cs b/Assets/VRM10/Editor/Components/LookAt/LookAtEditor.cs new file mode 100644 index 0000000000..588a99d740 --- /dev/null +++ b/Assets/VRM10/Editor/Components/LookAt/LookAtEditor.cs @@ -0,0 +1,62 @@ +using UnityEditor; +using UnityEngine; + +namespace UniVRM10 +{ + public static class LookAtEditor + { + public static void Draw2D(VRM10Controller target) + { + + } + + public static void Draw3D(VRM10Controller target) + { + if(target==null) + { + return; + } + OnSceneGUIOffset(target); + if (!Application.isPlaying) + { + // offset + var p = target.LookAt.OffsetFromHead; + Handles.Label(target.Head.position, $"fromHead: [{p.x:0.00}, {p.y:0.00}, {p.z:0.00}]"); + } + else + { + target.LookAt.OnSceneGUILookAt(target.Head); + } + } + + static void OnSceneGUIOffset(VRM10Controller m_target) + { + if (!m_target.LookAt.DrawGizmo) + { + return; + } + + var head = m_target.Head; + if (head == null) + { + return; + } + + EditorGUI.BeginChangeCheck(); + + var worldOffset = head.localToWorldMatrix.MultiplyPoint(m_target.LookAt.OffsetFromHead); + worldOffset = Handles.PositionHandle(worldOffset, head.rotation); + + Handles.DrawDottedLine(head.position, worldOffset, 5); + Handles.SphereHandleCap(0, head.position, Quaternion.identity, 0.02f, Event.current.type); + Handles.SphereHandleCap(0, worldOffset, Quaternion.identity, 0.02f, Event.current.type); + + if (EditorGUI.EndChangeCheck()) + { + Undo.RecordObject(m_target, "Changed FirstPerson"); + + m_target.LookAt.OffsetFromHead = head.worldToLocalMatrix.MultiplyPoint(worldOffset); + } + } + } +} diff --git a/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneColliderGroupEditor.cs.meta b/Assets/VRM10/Editor/Components/LookAt/LookAtEditor.cs.meta similarity index 83% rename from Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneColliderGroupEditor.cs.meta rename to Assets/VRM10/Editor/Components/LookAt/LookAtEditor.cs.meta index 0bd7bfe1b3..06c907abda 100644 --- a/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneColliderGroupEditor.cs.meta +++ b/Assets/VRM10/Editor/Components/LookAt/LookAtEditor.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 280a977934f9b9b449ac58bc0f016959 +guid: 0f9ee1d04c43d8048a51e032b19a8ebd MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/VRM10/Editor/Components/PropGui.cs b/Assets/VRM10/Editor/Components/PropGui.cs new file mode 100644 index 0000000000..9d833c711c --- /dev/null +++ b/Assets/VRM10/Editor/Components/PropGui.cs @@ -0,0 +1,41 @@ +using UnityEditor; + +namespace UniVRM10 +{ + /// + /// 指定した SerializedProperty を起点に再帰的に SerializedProperty を表示する。 + /// + class PropGui + { + SerializedProperty m_root; + + public PropGui(SerializedProperty property) + { + m_root = property; + } + + // public static PropGui FromSerializedObject(SerializedObject serializedObject, string name) + // { + // var prop = serializedObject.FindProperty(name); + // return new PropGui(prop); + // } + + public void RecursiveProperty() + { + var depth = m_root.depth; + var iterator = m_root.Copy(); + for (var enterChildren = true; iterator.NextVisible(enterChildren); enterChildren = false) + { + if (iterator.depth < depth) + return; + + depth = iterator.depth; + + using (new EditorGUI.DisabledScope("m_Script" == iterator.propertyPath)) + { + EditorGUILayout.PropertyField(iterator, true); + } + } + } + } +} diff --git a/Assets/VRM10/Editor/Components/PropGui.cs.meta b/Assets/VRM10/Editor/Components/PropGui.cs.meta new file mode 100644 index 0000000000..4fa08752a1 --- /dev/null +++ b/Assets/VRM10/Editor/Components/PropGui.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2a55f4351fdc33f4fadbaa02abc5ffdb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/Components/ScrollView.cs b/Assets/VRM10/Editor/Components/ScrollView.cs new file mode 100644 index 0000000000..0a35ab38d7 --- /dev/null +++ b/Assets/VRM10/Editor/Components/ScrollView.cs @@ -0,0 +1,44 @@ +using System; +using UnityEditor; +using UnityEngine; + +namespace UniVRM10 +{ + public class ScrollView + { + Vector2 m_scrollPosition; + + public void Draw(float height, Action content, Action repaint) + { + m_scrollPosition = EditorGUILayout.BeginScrollView(m_scrollPosition); + + // mouse wheel scroll part 1 + var isScroll = Event.current.isScrollWheel; + if (isScroll) + { + m_scrollPosition += Event.current.delta * EditorGUIUtility.singleLineHeight; + if (m_scrollPosition.y < 0) + { + m_scrollPosition = Vector2.zero; + } + } + + content(); + + // mouse wheel scroll part 2 + var bottom = EditorGUILayout.GetControlRect(); + if (isScroll) + { + var maxScroll = bottom.y - (height - EditorGUIUtility.singleLineHeight * 2); + // Debug.Log($"{bottom.y}: {this.position.size.y}: {maxScroll}"); + if (m_scrollPosition.y > maxScroll) + { + m_scrollPosition = new Vector2(0, maxScroll); + } + repaint(); + } + + EditorGUILayout.EndScrollView(); + } + } +} diff --git a/Assets/VRM10/Editor/Components/ScrollView.cs.meta b/Assets/VRM10/Editor/Components/ScrollView.cs.meta new file mode 100644 index 0000000000..4e62944adc --- /dev/null +++ b/Assets/VRM10/Editor/Components/ScrollView.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d8a1e5efc02576543b0a7f051bde4eca +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/Components/SpringBone/SelectedGUI.cs b/Assets/VRM10/Editor/Components/SpringBone/SelectedGUI.cs new file mode 100644 index 0000000000..720185c772 --- /dev/null +++ b/Assets/VRM10/Editor/Components/SpringBone/SelectedGUI.cs @@ -0,0 +1,260 @@ +using System.Linq; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; + +namespace UniVRM10 +{ + abstract class SelectedGUIBase + { + protected SerializedObject _so { get; private set; } + protected int _index { get; private set; } + + protected SelectedGUIBase(SerializedObject so, int i) + { + _so = so; + _index = i; + } + + public SerializedProperty Property { get; protected set; } + public abstract void Draw2D(Rect r); + public abstract void Draw3D(); + + /// + /// 領域を1行と残りに分割する + /// + /// + /// + public static (Rect line, Rect remain) LayoutLine(Rect rect) + { + return ( + new Rect( + rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight + ), + new Rect( + rect.x, rect.y + EditorGUIUtility.singleLineHeight, rect.width, rect.height - EditorGUIUtility.singleLineHeight + ) + ); + } + + /// + /// 領域を上下2分割 + /// + /// + /// + /// + public static (Rect layout, Rect remain) LayoutVerticalHalf(Rect r) + { + var half = r.height / 2; + return ( + new Rect( + r.x, r.y, r.width, half + ), + new Rect( + r.x, r.y + half, r.width, r.height + ) + ); + } + } + + class SelectedColliderGroupGUI + { + SerializedObject _so; + int _index; + ReorderableList _colliderGroupList; + + public SelectedColliderGroupGUI(SerializedObject so, int i) + { + var target_prop = so.FindProperty($"SpringBone.ColliderGroups.Array.data[{i}]"); + _index = i; + _so = new SerializedObject(target_prop.objectReferenceValue); + + var prop = _so.FindProperty("Colliders"); + _colliderGroupList = new ReorderableList(_so, prop); + + _colliderGroupList.drawElementCallback = (rect, index, isActive, isFocused) => + { + SerializedProperty element = prop.GetArrayElementAtIndex(index); + rect.height -= 4; + rect.y += 2; + EditorGUI.PropertyField(rect, element); + + if (isFocused) + { + var id = element.objectReferenceValue.GetInstanceID(); + if (id != VRM10SpringBoneCollider.SelectedGuid) + { + VRM10SpringBoneCollider.SelectedGuid = id; + SceneView.RepaintAll(); + EditorUtility.SetDirty(element.objectReferenceValue); + } + } + }; + } + + public void Draw2D(Rect r) + { + Rect layout = default; + (layout, r) = SelectedGUIBase.LayoutLine(r); + EditorGUI.PropertyField(layout, _so.FindProperty("Name")); + + (layout, r) = SelectedGUIBase.LayoutLine(r); + GUI.Label(layout, "colliders"); + _colliderGroupList.DoList(r); + } + + public static void DrawWireCapsule(Vector3 headPos, Vector3 tailPos, float radius) + { + var headToTail = tailPos - headPos; + if (headToTail.sqrMagnitude <= float.Epsilon) + { + Handles.DrawWireDisc(headPos, -SceneView.currentDrawingSceneView.camera.transform.forward, radius); + return; + } + + var forward = headToTail.normalized * radius; + + var xLen = Mathf.Abs(forward.x); + var yLen = Mathf.Abs(forward.y); + var zLen = Mathf.Abs(forward.z); + var rightWorldAxis = (yLen > xLen && yLen > zLen) ? Vector3.right : Vector3.up; + + var up = Vector3.Cross(forward, rightWorldAxis).normalized * radius; + var right = Vector3.Cross(up, forward).normalized * radius; + + const int division = 24; + DrawWireCircle(headPos, up, right, division, division); + DrawWireCircle(headPos, up, -forward, division, division / 2); + DrawWireCircle(headPos, right, -forward, division, division / 2); + + DrawWireCircle(tailPos, up, right, division, division); + DrawWireCircle(tailPos, up, forward, division, division / 2); + DrawWireCircle(tailPos, right, forward, division, division / 2); + + Handles.DrawLine(headPos + right, tailPos + right); + Handles.DrawLine(headPos - right, tailPos - right); + Handles.DrawLine(headPos + up, tailPos + up); + Handles.DrawLine(headPos - up, tailPos - up); + } + + private static void DrawWireCircle(Vector3 centerPos, Vector3 xAxis, Vector3 yAxis, int division, int count) + { + for (var idx = 0; idx < division && idx < count; ++idx) + { + var s = ((idx + 0) % division) / (float)division * Mathf.PI * 2f; + var t = ((idx + 1) % division) / (float)division * Mathf.PI * 2f; + + Gizmos.DrawLine( + centerPos + xAxis * Mathf.Cos(s) + yAxis * Mathf.Sin(s), + centerPos + xAxis * Mathf.Cos(t) + yAxis * Mathf.Sin(t) + ); + } + } + + public void Draw3D() + { + var target = _so.targetObject as VRM10SpringBoneColliderGroup; + + foreach (var c in target.Colliders) + { + Handles.color = c.IsSelected ? Color.red : Color.cyan; + + Matrix4x4 mat = c.transform.localToWorldMatrix; + Handles.matrix = mat * Matrix4x4.Scale(new Vector3( + 1.0f / c.transform.lossyScale.x, + 1.0f / c.transform.lossyScale.y, + 1.0f / c.transform.lossyScale.z + )); + switch (c.ColliderType) + { + case VRM10SpringBoneColliderTypes.Sphere: + Handles.DrawWireDisc(c.Offset, -SceneView.currentDrawingSceneView.camera.transform.forward, c.Radius); + break; + + case VRM10SpringBoneColliderTypes.Capsule: + DrawWireCapsule(c.Offset, c.Tail, c.Radius); + break; + } + + if (c.IsSelected) + { + Handles.color = Color.green; + EditorGUI.BeginChangeCheck(); + Handles.matrix = c.transform.localToWorldMatrix; + var offset = Handles.PositionHandle(c.Offset, Quaternion.identity); + if (EditorGUI.EndChangeCheck()) + { + Undo.RecordObject(c, "VRM10SpringBoneCollider"); + c.Offset = offset; + EditorUtility.SetDirty(_so.targetObject); + } + } + } + } + } + + class SelectedSpringGUI : SelectedGUIBase + { + ReorderableList _springColliderGroupList; + ReorderableList _springJointList; + + public SelectedSpringGUI(VRM10Controller target, SerializedObject so, int i) : base(so, i) + { + Property = so.FindProperty($"SpringBone.Springs.Array.data[{i}]"); + + { + var prop = Property.FindPropertyRelative("ColliderGroups"); + _springColliderGroupList = new ReorderableList(so, prop); + _springColliderGroupList.drawElementCallback = (rect, index, isActive, isFocused) => + { + rect.height -= 4; + rect.y += 2; + + SerializedProperty element = prop.GetArrayElementAtIndex(index); + var elements = target.SpringBone.ColliderGroups; + var element_index = elements.IndexOf(element.objectReferenceValue as VRM10SpringBoneColliderGroup); + var colliderGroups = target.SpringBone.ColliderGroups.Select((x, y) => x.GUIName(y)).ToArray(); + var new_index = EditorGUI.Popup(rect, element_index, colliderGroups); + if (new_index != element_index) + { + element.objectReferenceValue = elements[new_index]; + } + }; + } + + { + var prop = Property.FindPropertyRelative("Joints"); + _springJointList = new ReorderableList(so, prop); + _springJointList.drawElementCallback = (rect, index, isActive, isFocused) => + { + SerializedProperty element = prop.GetArrayElementAtIndex(index); + rect.height -= 4; + rect.y += 2; + EditorGUI.PropertyField(rect, element); + }; + } + } + + public override void Draw2D(Rect r) + { + Rect layout = default; + (layout, r) = LayoutLine(r); + EditorGUI.PropertyField(layout, Property.FindPropertyRelative("Name")); + + var (top, bottom) = LayoutVerticalHalf(r); + + (layout, r) = LayoutLine(top); + GUI.Label(layout, "collider groups"); + _springColliderGroupList.DoList(r); + + (layout, r) = LayoutLine(bottom); + GUI.Label(layout, "joints"); + _springJointList.DoList(r); + } + + public override void Draw3D() + { + + } + } +} \ No newline at end of file diff --git a/Assets/VRM10/Editor/Components/SpringBone/SelectedGUI.cs.meta b/Assets/VRM10/Editor/Components/SpringBone/SelectedGUI.cs.meta new file mode 100644 index 0000000000..8e02b3e70a --- /dev/null +++ b/Assets/VRM10/Editor/Components/SpringBone/SelectedGUI.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3afd3f6a158933546aa5d979acdf7030 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/Components/SpringBone/SpringBoneEditor.cs b/Assets/VRM10/Editor/Components/SpringBone/SpringBoneEditor.cs new file mode 100644 index 0000000000..1d3b754581 --- /dev/null +++ b/Assets/VRM10/Editor/Components/SpringBone/SpringBoneEditor.cs @@ -0,0 +1,54 @@ +using UnityEditor; +using UnityEditor.IMGUI.Controls; +using UnityEngine; + +namespace UniVRM10 +{ + /// + /// TreeView でアクティブな SpringBone, ColliderGroup を管理して、 + /// アクティブな SpringBone と ColliderGroup を SceneHandle で Edit する。 + /// + public static class SpringBoneEditor + { + static SpringBoneTreeView s_treeView; + static SpringBoneTreeView GetTree(VRM10Controller target, SerializedObject so) + { + if (s_treeView == null || s_treeView.Target != target) + { + var state = new TreeViewState(); + s_treeView = new SpringBoneTreeView(state, target, so); + s_treeView.Reload(); + } + return s_treeView; + } + + public static void Disable() + { + s_treeView = null; + } + + /// + /// 2D の GUI 描画 + /// + public static void Draw2D(VRM10Controller target, SerializedObject so) + { + var tree = GetTree(target, so); + if (GUILayout.Button("Reload")) + { + Disable(); + return; + } + + tree.Draw2D(); + } + + /// + /// 3D の Handle 描画 + /// + public static void Draw3D(VRM10Controller target, SerializedObject so) + { + var tree = GetTree(target, so); + tree.Draw3D(); + } + } +} diff --git a/Assets/VRM10/Editor/Components/SpringBone/SpringBoneEditor.cs.meta b/Assets/VRM10/Editor/Components/SpringBone/SpringBoneEditor.cs.meta new file mode 100644 index 0000000000..3e25de41ba --- /dev/null +++ b/Assets/VRM10/Editor/Components/SpringBone/SpringBoneEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 33ca35799b1168949bb4466aae13dfd4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/Components/SpringBone/SpringBoneTreeView.cs b/Assets/VRM10/Editor/Components/SpringBone/SpringBoneTreeView.cs new file mode 100644 index 0000000000..662c614933 --- /dev/null +++ b/Assets/VRM10/Editor/Components/SpringBone/SpringBoneTreeView.cs @@ -0,0 +1,122 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEditor.IMGUI.Controls; +using UnityEditorInternal; +using UnityEngine; + +namespace UniVRM10 +{ + public class SpringBoneTreeView : TreeView + { + public VRM10Controller Target { get; private set; } + SerializedObject _so; + + TreeViewItem _root; + TreeViewItem _colliderGroups; + TreeViewItem _springs; + + int _nextNodeID = 0; + + Dictionary _map = new Dictionary(); + + public SpringBoneTreeView(TreeViewState state, VRM10Controller target, SerializedObject so) : base(state) + { + Target = target; + _so = so; + + _root = new TreeViewItem(_nextNodeID++, -1, "Root"); + var springBone = new TreeViewItem(_nextNodeID++, 0, "SpringBone"); + _root.AddChild(springBone); + + _colliderGroups = new TreeViewItem(_nextNodeID++, 1, "ColliderGroups"); + springBone.AddChild(_colliderGroups); + + _springs = new TreeViewItem(_nextNodeID++, 1, "Springs"); + springBone.AddChild(_springs); + + // load + _map = new Dictionary(); + for (var i = 0; i < target.SpringBone.ColliderGroups.Count; ++i) + { + var colliderGroup = target.SpringBone.ColliderGroups[i]; + var name = colliderGroup.GUIName(i); + var id = _nextNodeID++; + var item = new TreeViewItem(id, 2, name); + _map.Add(id, colliderGroup); + _colliderGroups.AddChild(item); + } + + for (var i = 0; i < target.SpringBone.Springs.Count; ++i) + { + var spring = target.SpringBone.Springs[i]; + var name = spring.GUIName(i); + var id = _nextNodeID++; + var item = new TreeViewItem(id, 2, name); + _map.Add(id, spring); + _springs.AddChild(item); + } + } + + protected override TreeViewItem BuildRoot() + { + return _root; + } + + object _selected; + + protected override void SelectionChanged(IList selectedIds) + { + _selected = null; + if (selectedIds.Count > 0 && _map.TryGetValue(selectedIds[0], out object value)) + { + if (value is VRM10SpringBoneColliderGroup colliderGroup) + { + var i = Target.SpringBone.ColliderGroups.IndexOf(colliderGroup); + _selected = new SelectedColliderGroupGUI(_so, i); + } + else if (value is VRM10ControllerSpringBone.Spring spring) + { + var i = Target.SpringBone.Springs.IndexOf(spring); + _selected = new SelectedSpringGUI(Target, _so, i); + } + } + } + + const int WINDOW_HEIGHT = 500; + const int TREE_WIDTH = 160; + + // | + // left | right + // | + public void Draw2D() + { + var r = GUILayoutUtility.GetRect(GUIContent.none, GUIStyle.none, GUILayout.Height(WINDOW_HEIGHT)); + + // left + OnGUI(new Rect(r.x, r.y, TREE_WIDTH, r.height)); + + // right + if (_selected is SelectedColliderGroupGUI colliderGroup) + { + colliderGroup.Draw2D(new Rect(r.x + TREE_WIDTH, r.y, r.width - TREE_WIDTH, r.height)); + } + else if (_selected is SelectedSpringGUI spring) + { + spring.Draw2D(new Rect(r.x + TREE_WIDTH, r.y, r.width - TREE_WIDTH, r.height)); + } + } + + public void Draw3D() + { + if (_selected is SelectedColliderGroupGUI colliderGroup) + { + colliderGroup.Draw3D(); + } + else if (_selected is SelectedSpringGUI spring) + { + // TODO + } + } + } +} diff --git a/Assets/VRM10/Editor/Components/SpringBone/SpringBoneTreeView.cs.meta b/Assets/VRM10/Editor/Components/SpringBone/SpringBoneTreeView.cs.meta new file mode 100644 index 0000000000..026cd27176 --- /dev/null +++ b/Assets/VRM10/Editor/Components/SpringBone/SpringBoneTreeView.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 735a730511f68da4da8d2cf2dcba722e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/Components/SpringBone/VRM10SelectorWindow.cs b/Assets/VRM10/Editor/Components/SpringBone/VRM10SelectorWindow.cs deleted file mode 100644 index 3b96ce8dff..0000000000 --- a/Assets/VRM10/Editor/Components/SpringBone/VRM10SelectorWindow.cs +++ /dev/null @@ -1,227 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using UnityEditor; -using UnityEngine; - -namespace UniVRM10 -{ - /// - /// ヒエラルキーから VRM10SpringBone と VRM10ColliderGroup を集めてリスト表示する - /// - public class VRM10SelectorWindow : EditorWindow - { - const string MENU_KEY = VRMVersion.MENU + "/VRM selector window"; - const string WINDOW_TITLE = "VRM selector"; - - [MenuItem(MENU_KEY, false, 1)] - private static void ExportFromMenu() - { - var window = (VRM10SelectorWindow)GetWindow(typeof(VRM10SelectorWindow)); - window.titleContent = new GUIContent(WINDOW_TITLE); - window.Show(); - window.Root = Selection.activeTransform; - } - - void OnEnable() - { - // Debug.Log("OnEnable"); - Undo.willFlushUndoRecord += Repaint; - Selection.selectionChanged += Repaint; - } - - void OnDisable() - { - // Debug.Log("OnDisable"); - Selection.selectionChanged -= Repaint; - Undo.willFlushUndoRecord -= Repaint; - } - - Transform m_root; - Transform Root - { - get => m_root; - set - { - if (m_root == value) - { - return; - } - if (value != null && !value.gameObject.scene.IsValid()) - { - // skip prefab - return; - } - m_root = value; - - m_boneMap = null; - m_springs = null; - m_colliderGroups = null; - m_constraints = null; - } - } - - static bool s_foldHumanoidBones = true; - static HumanBodyBones[] s_bones; - public Dictionary m_boneMap; - - static bool s_foldSprings = true; - public VRM10SpringBone[] m_springs; - - static bool s_foldColliders = true; - public VRM10SpringBoneColliderGroup[] m_colliderGroups; - - static bool s_foldConstraints = true; - public VRM10Constraint[] m_constraints; - - void Reload() - { - var backup = Root; - Root = null; - Root = backup; - } - - Vector2 m_scrollPosition; - - private void OnGUI() - { - Root = (Transform)EditorGUILayout.ObjectField("vrm1 root", m_root, typeof(Transform), true); - - if (m_springs != null && m_springs.Length > 0 && m_springs.Any(x => x == null)) - { - Reload(); - } - - m_scrollPosition = EditorGUILayout.BeginScrollView(m_scrollPosition); - - // mouse wheel scroll part 1 - var isScroll = Event.current.isScrollWheel; - if (isScroll) - { - m_scrollPosition += Event.current.delta * EditorGUIUtility.singleLineHeight; - if (m_scrollPosition.y < 0) - { - m_scrollPosition = Vector2.zero; - } - } - - DrawContent(); - - // mouse wheel scroll part 2 - var bottom = EditorGUILayout.GetControlRect(); - if (isScroll) - { - var maxScroll = bottom.y - (this.position.size.y - EditorGUIUtility.singleLineHeight * 2); - // Debug.Log($"{bottom.y}: {this.position.size.y}: {maxScroll}"); - if (m_scrollPosition.y > maxScroll) - { - m_scrollPosition = new Vector2(0, maxScroll); - } - Repaint(); - } - - EditorGUILayout.EndScrollView(); - } - - void DrawContent() - { - if (m_root != null) - { - if (s_bones == null) - { - var values = Enum.GetValues(typeof(HumanBodyBones)); - s_bones = new HumanBodyBones[values.Length - 1]; - int j = 0; - foreach (HumanBodyBones bone in values) - { - if (bone != HumanBodyBones.LastBone) - { - s_bones[j++] = bone; - } - } - } - if (m_boneMap == null) - { - var animator = m_root.GetComponent(); - if (animator != null) - { - m_boneMap = new Dictionary(); - foreach (var bone in s_bones) - { - var t = animator.GetBoneTransform(bone); - if (t != null) - { - m_boneMap.Add(bone, t); - } - } - } - } - if (m_springs == null) - { - m_springs = m_root.GetComponentsInChildren(); - } - if (m_colliderGroups == null) - { - m_colliderGroups = m_root.GetComponentsInChildren(); - } - if (m_constraints == null) - { - m_constraints = m_root.GetComponentsInChildren(); - } - } - - GUI.enabled = false; - s_foldHumanoidBones = EditorGUILayout.Foldout(s_foldHumanoidBones, "humanoid bones"); - if (s_foldHumanoidBones) - { - if (m_boneMap != null) - { - foreach (var bone in s_bones) - { - if (m_boneMap.TryGetValue(bone, out Transform t)) - { - - } - EditorGUILayout.ObjectField(bone.ToString(), t, typeof(Transform), true); - } - } - } - - s_foldSprings = EditorGUILayout.Foldout(s_foldSprings, "springs"); - if (s_foldSprings) - { - if (m_springs != null) - { - foreach (var s in m_springs) - { - EditorGUILayout.ObjectField(s, typeof(VRM10SpringBone), true); - } - } - } - - s_foldColliders = EditorGUILayout.Foldout(s_foldColliders, "colliders"); - if (s_foldColliders) - { - if (m_colliderGroups != null) - { - foreach (var c in m_colliderGroups) - { - EditorGUILayout.ObjectField(c, typeof(VRM10SpringBoneColliderGroup), true); - } - } - } - - s_foldConstraints = EditorGUILayout.Foldout(s_foldConstraints, "constraints"); - if (s_foldConstraints) - { - if (m_constraints != null) - { - foreach (var c in m_constraints) - { - EditorGUILayout.ObjectField(c, typeof(VRM10Constraint), true); - } - } - } - } - } -} diff --git a/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneColliderEditor.cs b/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneColliderEditor.cs new file mode 100644 index 0000000000..b8e064bc06 --- /dev/null +++ b/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneColliderEditor.cs @@ -0,0 +1,24 @@ +using UnityEditor; +using UnityEngine; + +namespace UniVRM10 +{ + [CustomEditor(typeof(VRM10SpringBoneCollider))] + class VRM10SpringBoneColliderEditor : Editor + { + VRM10SpringBoneCollider _target; + void OnEnable() + { + _target = target as VRM10SpringBoneCollider; + } + + public override void OnInspectorGUI() + { + if (_target.GetInstanceID() == VRM10SpringBoneCollider.SelectedGuid) + { + GUI.backgroundColor = Color.red; + } + base.OnInspectorGUI(); + } + } +} diff --git a/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneColliderEditor.cs.meta b/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneColliderEditor.cs.meta new file mode 100644 index 0000000000..42024cfa0d --- /dev/null +++ b/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneColliderEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b1b992b7266d6d8429b8aeac90efa6a5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneColliderGroupEditor.cs b/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneColliderGroupEditor.cs deleted file mode 100644 index a10587da9c..0000000000 --- a/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneColliderGroupEditor.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System.Linq; -using UnityEditor; -using UnityEngine; - - -namespace UniVRM10 -{ - [CustomEditor(typeof(VRM10SpringBoneColliderGroup))] - public class VRM10SpringBoneColliderGroupEditor : Editor - { - VRM10SpringBoneColliderGroup m_target; - - private void OnEnable() - { - m_target = (VRM10SpringBoneColliderGroup)target; - } - - private void OnSceneGUI() - { - Undo.RecordObject(m_target, "VRMSpringBoneColliderGroupEditor"); - - Handles.matrix = m_target.transform.localToWorldMatrix; - Gizmos.color = Color.green; - - bool changed = false; - - foreach (var x in m_target.Colliders) - { - var offset = Handles.PositionHandle(x.Offset, Quaternion.identity); - if (offset != x.Offset) - { - changed = true; - x.Offset = offset; - } - } - - if (changed) - { - EditorUtility.SetDirty(m_target); - } - } - - [MenuItem("CONTEXT/VRM10SpringBoneColliderGroup/X Mirror")] - private static void InvertOffsetX(MenuCommand command) - { - var target = command.context as VRM10SpringBoneColliderGroup; - if (target == null) return; - - Undo.RecordObject(target, "X Mirror"); - - foreach (var sphereCollider in target.Colliders) - { - var offset = sphereCollider.Offset; - offset.x *= -1f; - sphereCollider.Offset = offset; - } - } - - [MenuItem("CONTEXT/VRM10SpringBoneColliderGroup/Sort Colliders by Radius")] - private static void SortByRadius(MenuCommand command) - { - var target = command.context as VRM10SpringBoneColliderGroup; - if (target == null) return; - - Undo.RecordObject(target, "Sort Colliders by Radius"); - - target.Colliders = target.Colliders.OrderBy(x => -x.Radius); - } - - [MenuItem("CONTEXT/VRM10SpringBoneColliderGroup/Sort Colliders by Offset Y")] - private static void SortByOffsetY(MenuCommand command) - { - var target = command.context as VRM10SpringBoneColliderGroup; - if (target == null) return; - - Undo.RecordObject(target, "Sort Colliders by Offset Y"); - - target.Colliders = target.Colliders.OrderBy(x => -x.Offset.y); - } - } -} diff --git a/Assets/VRM10/Editor/Components/VRM10ControllerEditor.cs b/Assets/VRM10/Editor/Components/VRM10ControllerEditor.cs index 4147e3b84b..739bb52c0c 100644 --- a/Assets/VRM10/Editor/Components/VRM10ControllerEditor.cs +++ b/Assets/VRM10/Editor/Components/VRM10ControllerEditor.cs @@ -1,61 +1,60 @@ -using System.Collections.Generic; -using UnityEditor; -using UnityEngine; -using System.Linq; - +using UnityEditor; namespace UniVRM10 { + /// + /// VRM10Controller CustomEditor + /// + /// * Controller + /// * Meta + /// * Expression + /// * LookAt + /// * ForstPerson + /// + /// [CustomEditor(typeof(VRM10Controller))] public class VRM10ControllerEditor : Editor { VRM10Controller m_target; - SkinnedMeshRenderer[] m_renderers; - Dictionary m_expressionKeyWeights = new Dictionary(); - public class ExpressionSlider + enum Tabs { - Dictionary m_expressionKeys; - ExpressionKey m_key; + Controller, + Meta, + Expression, + LookAt, + SpringBone, + FirstPerson, + } + Tabs _tab = Tabs.Meta; - public ExpressionSlider(Dictionary expressionKeys, ExpressionKey key) - { - m_expressionKeys = expressionKeys; - m_key = key; - } + // for ScriptedObject + Editor m_metaEditor; - public KeyValuePair Slider() - { - var oldValue = m_expressionKeys[m_key]; - var enable = GUI.enabled; - GUI.enabled = Application.isPlaying; - var newValue = EditorGUILayout.Slider(m_key.ToString(), oldValue, 0, 1.0f); - GUI.enabled = enable; - return new KeyValuePair(m_key, newValue); - } - } - List m_sliders; + // expression + VRM10ControllerEditorExpression m_expression; + + // for SerializedProperty + PropGui m_controller; + PropGui m_meta; + PropGui m_lookAt; + PropGui m_springBone; + PropGui m_firstPerson; + PropGui m_asset; void OnEnable() { m_target = (VRM10Controller)target; - - m_expressionKeyWeights = m_target.Expression.Clips.ToDictionary(x => ExpressionKey.CreateFromClip(x), x => 0.0f); - m_sliders = m_target.Expression.Clips - .Where(x => x != null) - .Select(x => new ExpressionSlider(m_expressionKeyWeights, ExpressionKey.CreateFromClip(x))) - .ToList() - ; - + m_expression = new VRM10ControllerEditorExpression(m_target); if (m_target?.Meta.Meta != null) { m_metaEditor = Editor.CreateEditor(m_target.Meta.Meta); } - m_controller = PropGui.FromObject(serializedObject, nameof(m_target.Controller)); - m_meta = PropGui.FromObject(serializedObject, nameof(m_target.Meta)); - m_expression = PropGui.FromObject(serializedObject, nameof(m_target.Expression)); - m_lookAt = PropGui.FromObject(serializedObject, nameof(m_target.LookAt)); - m_firstPerson = PropGui.FromObject(serializedObject, nameof(m_target.FirstPerson)); + m_controller = new PropGui(serializedObject.FindProperty(nameof(m_target.Controller))); + m_meta = new PropGui(serializedObject.FindProperty(nameof(m_target.Meta))); + m_lookAt = new PropGui(serializedObject.FindProperty(nameof(m_target.LookAt))); + m_springBone = new PropGui(serializedObject.FindProperty(nameof(m_target.SpringBone))); + m_firstPerson = new PropGui(serializedObject.FindProperty(nameof(m_target.FirstPerson))); } void OnDisable() @@ -67,75 +66,22 @@ void OnDisable() } } - enum Tabs - { - Controller, - Meta, - Expression, - LookAt, - FirstPerson, - } - Tabs _tab = Tabs.Meta; - - Editor m_metaEditor; - - class PropGui - { - SerializedProperty m_prop; - - PropGui(SerializedProperty property) - { - m_prop = property; - } - - public static PropGui FromObject(SerializedObject serializedObject, string name) - { - var prop = serializedObject.FindProperty(name); - return new PropGui(prop); - } - - public void RecursiveProperty() - { - var depth = m_prop.depth; - var iterator = m_prop.Copy(); - for (var enterChildren = true; iterator.NextVisible(enterChildren); enterChildren = false) - { - if (iterator.depth < depth) - return; - - depth = iterator.depth; - - using (new EditorGUI.DisabledScope("m_Script" == iterator.propertyPath)) - EditorGUILayout.PropertyField(iterator, true); - } - } - } - - PropGui m_controller; - PropGui m_meta; - PropGui m_expression; - PropGui m_lookAt; - PropGui m_firstPerson; - PropGui m_asset; - public override void OnInspectorGUI() { + // select sub editor + using (new EditorGUI.DisabledGroupScope(false)) { - var backup = GUI.enabled; - GUI.enabled = true; - _tab = (Tabs)EditorGUILayout.EnumPopup("Select GUI", _tab); EditorGUILayout.Separator(); - - GUI.enabled = backup; } - serializedObject.Update(); - // Setup runtime function. - m_target.Setup(); + if (UnityEngine.Application.isPlaying) + { + m_target.Setup(); + } - // base.OnInspectorGUI(); + serializedObject.Update(); switch (_tab) { case Tabs.Meta: @@ -148,99 +94,22 @@ public override void OnInspectorGUI() break; case Tabs.Expression: - // m_expression.RecursiveProperty(); - ExpressionGUI(); + m_expression.OnGUI(); break; case Tabs.LookAt: m_lookAt.RecursiveProperty(); break; + case Tabs.SpringBone: + m_springBone.RecursiveProperty(); + break; + case Tabs.FirstPerson: m_firstPerson.RecursiveProperty(); break; } - serializedObject.ApplyModifiedProperties(); } - - void ExpressionGUI() - { - EditorGUILayout.Space(); - - if (!Application.isPlaying) - { - EditorGUILayout.HelpBox("Enable when playing", MessageType.Info); - } - - if (m_sliders != null) - { - EditorGUILayout.Space(); - EditorGUILayout.LabelField("Expression Weights", EditorStyles.boldLabel); - - var sliders = m_sliders.Select(x => x.Slider()); - foreach (var slider in sliders) - { - m_expressionKeyWeights[slider.Key] = slider.Value; - } - m_target.Expression.SetWeights(m_expressionKeyWeights); - } - - EditorGUILayout.Space(); - EditorGUILayout.LabelField("Override rates", EditorStyles.boldLabel); - EditorGUI.BeginDisabledGroup(true); - { - EditorGUILayout.Slider("Blink override rate", m_target.Expression.BlinkOverrideRate, 0f, 1f); - EditorGUILayout.Slider("LookAt override rate", m_target.Expression.LookAtOverrideRate, 0f, 1f); - EditorGUILayout.Slider("Mouth override rate", m_target.Expression.MouthOverrideRate, 0f, 1f); - } - EditorGUI.EndDisabledGroup(); - } - - void OnSceneGUI() - { - OnSceneGUIOffset(); - if (!Application.isPlaying) - { - // offset - var p = m_target.LookAt.OffsetFromHead; - Handles.Label(m_target.Head.position, $"fromHead: [{p.x:0.00}, {p.y:0.00}, {p.z:0.00}]"); - } - else - { - m_target.LookAt.OnSceneGUILookAt(m_target.Head); - } - } - - void OnSceneGUIOffset() - { - var component = target as VRM10Controller; - if (!component.LookAt.DrawGizmo) - { - return; - } - - var head = component.Head; - if (head == null) - { - return; - } - - EditorGUI.BeginChangeCheck(); - - var worldOffset = head.localToWorldMatrix.MultiplyPoint(component.LookAt.OffsetFromHead); - worldOffset = Handles.PositionHandle(worldOffset, head.rotation); - - Handles.DrawDottedLine(head.position, worldOffset, 5); - Handles.SphereHandleCap(0, head.position, Quaternion.identity, 0.01f, Event.current.type); - Handles.SphereHandleCap(0, worldOffset, Quaternion.identity, 0.01f, Event.current.type); - - if (EditorGUI.EndChangeCheck()) - { - Undo.RecordObject(component, "Changed FirstPerson"); - - component.LookAt.OffsetFromHead = head.worldToLocalMatrix.MultiplyPoint(worldOffset); - } - } } } diff --git a/Assets/VRM10/Editor/Components/VRM10Window.cs b/Assets/VRM10/Editor/Components/VRM10Window.cs new file mode 100644 index 0000000000..8c00709391 --- /dev/null +++ b/Assets/VRM10/Editor/Components/VRM10Window.cs @@ -0,0 +1,188 @@ +using System; +using UnityEditor; +using UnityEngine; + +namespace UniVRM10 +{ + /// + /// VRM10操作 Window + /// + public class VRM10Window : EditorWindow + { + const string MENU_KEY = VRMVersion.MENU + "/VRM1 Window"; + const string WINDOW_TITLE = "VRM1 Window"; + + [MenuItem(MENU_KEY, false, 1)] + private static void ExportFromMenu() + { + var window = (VRM10Window)GetWindow(typeof(VRM10Window)); + window.titleContent = new GUIContent(WINDOW_TITLE); + window.Show(); + window.Root = UnityEditor.Selection.activeTransform.GetComponent(); + } + + void OnEnable() + { + // Debug.Log("OnEnable"); + Undo.willFlushUndoRecord += Repaint; + UnityEditor.Selection.selectionChanged += Repaint; + + SceneView.onSceneGUIDelegate += OnSceneGUI; + } + + void OnDisable() + { + SpringBoneEditor.Disable(); + + SceneView.onSceneGUIDelegate -= OnSceneGUI; + // Debug.Log("OnDisable"); + UnityEditor.Selection.selectionChanged -= Repaint; + Undo.willFlushUndoRecord -= Repaint; + + Tools.hidden = false; + } + + SerializedObject m_so; + VRM10Controller m_root; + VRM10Controller Root + { + get => m_root; + set + { + if (m_root == value) + { + return; + } + if (value != null && !value.gameObject.scene.IsValid()) + { + // skip prefab + return; + } + m_root = value; + m_so = m_root != null ? new SerializedObject(m_root) : null; + + m_constraints = null; + } + } + + public VRM10Constraint[] m_constraints; + + ScrollView m_scrollView = new ScrollView(); + + enum VRMSceneUI + { + Constraints, + LookAt, + SpringBone, + } + static VRMSceneUI s_ui = default; + static string[] s_selection; + static string[] Selection + { + get + { + if (s_selection == null) + { + s_selection = Enum.GetNames(typeof(VRMSceneUI)); + } + return s_selection; + } + } + + /// + /// public entry point + /// + /// + void OnSceneGUI(SceneView sceneView) + { + switch (s_ui) + { + case VRMSceneUI.Constraints: + Tools.hidden = false; + break; + + case VRMSceneUI.LookAt: + Tools.hidden = true; + LookAtEditor.Draw3D(Root); + break; + + case VRMSceneUI.SpringBone: + Tools.hidden = true; + SpringBoneEditor.Draw3D(Root, m_so); + break; + + default: + throw new NotImplementedException(); + } + } + + // + private void OnGUI() + { + Root = (VRM10Controller)EditorGUILayout.ObjectField("vrm1", Root, typeof(VRM10Controller), true); + if (Root == null) + { + return; + } + + var ui = (VRMSceneUI)GUILayout.SelectionGrid((int)s_ui, Selection, 3); + if (s_ui != ui) + { + s_ui = ui; + SceneView.RepaintAll(); + } + + if (m_so == null) + { + m_so = new SerializedObject(Root); + } + if (m_so == null) + { + return; + } + + m_so.Update(); + switch (s_ui) + { + case VRMSceneUI.Constraints: + m_scrollView.Draw(this.position.y, DrawConstraints, Repaint); + break; + + case VRMSceneUI.LookAt: + LookAtEditor.Draw2D(Root); + break; + + case VRMSceneUI.SpringBone: + SpringBoneEditor.Draw2D(Root, m_so); + break; + + default: + throw new NotImplementedException(); + } + + m_so.ApplyModifiedProperties(); + } + + void DrawConstraints() + { + if (Root != null) + { + if (m_constraints == null) + { + m_constraints = Root.GetComponentsInChildren(); + } + } + + using (new EditorGUI.DisabledGroupScope(true)) + { + if (m_constraints != null) + { + foreach (var c in m_constraints) + { + EditorGUILayout.ObjectField(c, typeof(VRM10Constraint), true); + } + } + } + } + } +} diff --git a/Assets/VRM10/Editor/Components/VRM10Window.cs.meta b/Assets/VRM10/Editor/Components/VRM10Window.cs.meta new file mode 100644 index 0000000000..1f7d77ff24 --- /dev/null +++ b/Assets/VRM10/Editor/Components/VRM10Window.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d10db1ad6a9273e4698465a666033569 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/SpringBone/VRM10SpringBone.cs b/Assets/VRM10/Runtime/Components/SpringBone/VRM10SpringBone.cs deleted file mode 100644 index 902196c9b2..0000000000 --- a/Assets/VRM10/Runtime/Components/SpringBone/VRM10SpringBone.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using UnityEngine; - -namespace UniVRM10 -{ - /// - /// The base algorithm is http://rocketjump.skr.jp/unity3d/109/ of @ricopin416 - /// DefaultExecutionOrder(11000) means calculate springbone after FinalIK( VRIK ) - /// - /// Setup の Editor の都合から、VRM10SpringBone は MonoBehaviour にする。 - /// Data置き場。Updateは使わない。 - /// - /// Springの根元のTranform (Joints.First()) に AddComponent して使う。 - /// - /// - [DisallowMultipleComponent] - [Serializable] - public class VRM10SpringBone : MonoBehaviour - { - [SerializeField] - Color m_gizmoColor = Color.yellow; - - [SerializeField] - public string Comment; - - [SerializeField] - public List Joints = new List(); - - [SerializeField] - public List ColliderGroups = new List(); - - [ContextMenu("Reset bones")] - public void ResetJoints() - { - foreach (var joint in Joints) - { - if (joint != null) - { - joint.Transform.localRotation = Quaternion.identity; - } - } - } - - Transform m_center; - - List m_colliderList = new List(); - public void Process(Transform center) - { - m_center = center; - if (Joints == null) - { - return; - } - - // gather colliders - m_colliderList.Clear(); - if (ColliderGroups != null) - { - foreach (var group in ColliderGroups.Where(x => x != null)) - { - foreach (var collider in group.Colliders) - { - switch (collider.ColliderType) - { - case VRM10SpringBoneColliderTypes.Sphere: - m_colliderList.Add(new SpringBoneLogic.InternalCollider - { - ColliderTypes = VRM10SpringBoneColliderTypes.Sphere, - WorldPosition = group.transform.TransformPoint(collider.Offset), - Radius = collider.Radius, - - }); - break; - - case VRM10SpringBoneColliderTypes.Capsule: - m_colliderList.Add(new SpringBoneLogic.InternalCollider - { - ColliderTypes = VRM10SpringBoneColliderTypes.Capsule, - WorldPosition = group.transform.TransformPoint(collider.Offset), - Radius = collider.Radius, - WorldTail = group.transform.TransformPoint(collider.Tail) - }); - break; - } - } - } - } - - { - // udpate joints - VRM10SpringJoint lastJoint = Joints.FirstOrDefault(x => x != null); - foreach (var joint in Joints.Where(x => x != null).Skip(1)) - { - lastJoint.Update(center, Time.deltaTime, m_colliderList, joint); - lastJoint = joint; - } - lastJoint.Update(center, Time.deltaTime, m_colliderList, null); - } - } - - public void OnDrawGizmosSelected() - { - foreach (var joint in Joints) - { - if (joint != null) - { - joint.DrawGizmo(m_center, m_gizmoColor); - } - } - } - } -} diff --git a/Assets/VRM10/Runtime/Components/SpringBone/VRM10SpringBoneCollider.cs b/Assets/VRM10/Runtime/Components/SpringBone/VRM10SpringBoneCollider.cs index 8b8baa86bf..1dfc772d52 100644 --- a/Assets/VRM10/Runtime/Components/SpringBone/VRM10SpringBoneCollider.cs +++ b/Assets/VRM10/Runtime/Components/SpringBone/VRM10SpringBoneCollider.cs @@ -23,73 +23,8 @@ public class VRM10SpringBoneCollider : MonoBehaviour /// bone local position public Vector3 Tail; - public static void DrawWireCapsule(Vector3 headPos, Vector3 tailPos, float radius) - { - var headToTail = tailPos - headPos; - if (headToTail.sqrMagnitude <= float.Epsilon) - { - Gizmos.DrawWireSphere(headPos, radius); - return; - } + public static int SelectedGuid; - var forward = headToTail.normalized * radius; - - var xLen = Mathf.Abs(forward.x); - var yLen = Mathf.Abs(forward.y); - var zLen = Mathf.Abs(forward.z); - var rightWorldAxis = (yLen > xLen && yLen > zLen) ? Vector3.right : Vector3.up; - - var up = Vector3.Cross(forward, rightWorldAxis).normalized * radius; - var right = Vector3.Cross(up, forward).normalized * radius; - - const int division = 24; - DrawWireCircle(headPos, up, right, division, division); - DrawWireCircle(headPos, up, -forward, division, division / 2); - DrawWireCircle(headPos, right, -forward, division, division / 2); - - DrawWireCircle(tailPos, up, right, division, division); - DrawWireCircle(tailPos, up, forward, division, division / 2); - DrawWireCircle(tailPos, right, forward, division, division / 2); - - Gizmos.DrawLine(headPos + right, tailPos + right); - Gizmos.DrawLine(headPos - right, tailPos - right); - Gizmos.DrawLine(headPos + up, tailPos + up); - Gizmos.DrawLine(headPos - up, tailPos - up); - } - - private static void DrawWireCircle(Vector3 centerPos, Vector3 xAxis, Vector3 yAxis, int division, int count) - { - for (var idx = 0; idx < division && idx < count; ++idx) - { - var s = ((idx + 0) % division) / (float)division * Mathf.PI * 2f; - var t = ((idx + 1) % division) / (float)division * Mathf.PI * 2f; - - Gizmos.DrawLine( - centerPos + xAxis * Mathf.Cos(s) + yAxis * Mathf.Sin(s), - centerPos + xAxis * Mathf.Cos(t) + yAxis * Mathf.Sin(t) - ); - } - } - - public void OnDrawGizmosSelected() - { - Gizmos.color = Color.cyan; - Matrix4x4 mat = transform.localToWorldMatrix; - Gizmos.matrix = mat * Matrix4x4.Scale(new Vector3( - 1.0f / transform.lossyScale.x, - 1.0f / transform.lossyScale.y, - 1.0f / transform.lossyScale.z - )); - switch (ColliderType) - { - case VRM10SpringBoneColliderTypes.Sphere: - Gizmos.DrawWireSphere(Offset, Radius); - break; - - case VRM10SpringBoneColliderTypes.Capsule: - DrawWireCapsule(Offset, Tail, Radius); - break; - } - } + public bool IsSelected => GetInstanceID() == SelectedGuid; } } diff --git a/Assets/VRM10/Runtime/Components/SpringBone/VRM10SpringBoneColliderGroup.cs b/Assets/VRM10/Runtime/Components/SpringBone/VRM10SpringBoneColliderGroup.cs index 8ecae79fdc..e1c782085e 100644 --- a/Assets/VRM10/Runtime/Components/SpringBone/VRM10SpringBoneColliderGroup.cs +++ b/Assets/VRM10/Runtime/Components/SpringBone/VRM10SpringBoneColliderGroup.cs @@ -1,54 +1,18 @@ +using System; using System.Collections.Generic; -using System.Linq; using UnityEngine; - namespace UniVRM10 { - /// - /// VRMC_node_collider - /// - [AddComponentMenu("VRM10/VRM10SpringBoneColliderGroup")] + [Serializable] public class VRM10SpringBoneColliderGroup : MonoBehaviour { [SerializeField] - List m_colliders = new List(); - - public IEnumerable Colliders - { - get - { - return m_colliders.Where(x => x != null); - } - set - { - m_colliders = value.ToList(); - OnValidate(); - } - } + public string Name; - public void AddCollider(VRM10SpringBoneCollider collider) - { - if (collider == null) - { - Debug.LogWarning("null collider"); - return; - } + public string GUIName(int i) => $"{i:00}:{Name}"; - if (m_colliders == null) - { - m_colliders = new List(); - } - m_colliders.Add(collider); - } - - void OnValidate() - { - if (m_colliders.Any(x => x == null)) - { - Debug.LogWarning($"{this} remove null"); - m_colliders = m_colliders.Where(x => x != null).ToList(); - } - } + [SerializeField] + public List Colliders = new List(); } -} +} \ No newline at end of file diff --git a/Assets/VRM10/Runtime/Components/SpringBone/VRM10SpringBoneColliderGroup.cs.meta b/Assets/VRM10/Runtime/Components/SpringBone/VRM10SpringBoneColliderGroup.cs.meta index a53d706324..dd14631151 100644 --- a/Assets/VRM10/Runtime/Components/SpringBone/VRM10SpringBoneColliderGroup.cs.meta +++ b/Assets/VRM10/Runtime/Components/SpringBone/VRM10SpringBoneColliderGroup.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 1542d3467a35afa4dbefa2a112058078 +guid: 177ea458e237fee41b0902e3006c744b MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/VRM10/Runtime/Components/SpringBone/VRM10SpringJoint.cs b/Assets/VRM10/Runtime/Components/SpringBone/VRM10SpringJoint.cs index d37cc2c495..d9889c509a 100644 --- a/Assets/VRM10/Runtime/Components/SpringBone/VRM10SpringJoint.cs +++ b/Assets/VRM10/Runtime/Components/SpringBone/VRM10SpringJoint.cs @@ -8,16 +8,8 @@ namespace UniVRM10 { [Serializable] - public class VRM10SpringJoint + public class VRM10SpringJoint : MonoBehaviour { - [SerializeField] - public Transform Transform; - - public VRM10SpringJoint(Transform t) - { - Transform = t; - } - [SerializeField, Range(0, 4), Header("Settings")] public float m_stiffnessForce = 1.0f; @@ -49,21 +41,21 @@ public void DrawGizmo(Transform center, Color color) #if UNITY_EDITOR // Gizmos.matrix = Transform.localToWorldMatrix; Gizmos.color = color; - Gizmos.DrawSphere(Transform.position, m_jointRadius); + Gizmos.DrawSphere(transform.position, m_jointRadius); #endif } } - public void Update(Transform center, float deltaTime, List colliders, VRM10SpringJoint tail) + public void Process(Transform center, float deltaTime, List colliders, VRM10SpringJoint tail) { if (m_logic == null) { // 初期化 if (tail != null) { - var localPosition = tail.Transform.localPosition; - var scale = tail.Transform.lossyScale; - m_logic = new SpringBoneLogic(center, Transform, + var localPosition = tail.transform.localPosition; + var scale = tail.transform.lossyScale; + m_logic = new SpringBoneLogic(center, transform, new Vector3( localPosition.x * scale.x, localPosition.y * scale.y, @@ -73,9 +65,9 @@ public void Update(Transform center, float deltaTime, List(); - } - foreach (var spring in m_springs) - { - spring.Process(Controller.SpringBoneCenter); - } + SpringBone.Process(Controller.SpringBoneCenter); // // gaze control diff --git a/Assets/VRM10/Runtime/Components/VRM10ControllerSpringBone.cs b/Assets/VRM10/Runtime/Components/VRM10ControllerSpringBone.cs new file mode 100644 index 0000000000..7be3c3f9c4 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/VRM10ControllerSpringBone.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace UniVRM10 +{ + /// + /// SpringBone の情報をすべて保持する + /// + /// * SpringBoneCollider + /// * SpringBoneJoint + /// + /// は、個別の MonoBehaviour として設定する + /// + /// + [Serializable] + public sealed class VRM10ControllerSpringBone + { + [SerializeField] + public List ColliderGroups = new List(); + + [Serializable] + public class Spring + { + [SerializeField] + public string Name; + + public string GUIName(int i) => $"{i:00}:{Name}"; + + [SerializeField] + public List ColliderGroups = new List(); + + [SerializeField] + public List Joints = new List(); + + Transform m_center; + List m_colliderList = new List(); + + public Spring(string name) + { + Name = name; + } + + public void Process(Transform center) + { + m_center = center; + if (Joints == null) + { + return; + } + + // gather colliders + m_colliderList.Clear(); + if (ColliderGroups != null) + { + foreach (var group in ColliderGroups.Where(x => x != null)) + { + foreach (var collider in group.Colliders) + { + switch (collider.ColliderType) + { + case VRM10SpringBoneColliderTypes.Sphere: + m_colliderList.Add(new SpringBoneLogic.InternalCollider + { + ColliderTypes = VRM10SpringBoneColliderTypes.Sphere, + WorldPosition = collider.transform.TransformPoint(collider.Offset), + Radius = collider.Radius, + + }); + break; + + case VRM10SpringBoneColliderTypes.Capsule: + m_colliderList.Add(new SpringBoneLogic.InternalCollider + { + ColliderTypes = VRM10SpringBoneColliderTypes.Capsule, + WorldPosition = collider.transform.TransformPoint(collider.Offset), + Radius = collider.Radius, + WorldTail = collider.transform.TransformPoint(collider.Tail) + }); + break; + } + } + } + } + + { + // udpate joints + VRM10SpringJoint lastJoint = Joints.FirstOrDefault(x => x != null); + foreach (var joint in Joints.Where(x => x != null).Skip(1)) + { + lastJoint.Process(center, Time.deltaTime, m_colliderList, joint); + lastJoint = joint; + } + lastJoint.Process(center, Time.deltaTime, m_colliderList, null); + } + } + } + [SerializeField] + public List Springs = new List(); + + public void Process(Transform center) + { + foreach (var spring in Springs) + { + spring.Process(center); + } + } + } +} diff --git a/Assets/VRM10/Runtime/Components/VRM10ControllerSpringBone.cs.meta b/Assets/VRM10/Runtime/Components/VRM10ControllerSpringBone.cs.meta new file mode 100644 index 0000000000..4e72b34cc4 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/VRM10ControllerSpringBone.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 99c0f25b461a78e4099b81021d029663 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/IO/Vrm10Exporter.cs b/Assets/VRM10/Runtime/IO/Vrm10Exporter.cs index 15339fd4ad..10121ce1a4 100644 --- a/Assets/VRM10/Runtime/IO/Vrm10Exporter.cs +++ b/Assets/VRM10/Runtime/IO/Vrm10Exporter.cs @@ -263,7 +263,7 @@ public void Export(GameObject root, Model model, ModelExporter converter, Export ExportLookAt(vrm, vrmController); ExportFirstPerson(vrm, vrmController, model, converter); - vrmSpringBone = ExportSpringBone(vrmController, model, converter); + vrmSpringBone = ExportSpringBone(vrmController.SpringBone, model, converter); ExportConstraints(vrmController, model, converter); } @@ -303,7 +303,7 @@ UniGLTF.Extensions.VRMC_springBone.SpringBoneJoint ExportJoint(VRM10SpringJoint { var joint = new UniGLTF.Extensions.VRMC_springBone.SpringBoneJoint { - Node = getIndexFromTransform(y.Transform), + Node = getIndexFromTransform(y.transform), HitRadius = y.m_jointRadius, DragForce = y.m_dragForce, Stiffness = y.m_stiffnessForce, @@ -313,7 +313,7 @@ UniGLTF.Extensions.VRMC_springBone.SpringBoneJoint ExportJoint(VRM10SpringJoint return joint; } - UniGLTF.Extensions.VRMC_springBone.VRMC_springBone ExportSpringBone(VRM10Controller vrmController, Model model, ModelExporter converter) + UniGLTF.Extensions.VRMC_springBone.VRMC_springBone ExportSpringBone(VRM10ControllerSpringBone src, Model model, ModelExporter converter) { var springBone = new UniGLTF.Extensions.VRMC_springBone.VRMC_springBone { @@ -327,8 +327,7 @@ UniGLTF.Extensions.VRMC_springBone.VRMC_springBone ExportSpringBone(VRM10Control return model.Nodes.IndexOf(node); }; - var colliderGroups = vrmController.GetComponentsInChildren(); - foreach (var x in colliderGroups) + foreach (var x in src.ColliderGroups) { var colliderGroup = new UniGLTF.Extensions.VRMC_springBone.ColliderGroup { @@ -350,13 +349,13 @@ UniGLTF.Extensions.VRMC_springBone.VRMC_springBone ExportSpringBone(VRM10Control } } - foreach (var x in vrmController.GetComponentsInChildren()) + foreach (var x in src.Springs) { var spring = new UniGLTF.Extensions.VRMC_springBone.Spring { - Name = x.Comment, + Name = x.Name, Joints = x.Joints.Select(y => ExportJoint(y, getNodeIndexFromTransform)).ToList(), - ColliderGroups = x.ColliderGroups.Select(y => Array.IndexOf(colliderGroups, y)).ToArray(), + ColliderGroups = x.ColliderGroups.Select(y => src.ColliderGroups.IndexOf(y)).ToArray(), }; springBone.Springs.Add(spring); } diff --git a/Assets/VRM10/Runtime/IO/Vrm10Importer.cs b/Assets/VRM10/Runtime/IO/Vrm10Importer.cs index 51329db121..53413d3fc7 100644 --- a/Assets/VRM10/Runtime/IO/Vrm10Importer.cs +++ b/Assets/VRM10/Runtime/IO/Vrm10Importer.cs @@ -374,18 +374,17 @@ async Task LoadSpringBoneAsync(IAwaitCaller awaitCaller, VRM10Controller control } // colliderGroup - var list = new List(); foreach (var g in gltfVrmSpringBone.ColliderGroups) { var colliderGroup = secondary.gameObject.AddComponent(); - list.Add(colliderGroup); + controller.SpringBone.ColliderGroups.Add(colliderGroup); foreach (var c in g.Colliders) { var node = Nodes[c.Node.Value]; var collider = node.gameObject.AddComponent(); - colliderGroup.AddCollider(collider); + colliderGroup.Colliders.Add(collider); if (c.Shape.Sphere is UniGLTF.Extensions.VRMC_springBone.ColliderShapeSphere sphere) { @@ -413,11 +412,10 @@ async Task LoadSpringBoneAsync(IAwaitCaller awaitCaller, VRM10Controller control { continue; } - var firstJointNode = Nodes[gltfSpring.Joints.First().Node.Value]; - var springBone = firstJointNode.gameObject.AddComponent(); - springBone.Comment = gltfSpring.Name; - springBone.ColliderGroups = gltfSpring.ColliderGroups.Select(x => list[x]).ToList(); + var spring = new VRM10ControllerSpringBone.Spring(gltfSpring.Name); + controller.SpringBone.Springs.Add(spring); + spring.ColliderGroups = gltfSpring.ColliderGroups.Select(x => controller.SpringBone.ColliderGroups[x]).ToList(); // joint foreach (var gltfJoint in gltfSpring.Joints) { @@ -428,14 +426,14 @@ async Task LoadSpringBoneAsync(IAwaitCaller awaitCaller, VRM10Controller control { throw new IndexOutOfRangeException($"{index} > {Nodes.Count}"); } - var joint = new VRM10SpringJoint(Nodes[gltfJoint.Node.Value]); + var joint = Nodes[gltfJoint.Node.Value].gameObject.AddComponent(); joint.m_jointRadius = gltfJoint.HitRadius.Value; joint.m_dragForce = gltfJoint.DragForce.Value; joint.m_gravityDir = Vector3InvertX(gltfJoint.GravityDir); joint.m_gravityPower = gltfJoint.GravityPower.Value; joint.m_stiffnessForce = gltfJoint.Stiffness.Value; // joint.m_exclude = gltfJoint.Exclude.GetValueOrDefault(); - springBone.Joints.Add(joint); + spring.Joints.Add(joint); } } }