diff --git a/Assets/UniGLTF/Editor/UniGLTF/ExportDialog/TabBar.cs b/Assets/UniGLTF/Editor/UniGLTF/ExportDialog/TabBar.cs index 8bea7db02..09ffd2264 100644 --- a/Assets/UniGLTF/Editor/UniGLTF/ExportDialog/TabBar.cs +++ b/Assets/UniGLTF/Editor/UniGLTF/ExportDialog/TabBar.cs @@ -5,7 +5,6 @@ using UnityEngine; namespace UniGLTF.MeshUtility { - public static class TabBar { /// diff --git a/Assets/VRM10/Editor/MeshUtility.meta b/Assets/VRM10/Editor/MeshUtility.meta new file mode 100644 index 000000000..a7f3f27f4 --- /dev/null +++ b/Assets/VRM10/Editor/MeshUtility.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6fb2795d5ca5ce9449de94ea5fb94dde +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/MeshUtility/MeshIntegrationAndSplit.cs b/Assets/VRM10/Editor/MeshUtility/MeshIntegrationAndSplit.cs new file mode 100644 index 000000000..78bdb227d --- /dev/null +++ b/Assets/VRM10/Editor/MeshUtility/MeshIntegrationAndSplit.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UniGLTF; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; + +namespace UniVRM10 +{ + class MeshIntegrationAndSplit + { + class MeshIntegrationGroup + { + public string Name; + public List Renderers = new List(); + } + List _meshIntegrationList = new List(); + List _renderers = new List(); + + Splitter _splitter; + ReorderableList _groupList; + ReorderableList _rendererList; + int _selected = -1; + int Selected + { + set + { + if (_selected == value) + { + return; + } + if (value < 0 || value >= _meshIntegrationList.Count) + { + return; + } + _selected = value; + _renderers.Clear(); + _renderers.AddRange(_meshIntegrationList[_selected].Renderers); + } + } + + public MeshIntegrationAndSplit(EditorWindow editor) + { + _splitter = new VerticalSplitter(editor, 50, 50); + + _groupList = new ReorderableList(_meshIntegrationList, typeof(MeshIntegrationGroup)); + _groupList.drawHeaderCallback = (Rect rect) => + { + GUI.Label(rect, "Integration group"); + }; + _groupList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) => + { + var group = _meshIntegrationList[index]; + EditorGUI.TextField(rect, group.Name); + }; + _groupList.onSelectCallback = rl => + { + Selected = (rl.selectedIndices.Count > 0) ? rl.selectedIndices[0] : -1; + }; + + _rendererList = new ReorderableList(_renderers, typeof(Renderer)); + _rendererList.drawHeaderCallback = (Rect rect) => + { + GUI.Label(rect, "Renderer"); + }; + _rendererList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) => + { + var r = _renderers[index]; + EditorGUI.ObjectField(rect, r, typeof(Renderer), true); + }; + } + + public void UpdateMeshIntegrationList(GameObject root) + { + _selected = -1; + _meshIntegrationList.Clear(); + + IntegrateFirstPerson(root); + Selected = 0; + } + + void IntegrateAll(GameObject root) + { + if (root == null) + { + return; + } + _meshIntegrationList.Add(new MeshIntegrationGroup + { + Name = "ALL", + Renderers = root.GetComponentsInChildren().ToList(), + }); + } + + MeshIntegrationGroup GetOrCreateGroup(string name) + { + foreach (var g in _meshIntegrationList) + { + if (g.Name == name) + { + return g; + } + } + _meshIntegrationList.Add(new MeshIntegrationGroup + { + Name = name, + }); + return _meshIntegrationList.Last(); + } + + void IntegrateFirstPerson(GameObject root) + { + if (root == null) + { + return; + } + var vrm1 = root.GetComponent(); + if (vrm1 == null) + { + return; + } + var vrmObject = vrm1.Vrm; + if (vrmObject == null) + { + return; + } + var fp = vrmObject.FirstPerson; + if (fp == null) + { + return; + } + foreach (var a in fp.Renderers) + { + var g = GetOrCreateGroup(a.FirstPersonFlag.ToString()); + g.Renderers.Add(a.GetRenderer(root.transform)); + } + } + + private void ShowGroup(Rect rect) + { + _groupList.DoList(rect); + } + + private void ShowSelected(Rect rect) + { + _rendererList.DoList(rect); + } + + public void OnGui(Rect rect) + { + _splitter.OnGUI( + rect, + ShowGroup, + ShowSelected); + } + } +} \ No newline at end of file diff --git a/Assets/VRM10/Editor/MeshUtility/MeshIntegrationAndSplit.cs.meta b/Assets/VRM10/Editor/MeshUtility/MeshIntegrationAndSplit.cs.meta new file mode 100644 index 000000000..2fb20646e --- /dev/null +++ b/Assets/VRM10/Editor/MeshUtility/MeshIntegrationAndSplit.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0dd9385e7eb3d8b468ca851b18d9846f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/MeshUtility/Splitter.cs b/Assets/VRM10/Editor/MeshUtility/Splitter.cs new file mode 100644 index 000000000..743bae80c --- /dev/null +++ b/Assets/VRM10/Editor/MeshUtility/Splitter.cs @@ -0,0 +1,183 @@ +using UnityEngine; +using UnityEditor; +using System; + +namespace UniVRM10 +{ + [Serializable] + public abstract class Splitter + { + internal enum SplitMode + { + Horizontal, + Vertical, + } + + protected float _value; + + private EditorWindow _window; + private SplitMode _lockMode; + private float _lockValues; + private MouseCursor _mouseCursor; + protected float _barSize; + + private bool _isResize; + private Rect _resizeRect; + private float _ratio = 0.5f; + + private static readonly Color SPLITTER_COLOR = new Color(0, 0, 0, 1.0f); + private static readonly Color SPLITTER_COLOR_FREE = new Color(0.5f, 0.5f, 0.5f, 1.0f); + + internal Splitter(EditorWindow window, float defaultValue, SplitMode lockMode, float minValue, float barSize = 16f) + { + _window = window; + _value = defaultValue; + _lockMode = lockMode; + _lockValues = minValue; + _mouseCursor = lockMode == SplitMode.Vertical ? MouseCursor.ResizeHorizontal : MouseCursor.ResizeVertical; + _barSize = barSize; + } + + protected abstract RectOffset RectOffset(); + + protected abstract Rect MainRect(Rect rect); + + protected abstract Rect SubRect(Rect rect); + + protected abstract Rect BarRect(Rect rect); + + public void OnGUI(Rect rect, Action mainDelegate, Action subDelegate) + { + var current = Event.current; + + mainDelegate(MainRect(rect)); + subDelegate(SubRect(rect)); + + _resizeRect = BarRect(rect); + EditorGUIUtility.AddCursorRect(_resizeRect, _mouseCursor); + + var clampMax = _lockMode == SplitMode.Vertical ? rect.width - _lockValues : rect.height - _lockValues; + var targetSplitterValue = _lockMode == SplitMode.Vertical ? rect.width : rect.height; + + _ratio = _lockMode == SplitMode.Vertical ? _value / rect.width : _value / rect.height; + + if (current.type == EventType.MouseDown && _resizeRect.Contains(current.mousePosition)) + _isResize = true; + if (current.type == EventType.MouseUp) + _isResize = false; + + if (_isResize && current.type == EventType.MouseDrag) + { + var targetValue = _lockMode == SplitMode.Vertical + ? current.mousePosition.x + : current.mousePosition.y; + var diffValue = _lockMode == SplitMode.Vertical ? rect.width : rect.height; + _ratio = targetValue / diffValue; + } + else if (current.type != EventType.Layout && Event.current.type != EventType.Used) + { + _ratio = targetSplitterValue * _ratio / targetSplitterValue; + } + + _value = Mathf.Clamp(targetSplitterValue * _ratio, _lockValues, clampMax); + + EditorGUI.DrawRect(RectOffset().Remove(_resizeRect), SPLITTER_COLOR_FREE); + + if (_isResize) + _window.Repaint(); + } + } + + [Serializable] + public class HorizontalSplitter : Splitter + { + private static readonly RectOffset RECT_OFFSET = new RectOffset(0, 0, 7, 8); + + public HorizontalSplitter(EditorWindow window, float defaultValue, float minValue, float barSize = 16) + : base(window, defaultValue, SplitMode.Horizontal, minValue, barSize) + { + } + + protected override RectOffset RectOffset() + { + return RECT_OFFSET; + } + + protected override Rect MainRect(Rect rect) + { + return new Rect(rect) + { + x = 0, + y = 0, + height = _value, + }; + } + + protected override Rect SubRect(Rect rect) + { + return new Rect(rect) + { + x = 0, + y = _value, + height = rect.height - _value, + }; + } + + protected override Rect BarRect(Rect rect) + { + return new Rect(rect) + { + x = 0, + y = _value - _barSize / 2, + height = _barSize, + }; + } + + } + + [Serializable] + public class VerticalSplitter : Splitter + { + private static readonly RectOffset RECT_OFFSET = new RectOffset(0, 0, 7, 8); + + public VerticalSplitter(EditorWindow window, float defaultValue, float minValue, float barSize = 4) + : base(window, defaultValue, SplitMode.Vertical, minValue, barSize) + { + } + + protected override RectOffset RectOffset() + { + return RECT_OFFSET; + } + + protected override Rect MainRect(Rect rect) + { + return new Rect(rect) + { + x = rect.x + 0, + y = rect.y + 0, + width = _value, + }; + } + + protected override Rect SubRect(Rect rect) + { + return new Rect(rect) + { + x = rect.x + _value, + y = rect.y + 0, + width = rect.width - _value, + }; + } + + protected override Rect BarRect(Rect rect) + { + return new Rect(rect) + { + x = rect.x + _value - _barSize / 2, + y = rect.y + 0, + width = _barSize, + }; + } + } +} \ No newline at end of file diff --git a/Assets/VRM10/Editor/MeshUtility/Splitter.cs.meta b/Assets/VRM10/Editor/MeshUtility/Splitter.cs.meta new file mode 100644 index 000000000..57ca79cfc --- /dev/null +++ b/Assets/VRM10/Editor/MeshUtility/Splitter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 385274edf555ed84a9f3706ca2a99023 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/MeshUtility/Vrm10MeshUtilityDialog.cs b/Assets/VRM10/Editor/MeshUtility/Vrm10MeshUtilityDialog.cs new file mode 100644 index 000000000..2ef2a208f --- /dev/null +++ b/Assets/VRM10/Editor/MeshUtility/Vrm10MeshUtilityDialog.cs @@ -0,0 +1,144 @@ +using UnityEngine; +using UnityEditor; +using UniGLTF.M17N; +using System.Collections.Generic; +using UniGLTF.MeshUtility; +using UniGLTF; +using System.Linq; + +namespace UniVRM10 +{ + public class Vrm10MeshUtilityDialog : EditorWindow + { + const string TITLE = "Vrm10 Mesh Utility Window"; + + enum Tabs + { + MeshFreeze, + MeshIntegration, + } + Tabs _tab; + + public static void OpenWindow() + { + var window = + (Vrm10MeshUtilityDialog)EditorWindow.GetWindow(typeof(Vrm10MeshUtilityDialog)); + window.titleContent = new GUIContent(TITLE); + window.Show(); + } + + private void Validate() + { + _validations.Clear(); + if (_exportTarget == null) + { + _validations.Add(Validation.Error("set vrm1")); + return; + } + if (_exportTarget.GetComponent() == null) + { + _validations.Add(Validation.Error("target is not vrm1")); + return; + } + } + bool IsValid => !_validations.Any(v => !v.CanExport); + + Vector2 _scrollPos; + List _validations = new List(); + GameObject _exportTarget; + + MeshIntegrationAndSplit _meshIntegration; + + void OnEnable() + { + _meshIntegration = new MeshIntegrationAndSplit(this); + } + + private void OnGUI() + { + var modified = false; + _scrollPos = EditorGUILayout.BeginScrollView(_scrollPos); + EditorGUIUtility.labelWidth = 200; + LanguageGetter.OnGuiSelectLang(); + var exportTarget = (GameObject)EditorGUILayout.ObjectField( + MeshUtilityMessages.TARGET_OBJECT.Msg(), + _exportTarget, typeof(GameObject), true); + if (exportTarget != _exportTarget) + { + _exportTarget = exportTarget; + _meshIntegration.UpdateMeshIntegrationList(_exportTarget); + modified = true; + } + _tab = TabBar.OnGUI(_tab, "LargeButton", GUI.ToolbarButtonSize.Fixed); + + foreach (var validation in _validations) + { + validation.DrawGUI(); + } + + switch (_tab) + { + case Tabs.MeshFreeze: + { + if (MeshBakeGui()) + { + modified = true; + } + break; + } + + case Tabs.MeshIntegration: + { + if (MeshIntegrateGui()) + { + modified = true; + } + break; + } + } + EditorGUILayout.EndScrollView(); + + if (modified) + { + Validate(); + } + + GUI.enabled = IsValid; + var pressed = GUILayout.Button("Process", GUILayout.MinWidth(100)); + GUI.enabled = true; + if (pressed) + { + // Show Result ? + // Close(); + // GUIUtility.ExitGUI(); + Debug.Log("Process !"); + } + } + + bool MeshBakeGui() + { + EditorGUILayout.Toggle("BlendShape", false); + EditorGUILayout.Toggle("Scale", false); + EditorGUILayout.Toggle("Rotation", false); + return default; + } + + bool MeshIntegrateGui() + { + EditorGUILayout.Toggle("FirstPerson == AUTO の生成", false); + EditorGUILayout.Toggle("Separate by BlendShape", false); + var p = position; + var last = GUILayoutUtility.GetLastRect(); + var y = last.y + last.height; + var rect = new Rect + { + x = last.x, + y = y, + width = p.width, + height = p.height - y - 30 + }; + _meshIntegration.OnGui(rect); + return default; + } + } +} \ No newline at end of file diff --git a/Assets/VRM10/Editor/MeshUtility/Vrm10MeshUtilityDialog.cs.meta b/Assets/VRM10/Editor/MeshUtility/Vrm10MeshUtilityDialog.cs.meta new file mode 100644 index 000000000..829b51745 --- /dev/null +++ b/Assets/VRM10/Editor/MeshUtility/Vrm10MeshUtilityDialog.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9f3af8a1b265d8a4da7ad8c1c29d2585 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Editor/Vrm10TopMenu.cs b/Assets/VRM10/Editor/Vrm10TopMenu.cs index 914101aa3..81efdc956 100644 --- a/Assets/VRM10/Editor/Vrm10TopMenu.cs +++ b/Assets/VRM10/Editor/Vrm10TopMenu.cs @@ -1,3 +1,4 @@ +using UniGLTF.MeshUtility; using UnityEditor; namespace UniVRM10 @@ -11,6 +12,9 @@ namespace UniVRM10 [MenuItem(UserMenuPrefix + "/Export VRM-1.0", priority = 1)] private static void OpenExportDialog() => VRM10ExportDialog.Open(); + [MenuItem(UserMenuPrefix + "/MeshUtility", priority = 2)] + private static void OpenMeshUtility() => Vrm10MeshUtilityDialog.OpenWindow(); + [MenuItem(ExperimentalMenuPrefix + "/Convert BVH to VRM-Animation", priority = 100)] private static void ConvertVrmAnimation() => VrmAnimationMenu.BvhToVrmAnimationMenu();