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:
ousttrue
2021-06-01 16:00:49 +09:00
committed by GitHub
parent 4d207c643d
commit 3f09717236
35 changed files with 1184 additions and 764 deletions

View File

@@ -1,6 +1,4 @@
using System.Text;
using UnityEditor;
using UnityEngine;
namespace UniVRM10
{

View File

@@ -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);
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 1f440a6e91549e542bda6d6d741b1409
guid: d1d6be45aa67f5445884833ae12546bb
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -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();
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 96fdd3a9c7c5ca44b977ef564a2cf15b
guid: 3387f1e8cba522b44a78609529674819
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8823eaceb68051c43be15ea92028d412
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View 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);
}
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 280a977934f9b9b449ac58bc0f016959
guid: 0f9ee1d04c43d8048a51e032b19a8ebd
MonoImporter:
externalObjects: {}
serializedVersion: 2

View 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);
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2a55f4351fdc33f4fadbaa02abc5ffdb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d8a1e5efc02576543b0a7f051bde4eca
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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()
{
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3afd3f6a158933546aa5d979acdf7030
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 33ca35799b1168949bb4466aae13dfd4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 735a730511f68da4da8d2cf2dcba722e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b1b992b7266d6d8429b8aeac90efa6a5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}
}

View 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);
}
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d10db1ad6a9273e4698465a666033569
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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>();
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 1542d3467a35afa4dbefa2a112058078
guid: 177ea458e237fee41b0902e3006c744b
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -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));
}
}

View File

@@ -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

View 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);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 99c0f25b461a78e4099b81021d029663
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}

View File

@@ -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);
}
}
}