mirror of
https://github.com/vrm-c/UniVRM.git
synced 2026-09-12 05:48:47 -05:00
Feature10/spring bone editor (#994)
* SceneView switch * VRM10ControllerEditor を分割 * VRM10/Editor/Components/VRM10ControllerSceneView.cs * LookAtEditor, SpringBoneEditor * VRM10SpringBoneColliderGroup and VRM10SpringBone to VRM10ControllerSpringBone. not MonoBehaviour * ReorderableList * VRM10Window.cs * SpringBoneTreeView * ScrollView * rename * VRM10SpringBoneColliderEditor * SelectedGUIBase * spring name * VRM10SpringBoneCollider gizmo to handle * SelectedGUI * 再度、VRM10SpringBoneColliderGroup を MonoBehaviour へ。SerializedObject の参照の都合
This commit is contained in:
@@ -1,6 +1,4 @@
|
||||
using System.Text;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniVRM10
|
||||
{
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniVRM10
|
||||
{
|
||||
public class ExpressionSlider
|
||||
{
|
||||
Dictionary<ExpressionKey, float> m_expressionKeys;
|
||||
ExpressionKey m_key;
|
||||
|
||||
public ExpressionSlider(Dictionary<ExpressionKey, float> expressionKeys, ExpressionKey key)
|
||||
{
|
||||
m_expressionKeys = expressionKeys;
|
||||
m_key = key;
|
||||
}
|
||||
|
||||
public KeyValuePair<ExpressionKey, float> 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<ExpressionKey, float>(m_key, newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1f440a6e91549e542bda6d6d741b1409
|
||||
guid: d1d6be45aa67f5445884833ae12546bb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -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<ExpressionKey, float> m_expressionKeyWeights = new Dictionary<ExpressionKey, float>();
|
||||
List<ExpressionSlider> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 96fdd3a9c7c5ca44b977ef564a2cf15b
|
||||
guid: 3387f1e8cba522b44a78609529674819
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
8
Assets/VRM10/Editor/Components/LookAt.meta
Normal file
8
Assets/VRM10/Editor/Components/LookAt.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8823eaceb68051c43be15ea92028d412
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
62
Assets/VRM10/Editor/Components/LookAt/LookAtEditor.cs
Normal file
62
Assets/VRM10/Editor/Components/LookAt/LookAtEditor.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 280a977934f9b9b449ac58bc0f016959
|
||||
guid: 0f9ee1d04c43d8048a51e032b19a8ebd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
41
Assets/VRM10/Editor/Components/PropGui.cs
Normal file
41
Assets/VRM10/Editor/Components/PropGui.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using UnityEditor;
|
||||
|
||||
namespace UniVRM10
|
||||
{
|
||||
/// <summary>
|
||||
/// 指定した SerializedProperty を起点に再帰的に SerializedProperty を表示する。
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/VRM10/Editor/Components/PropGui.cs.meta
Normal file
11
Assets/VRM10/Editor/Components/PropGui.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2a55f4351fdc33f4fadbaa02abc5ffdb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
44
Assets/VRM10/Editor/Components/ScrollView.cs
Normal file
44
Assets/VRM10/Editor/Components/ScrollView.cs
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/VRM10/Editor/Components/ScrollView.cs.meta
Normal file
11
Assets/VRM10/Editor/Components/ScrollView.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d8a1e5efc02576543b0a7f051bde4eca
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
260
Assets/VRM10/Editor/Components/SpringBone/SelectedGUI.cs
Normal file
260
Assets/VRM10/Editor/Components/SpringBone/SelectedGUI.cs
Normal file
@@ -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();
|
||||
|
||||
/// <summary>
|
||||
/// 領域を1行と残りに分割する
|
||||
/// </summary>
|
||||
/// <param name="rect"></param>
|
||||
/// <returns></returns>
|
||||
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
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 領域を上下2分割
|
||||
/// </summary>
|
||||
/// <param name="layout"></param>
|
||||
/// <param name="r"></param>
|
||||
/// <returns></returns>
|
||||
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()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3afd3f6a158933546aa5d979acdf7030
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,54 @@
|
||||
using UnityEditor;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniVRM10
|
||||
{
|
||||
/// <summary>
|
||||
/// TreeView でアクティブな SpringBone, ColliderGroup を管理して、
|
||||
/// アクティブな SpringBone と ColliderGroup を SceneHandle で Edit する。
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 2D の GUI 描画
|
||||
/// </summary>
|
||||
public static void Draw2D(VRM10Controller target, SerializedObject so)
|
||||
{
|
||||
var tree = GetTree(target, so);
|
||||
if (GUILayout.Button("Reload"))
|
||||
{
|
||||
Disable();
|
||||
return;
|
||||
}
|
||||
|
||||
tree.Draw2D();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3D の Handle 描画
|
||||
/// </summary>
|
||||
public static void Draw3D(VRM10Controller target, SerializedObject so)
|
||||
{
|
||||
var tree = GetTree(target, so);
|
||||
tree.Draw3D();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 33ca35799b1168949bb4466aae13dfd4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
122
Assets/VRM10/Editor/Components/SpringBone/SpringBoneTreeView.cs
Normal file
122
Assets/VRM10/Editor/Components/SpringBone/SpringBoneTreeView.cs
Normal file
@@ -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<int, object> _map = new Dictionary<int, object>();
|
||||
|
||||
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<int, object>();
|
||||
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<int> 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 735a730511f68da4da8d2cf2dcba722e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,227 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniVRM10
|
||||
{
|
||||
/// <summary>
|
||||
/// ヒエラルキーから VRM10SpringBone と VRM10ColliderGroup を集めてリスト表示する
|
||||
/// </summary>
|
||||
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<HumanBodyBones, Transform> 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<Animator>();
|
||||
if (animator != null)
|
||||
{
|
||||
m_boneMap = new Dictionary<HumanBodyBones, Transform>();
|
||||
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<VRM10SpringBone>();
|
||||
}
|
||||
if (m_colliderGroups == null)
|
||||
{
|
||||
m_colliderGroups = m_root.GetComponentsInChildren<VRM10SpringBoneColliderGroup>();
|
||||
}
|
||||
if (m_constraints == null)
|
||||
{
|
||||
m_constraints = m_root.GetComponentsInChildren<VRM10Constraint>();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b1b992b7266d6d8429b8aeac90efa6a5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +1,60 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using System.Linq;
|
||||
|
||||
using UnityEditor;
|
||||
|
||||
namespace UniVRM10
|
||||
{
|
||||
/// <summary>
|
||||
/// VRM10Controller CustomEditor
|
||||
///
|
||||
/// * Controller
|
||||
/// * Meta
|
||||
/// * Expression
|
||||
/// * LookAt
|
||||
/// * ForstPerson
|
||||
///
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(VRM10Controller))]
|
||||
public class VRM10ControllerEditor : Editor
|
||||
{
|
||||
VRM10Controller m_target;
|
||||
SkinnedMeshRenderer[] m_renderers;
|
||||
Dictionary<ExpressionKey, float> m_expressionKeyWeights = new Dictionary<ExpressionKey, float>();
|
||||
|
||||
public class ExpressionSlider
|
||||
enum Tabs
|
||||
{
|
||||
Dictionary<ExpressionKey, float> m_expressionKeys;
|
||||
ExpressionKey m_key;
|
||||
|
||||
public ExpressionSlider(Dictionary<ExpressionKey, float> expressionKeys, ExpressionKey key)
|
||||
{
|
||||
m_expressionKeys = expressionKeys;
|
||||
m_key = key;
|
||||
}
|
||||
|
||||
public KeyValuePair<ExpressionKey, float> 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<ExpressionKey, float>(m_key, newValue);
|
||||
}
|
||||
Controller,
|
||||
Meta,
|
||||
Expression,
|
||||
LookAt,
|
||||
SpringBone,
|
||||
FirstPerson,
|
||||
}
|
||||
List<ExpressionSlider> m_sliders;
|
||||
Tabs _tab = Tabs.Meta;
|
||||
|
||||
// for ScriptedObject
|
||||
Editor m_metaEditor;
|
||||
|
||||
// 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 @@ namespace UniVRM10
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
// Setup runtime function.
|
||||
if (UnityEngine.Application.isPlaying)
|
||||
{
|
||||
m_target.Setup();
|
||||
}
|
||||
|
||||
serializedObject.Update();
|
||||
|
||||
// Setup runtime function.
|
||||
m_target.Setup();
|
||||
|
||||
// base.OnInspectorGUI();
|
||||
switch (_tab)
|
||||
{
|
||||
case Tabs.Meta:
|
||||
@@ -148,99 +94,22 @@ namespace UniVRM10
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
188
Assets/VRM10/Editor/Components/VRM10Window.cs
Normal file
188
Assets/VRM10/Editor/Components/VRM10Window.cs
Normal file
@@ -0,0 +1,188 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniVRM10
|
||||
{
|
||||
/// <summary>
|
||||
/// VRM10操作 Window
|
||||
/// </summary>
|
||||
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<VRM10Controller>();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// public entry point
|
||||
/// </summary>
|
||||
/// <param name="target"></param>
|
||||
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<VRM10Constraint>();
|
||||
}
|
||||
}
|
||||
|
||||
using (new EditorGUI.DisabledGroupScope(true))
|
||||
{
|
||||
if (m_constraints != null)
|
||||
{
|
||||
foreach (var c in m_constraints)
|
||||
{
|
||||
EditorGUILayout.ObjectField(c, typeof(VRM10Constraint), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/VRM10/Editor/Components/VRM10Window.cs.meta
Normal file
11
Assets/VRM10/Editor/Components/VRM10Window.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d10db1ad6a9273e4698465a666033569
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,114 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniVRM10
|
||||
{
|
||||
/// <summary>
|
||||
/// 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 して使う。
|
||||
///
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
[Serializable]
|
||||
public class VRM10SpringBone : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
Color m_gizmoColor = Color.yellow;
|
||||
|
||||
[SerializeField]
|
||||
public string Comment;
|
||||
|
||||
[SerializeField]
|
||||
public List<VRM10SpringJoint> Joints = new List<VRM10SpringJoint>();
|
||||
|
||||
[SerializeField]
|
||||
public List<VRM10SpringBoneColliderGroup> ColliderGroups = new List<VRM10SpringBoneColliderGroup>();
|
||||
|
||||
[ContextMenu("Reset bones")]
|
||||
public void ResetJoints()
|
||||
{
|
||||
foreach (var joint in Joints)
|
||||
{
|
||||
if (joint != null)
|
||||
{
|
||||
joint.Transform.localRotation = Quaternion.identity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Transform m_center;
|
||||
|
||||
List<SpringBoneLogic.InternalCollider> m_colliderList = new List<SpringBoneLogic.InternalCollider>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,73 +23,8 @@ namespace UniVRM10
|
||||
/// <summary>bone local position</summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,54 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace UniVRM10
|
||||
{
|
||||
/// <summary>
|
||||
/// VRMC_node_collider
|
||||
/// </summary>
|
||||
[AddComponentMenu("VRM10/VRM10SpringBoneColliderGroup")]
|
||||
[Serializable]
|
||||
public class VRM10SpringBoneColliderGroup : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
List<VRM10SpringBoneCollider> m_colliders = new List<VRM10SpringBoneCollider>();
|
||||
public string Name;
|
||||
|
||||
public IEnumerable<VRM10SpringBoneCollider> Colliders
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_colliders.Where(x => x != null);
|
||||
}
|
||||
set
|
||||
{
|
||||
m_colliders = value.ToList();
|
||||
OnValidate();
|
||||
}
|
||||
}
|
||||
public string GUIName(int i) => $"{i:00}:{Name}";
|
||||
|
||||
public void AddCollider(VRM10SpringBoneCollider collider)
|
||||
{
|
||||
if (collider == null)
|
||||
{
|
||||
Debug.LogWarning("null collider");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_colliders == null)
|
||||
{
|
||||
m_colliders = new List<VRM10SpringBoneCollider>();
|
||||
}
|
||||
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<VRM10SpringBoneCollider> Colliders = new List<VRM10SpringBoneCollider>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1542d3467a35afa4dbefa2a112058078
|
||||
guid: 177ea458e237fee41b0902e3006c744b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
||||
@@ -8,16 +8,8 @@ using UnityEditor;
|
||||
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 @@ namespace UniVRM10
|
||||
#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<SpringBoneLogic.InternalCollider> colliders, VRM10SpringJoint tail)
|
||||
public void Process(Transform center, float deltaTime, List<SpringBoneLogic.InternalCollider> 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 @@ namespace UniVRM10
|
||||
else
|
||||
{
|
||||
// 親からまっすぐの位置に tail を作成
|
||||
var delta = Transform.position - Transform.parent.position;
|
||||
var childPosition = Transform.position + delta.normalized * 0.07f;
|
||||
m_logic = new SpringBoneLogic(center, Transform, Transform.worldToLocalMatrix.MultiplyPoint(childPosition));
|
||||
var delta = transform.position - transform.parent.position;
|
||||
var childPosition = transform.position + delta.normalized * 0.07f;
|
||||
m_logic = new SpringBoneLogic(center, transform, transform.worldToLocalMatrix.MultiplyPoint(childPosition));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,9 @@ namespace UniVRM10
|
||||
[SerializeField]
|
||||
public VRM10ControllerFirstPerson FirstPerson = new VRM10ControllerFirstPerson();
|
||||
|
||||
[SerializeField]
|
||||
public VRM10ControllerSpringBone SpringBone = new VRM10ControllerSpringBone();
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
if (Expression != null)
|
||||
@@ -55,8 +58,6 @@ namespace UniVRM10
|
||||
}
|
||||
}
|
||||
|
||||
VRM10SpringBone[] m_springs;
|
||||
|
||||
VRM10Constraint[] m_constraints;
|
||||
|
||||
Transform m_head;
|
||||
@@ -125,14 +126,7 @@ namespace UniVRM10
|
||||
//
|
||||
// spring
|
||||
//
|
||||
if (m_springs == null)
|
||||
{
|
||||
m_springs = GetComponentsInChildren<VRM10SpringBone>();
|
||||
}
|
||||
foreach (var spring in m_springs)
|
||||
{
|
||||
spring.Process(Controller.SpringBoneCenter);
|
||||
}
|
||||
SpringBone.Process(Controller.SpringBoneCenter);
|
||||
|
||||
//
|
||||
// gaze control
|
||||
|
||||
110
Assets/VRM10/Runtime/Components/VRM10ControllerSpringBone.cs
Normal file
110
Assets/VRM10/Runtime/Components/VRM10ControllerSpringBone.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniVRM10
|
||||
{
|
||||
/// <summary>
|
||||
/// SpringBone の情報をすべて保持する
|
||||
///
|
||||
/// * SpringBoneCollider
|
||||
/// * SpringBoneJoint
|
||||
///
|
||||
/// は、個別の MonoBehaviour として設定する
|
||||
///
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class VRM10ControllerSpringBone
|
||||
{
|
||||
[SerializeField]
|
||||
public List<VRM10SpringBoneColliderGroup> ColliderGroups = new List<VRM10SpringBoneColliderGroup>();
|
||||
|
||||
[Serializable]
|
||||
public class Spring
|
||||
{
|
||||
[SerializeField]
|
||||
public string Name;
|
||||
|
||||
public string GUIName(int i) => $"{i:00}:{Name}";
|
||||
|
||||
[SerializeField]
|
||||
public List<VRM10SpringBoneColliderGroup> ColliderGroups = new List<VRM10SpringBoneColliderGroup>();
|
||||
|
||||
[SerializeField]
|
||||
public List<VRM10SpringJoint> Joints = new List<VRM10SpringJoint>();
|
||||
|
||||
Transform m_center;
|
||||
List<SpringBoneLogic.InternalCollider> m_colliderList = new List<SpringBoneLogic.InternalCollider>();
|
||||
|
||||
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<Spring> Springs = new List<Spring>();
|
||||
|
||||
public void Process(Transform center)
|
||||
{
|
||||
foreach (var spring in Springs)
|
||||
{
|
||||
spring.Process(center);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 99c0f25b461a78e4099b81021d029663
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -263,7 +263,7 @@ namespace UniVRM10
|
||||
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 @@ namespace UniVRM10
|
||||
{
|
||||
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 @@ namespace UniVRM10
|
||||
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 @@ namespace UniVRM10
|
||||
return model.Nodes.IndexOf(node);
|
||||
};
|
||||
|
||||
var colliderGroups = vrmController.GetComponentsInChildren<VRM10SpringBoneColliderGroup>();
|
||||
foreach (var x in colliderGroups)
|
||||
foreach (var x in src.ColliderGroups)
|
||||
{
|
||||
var colliderGroup = new UniGLTF.Extensions.VRMC_springBone.ColliderGroup
|
||||
{
|
||||
@@ -350,13 +349,13 @@ namespace UniVRM10
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var x in vrmController.GetComponentsInChildren<VRM10SpringBone>())
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -377,18 +377,17 @@ namespace UniVRM10
|
||||
}
|
||||
|
||||
// colliderGroup
|
||||
var list = new List<VRM10SpringBoneColliderGroup>();
|
||||
foreach (var g in gltfVrmSpringBone.ColliderGroups)
|
||||
{
|
||||
var colliderGroup = secondary.gameObject.AddComponent<VRM10SpringBoneColliderGroup>();
|
||||
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<VRM10SpringBoneCollider>();
|
||||
colliderGroup.AddCollider(collider);
|
||||
colliderGroup.Colliders.Add(collider);
|
||||
|
||||
if (c.Shape.Sphere is UniGLTF.Extensions.VRMC_springBone.ColliderShapeSphere sphere)
|
||||
{
|
||||
@@ -416,11 +415,10 @@ namespace UniVRM10
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var firstJointNode = Nodes[gltfSpring.Joints.First().Node.Value];
|
||||
var springBone = firstJointNode.gameObject.AddComponent<VRM10SpringBone>();
|
||||
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)
|
||||
{
|
||||
@@ -431,14 +429,14 @@ namespace UniVRM10
|
||||
{
|
||||
throw new IndexOutOfRangeException($"{index} > {Nodes.Count}");
|
||||
}
|
||||
var joint = new VRM10SpringJoint(Nodes[gltfJoint.Node.Value]);
|
||||
var joint = Nodes[gltfJoint.Node.Value].gameObject.AddComponent<VRM10SpringJoint>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user