Merge pull request #2177 from ousttrue/fix/impl_remove_scaling

[vrm1.0] impl remove scaling
This commit is contained in:
ousttrue
2023-10-26 14:38:39 +09:00
committed by GitHub
28 changed files with 762 additions and 409 deletions

View File

@@ -1,309 +0,0 @@
using System;
using System.IO;
using UnityEngine;
using System.Collections.Generic;
using UnityEditor;
namespace UniGLTF.MeshUtility
{
/// <summary>
/// Application.dataPath を root とするファイルパスを扱う。
/// (Project/Assets)
/// </summary>
public struct AssetsPath
{
public string RelativePath
{
get;
private set;
}
public override string ToString()
{
return $"assets://{RelativePath}";
}
public string FileName
{
get { return Path.GetFileName(RelativePath); }
}
public string FileNameWithoutExtension
{
get
{
return Path.GetFileNameWithoutExtension(RelativePath);
}
}
public string Extension
{
get { return Path.GetExtension(RelativePath); }
}
public AssetsPath Parent
{
get
{
return new AssetsPath(Path.GetDirectoryName(RelativePath));
}
}
static readonly char[] EscapeChars = new char[]
{
'\\',
'/',
':',
'*',
'?',
'"',
'<',
'>',
'|',
};
static string EscapeFilePath(string path)
{
foreach (var x in EscapeChars)
{
path = path.Replace(x, '+');
}
return path;
}
public AssetsPath Child(string name)
{
if (RelativePath == null)
{
throw new NotImplementedException();
}
else if (RelativePath == "")
{
return new AssetsPath(name);
}
else
{
return new AssetsPath(RelativePath + "/" + name);
}
}
public override int GetHashCode()
{
if (RelativePath == null)
{
return 0;
}
return RelativePath.GetHashCode();
}
public override bool Equals(object obj)
{
if (obj is AssetsPath)
{
var rhs = (AssetsPath)obj;
if (RelativePath == null && rhs.RelativePath == null)
{
return true;
}
else if (RelativePath == null)
{
return false;
}
else if (rhs.RelativePath == null)
{
return false;
}
else
{
return RelativePath == rhs.RelativePath;
}
}
else
{
return false;
}
}
AssetsPath(string value) : this()
{
RelativePath = value.Replace("\\", "/");
}
#region FullPath
static string s_basePath;
static string BaseFullPath
{
get
{
if (string.IsNullOrEmpty(s_basePath))
{
s_basePath = Path.GetFullPath(Application.dataPath).Replace("\\", "/");
}
return s_basePath;
}
}
public string FullPath
{
get
{
if (RelativePath == null)
{
throw new NotImplementedException();
}
return Path.Combine(BaseFullPath, RelativePath).Replace("\\", "/");
}
}
public bool IsFileExists
{
get { return File.Exists(FullPath); }
}
public bool IsDirectoryExists
{
get { return Directory.Exists(FullPath); }
}
/// <summary>
///
/// </summary>
/// <param name="fullPath">C:/path/to/file</param>
/// <returns></returns>
public static AssetsPath FromFullpath(string fullPath)
{
if (fullPath == null)
{
fullPath = "";
}
fullPath = fullPath.Replace("\\", "/");
if (fullPath == BaseFullPath)
{
return new AssetsPath
{
RelativePath = ""
};
}
else if (fullPath.StartsWith(BaseFullPath + "/"))
{
return new AssetsPath(fullPath.Substring(BaseFullPath.Length + 1));
}
else
{
return default(AssetsPath);
}
}
#endregion
public IEnumerable<AssetsPath> TraverseDir()
{
if (IsDirectoryExists)
{
yield return this;
foreach (var child in ChildDirs)
{
foreach (var x in child.TraverseDir())
{
yield return x;
}
}
}
}
public IEnumerable<AssetsPath> ChildDirs
{
get
{
foreach (var x in Directory.GetDirectories(FullPath))
{
yield return AssetsPath.FromFullpath(x);
}
}
}
public IEnumerable<AssetsPath> ChildFiles
{
get
{
foreach (var x in Directory.GetFiles(FullPath))
{
yield return AssetsPath.FromFullpath(x);
}
}
}
#if UNITY_EDITOR
string UnityPath => $"Assets/{RelativePath}";
public T GetImporter<T>() where T : AssetImporter
{
return AssetImporter.GetAtPath(UnityPath) as T;
}
public static AssetsPath FromAsset(UnityEngine.Object asset)
{
// skip Assets/
return new AssetsPath(AssetDatabase.GetAssetPath(asset).Substring(7));
}
public void ImportAsset()
{
AssetDatabase.ImportAsset(UnityPath);
}
public void EnsureFolder()
{
if (RelativePath == null)
{
throw new NotImplementedException();
}
if (!string.IsNullOrEmpty(Parent.RelativePath))
{
Parent.EnsureFolder();
}
if (!IsDirectoryExists)
{
var parent = Parent;
// ensure parent
parent.ImportAsset();
// create
AssetDatabase.CreateFolder(
parent.UnityPath,
Path.GetFileName(RelativePath)
);
ImportAsset();
}
}
public UnityEngine.Object[] GetSubAssets()
{
return AssetDatabase.LoadAllAssetsAtPath(UnityPath);
}
public void CreateAsset(UnityEngine.Object o)
{
AssetDatabase.CreateAsset(o, UnityPath);
}
public void AddObjectToAsset(UnityEngine.Object o)
{
AssetDatabase.AddObjectToAsset(o, UnityPath);
}
public T LoadAsset<T>() where T : UnityEngine.Object
{
return AssetDatabase.LoadAssetAtPath<T>(UnityPath);
}
public AssetsPath GenerateUniqueAssetPath()
{
return new AssetsPath(AssetDatabase.GenerateUniqueAssetPath(UnityPath));
}
#endif
}
}

View File

@@ -15,20 +15,20 @@ namespace UniGLTF.MeshUtility
/// UnityEngine.Object を継承したクラスのフィールドに ユーザー定義 struct を配置する。
/// 持ち主の SerializedObject を経由して EditorGUILayout.PropertyField してる。
/// </summary>
[CustomEditor(typeof(MeshProcessDialog), true)]
class MeshProcessDialogEditor : Editor
[CustomEditor(typeof(MeshUtilityDialog), true)]
class BoneMeshEraserEditor : Editor
{
MeshProcessDialog _targetDialog;
MeshUtilityDialog _targetDialog;
SerializedProperty _skinnedMesh;
SerializedProperty _eraseBones;
void OnEnable()
{
_targetDialog = target as MeshProcessDialog;
_targetDialog = target as MeshUtilityDialog;
if (_targetDialog)
{
_skinnedMesh = serializedObject.FindProperty(nameof(MeshProcessDialog._skinnedMeshRenderer));
_eraseBones = serializedObject.FindProperty(nameof(MeshProcessDialog._eraseBones));
_skinnedMesh = serializedObject.FindProperty(nameof(MeshUtilityDialog._skinnedMeshRenderer));
_eraseBones = serializedObject.FindProperty(nameof(MeshUtilityDialog._eraseBones));
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 04fa1873cf1c0374aab1670a24d741b5
guid: 2021859c0d7255643bd93c95ab3fcf3d
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -1,9 +0,0 @@
namespace UniGLTF.MeshUtility
{
public enum MeshProcessDialogTabs
{
MeshSeparator,
MeshIntegrator,
BoneMeshEraser,
}
}

View File

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

View File

@@ -5,9 +5,16 @@ using System.Collections.Generic;
namespace UniGLTF.MeshUtility
{
public class MeshProcessDialog : EditorWindow
public class MeshUtilityDialog : EditorWindow
{
const string TITLE = "Mesh Processing Window";
const string TITLE = "Mesh Utility Window";
enum MeshProcessDialogTabs
{
MeshSeparator,
MeshIntegrator,
BoneMeshEraser,
}
MeshProcessDialogTabs _tab;
private GameObject _exportTarget;
@@ -21,13 +28,13 @@ namespace UniGLTF.MeshUtility
[SerializeField]
public List<BoneMeshEraser.EraseBone> _eraseBones;
private MeshProcessDialogEditor _boneMeshEraserEditor;
private BoneMeshEraserEditor _boneMeshEraserEditor;
private Vector2 _scrollPos = new Vector2(0, 0);
public static void OpenWindow()
{
var window =
(MeshProcessDialog)EditorWindow.GetWindow(typeof(MeshProcessDialog));
(MeshUtilityDialog)EditorWindow.GetWindow(typeof(MeshUtilityDialog));
window.titleContent = new GUIContent(TITLE);
window.Show();
}
@@ -36,7 +43,7 @@ namespace UniGLTF.MeshUtility
{
if (!_boneMeshEraserEditor)
{
_boneMeshEraserEditor = (MeshProcessDialogEditor)Editor.CreateEditor(this);
_boneMeshEraserEditor = (BoneMeshEraserEditor)Editor.CreateEditor(this);
}
}
@@ -45,7 +52,7 @@ namespace UniGLTF.MeshUtility
_scrollPos = EditorGUILayout.BeginScrollView(_scrollPos);
EditorGUIUtility.labelWidth = 200;
LanguageGetter.OnGuiSelectLang();
_exportTarget = (GameObject)EditorGUILayout.ObjectField(MeshProcessingMessages.TARGET_OBJECT.Msg(), _exportTarget, typeof(GameObject), true);
_exportTarget = (GameObject)EditorGUILayout.ObjectField(MeshUtilityMessages.TARGET_OBJECT.Msg(), _exportTarget, typeof(GameObject), true);
_tab = TabBar.OnGUI(_tab, "LargeButton", GUI.ToolbarButtonSize.Fixed);
var processed = false;
@@ -53,7 +60,7 @@ namespace UniGLTF.MeshUtility
{
case MeshProcessDialogTabs.MeshSeparator:
{
EditorGUILayout.HelpBox(MeshProcessingMessages.MESH_SEPARATOR.Msg(), MessageType.Info);
EditorGUILayout.HelpBox(MeshUtilityMessages.MESH_SEPARATOR.Msg(), MessageType.Info);
if (TabMeshSeparator.TryExecutable(_exportTarget, out string msg))
{
processed = TabMeshSeparator.OnGUI(_exportTarget);
@@ -67,8 +74,8 @@ namespace UniGLTF.MeshUtility
case MeshProcessDialogTabs.MeshIntegrator:
{
EditorGUILayout.HelpBox(MeshProcessingMessages.MESH_INTEGRATOR.Msg(), MessageType.Info);
_separateByBlendShape = EditorGUILayout.Toggle(MeshProcessingMessages.MESH_SEPARATOR_BY_BLENDSHAPE.Msg(), _separateByBlendShape);
EditorGUILayout.HelpBox(MeshUtilityMessages.MESH_INTEGRATOR.Msg(), MessageType.Info);
_separateByBlendShape = EditorGUILayout.Toggle(MeshUtilityMessages.MESH_SEPARATOR_BY_BLENDSHAPE.Msg(), _separateByBlendShape);
if (TabMeshIntegrator.TryExecutable(_exportTarget, out string msg))
{
if (GUILayout.Button("Process", GUILayout.MinWidth(100)))
@@ -85,7 +92,7 @@ namespace UniGLTF.MeshUtility
case MeshProcessDialogTabs.BoneMeshEraser:
{
EditorGUILayout.HelpBox(MeshProcessingMessages.BONE_MESH_ERASER.Msg(), MessageType.Info);
EditorGUILayout.HelpBox(MeshUtilityMessages.BONE_MESH_ERASER.Msg(), MessageType.Info);
if (_boneMeshEraserEditor)
{
_boneMeshEraserEditor.OnInspectorGUI();

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: da24ccbbed21eb94d902d42337df3699
guid: 1bdcb928351f92047a91c17c3d629853
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -2,7 +2,7 @@ using UniGLTF.M17N;
namespace UniGLTF.MeshUtility
{
public enum MeshProcessingMessages
public enum MeshUtilityMessages
{
[LangMsg(Languages.ja, "ターゲットオブジェクト")]
[LangMsg(Languages.en, "TargetObject")]

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 829a678062065f04582189619babae79
guid: abaf50b8dc367d4448cd5d7c99a4eea3
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -17,13 +17,13 @@ namespace UniGLTF.MeshUtility
{
if (root == null)
{
msg = MeshProcessingMessages.NO_GAMEOBJECT_SELECTED.Msg();
msg = MeshUtilityMessages.NO_GAMEOBJECT_SELECTED.Msg();
return false;
}
if (smr == null)
{
msg = MeshProcessingMessages.SELECT_SKINNED_MESH.Msg();
msg = MeshUtilityMessages.SELECT_SKINNED_MESH.Msg();
return false;
}

View File

@@ -13,19 +13,19 @@ namespace UniGLTF.MeshUtility
// check
if (root == null)
{
msg = MeshProcessingMessages.NO_GAMEOBJECT_SELECTED.Msg();
msg = MeshUtilityMessages.NO_GAMEOBJECT_SELECTED.Msg();
return false;
}
if (HasVrm(root))
{
msg = MeshProcessingMessages.VRM_DETECTED.Msg();
msg = MeshUtilityMessages.VRM_DETECTED.Msg();
return false;
}
if (root.GetComponentsInChildren<SkinnedMeshRenderer>().Length == 0 && root.GetComponentsInChildren<MeshFilter>().Length == 0)
{
msg = MeshProcessingMessages.NO_MESH.Msg();
msg = MeshUtilityMessages.NO_MESH.Msg();
return false;
}

View File

@@ -24,13 +24,13 @@ namespace UniGLTF.MeshUtility
{
if (root == null)
{
msg = MeshProcessingMessages.NO_GAMEOBJECT_SELECTED.Msg();
msg = MeshUtilityMessages.NO_GAMEOBJECT_SELECTED.Msg();
return false;
}
if (root.GetComponentsInChildren<SkinnedMeshRenderer>().Length == 0)
{
msg = MeshProcessingMessages.NO_SKINNED_MESH.Msg();
msg = MeshUtilityMessages.NO_SKINNED_MESH.Msg();
return false;
}

View File

@@ -10,7 +10,6 @@ namespace UniGLTF
public static class TopMenu
{
private const string UserGltfMenuPrefix = UniGLTFVersion.MENU;
private const string UserMeshUtilityPrefix = UniGLTFVersion.MENU + "/Mesh Utility";
private const string DevelopmentMenuPrefix = UniGLTFVersion.MENU + "/Development";
[MenuItem(UserGltfMenuPrefix + "/Version: " + UniGLTFVersion.UNIGLTF_VERSION, validate = true)]
@@ -25,8 +24,8 @@ namespace UniGLTF
[MenuItem(UserGltfMenuPrefix + "/Import from GLTF (*.gltf|*.glb|*.zip)", priority = 2)]
private static void ImportGltfFile() => TopMenuImplementation.ImportGltfFileToGameObject();
[MenuItem(UserMeshUtilityPrefix + "/MeshProcessing Wizard", priority = 10)]
private static void OpenMeshProcessingWindow() => MeshUtility.MeshProcessDialog.OpenWindow();
[MenuItem(UserGltfMenuPrefix + "/MeshUtility Wizard", priority = 10)]
private static void OpenMeshProcessingWindow() => MeshUtility.MeshUtilityDialog.OpenWindow();
#if VRM_DEVELOP
[MenuItem(DevelopmentMenuPrefix + "/Generate Serialization Code", priority = 20)]

View File

@@ -5,7 +5,6 @@ using UnityEngine;
namespace UniGLTF.MeshUtility
{
public static class TabBar
{
/// <summary>

View File

@@ -11,36 +11,35 @@ namespace UniGLTF.MeshUtility
{
public delegate Avatar CreateAvatarFunc(GameObject original, GameObject normalized, Dictionary<Transform, Transform> boneMap);
static (GameObject, Dictionary<Transform, Transform>) NormalizeHierarchy(GameObject go, CreateAvatarFunc createAvatar)
public static (GameObject, Dictionary<Transform, Transform>) CreateNormalizedHierarchy(GameObject go,
bool removeScaling = true,
bool removeRotation = true)
{
var boneMap = new Dictionary<Transform, Transform>();
//
// 回転・スケールの無いヒエラルキーをコピーする
//
var normalized = new GameObject(go.name + "(normalized)");
normalized.transform.position = go.transform.position;
CopyAndBuild(go.transform, normalized.transform, boneMap);
//
// 新しいヒエラルキーからAvatarを作る
//
if (removeScaling && removeRotation)
{
var animator = normalized.AddComponent<Animator>();
var avatar = createAvatar(go, normalized, boneMap);
avatar.name = go.name + ".normalized";
animator.avatar = avatar;
RemoveScaleAndRotationRecursive(go.transform, normalized.transform, boneMap);
}
else if (removeScaling)
{
RemoveScaleAndRotationRecursive(go.transform, normalized.transform, boneMap);
}
else if (removeRotation)
{
throw new NotImplementedException();
}
else
{
throw new ArgumentNullException();
}
return (normalized, boneMap);
}
/// <summary>
/// 回転とスケールを除去したヒエラルキーをコピーする。
/// </summary>
/// <param name="src"></param>
/// <param name="dst"></param>
static void CopyAndBuild(Transform src, Transform dst, Dictionary<Transform, Transform> boneMap)
static void RemoveScaleRecursive(Transform src, Transform dst, Dictionary<Transform, Transform> boneMap)
{
boneMap[src] = dst;
@@ -50,9 +49,27 @@ namespace UniGLTF.MeshUtility
{
var dstChild = new GameObject(child.name);
dstChild.transform.SetParent(dst);
dstChild.transform.position = child.position; // copy position only
dstChild.transform.position = child.position; // copy world position
dstChild.transform.rotation = child.localToWorldMatrix.rotation; // copy world rotation
// scale is removed
RemoveScaleRecursive(child, dstChild.transform, boneMap);
}
}
}
CopyAndBuild(child, dstChild.transform, boneMap);
static void RemoveScaleAndRotationRecursive(Transform src, Transform dst, Dictionary<Transform, Transform> boneMap)
{
boneMap[src] = dst;
foreach (Transform child in src)
{
if (child.gameObject.activeSelf)
{
var dstChild = new GameObject(child.name);
dstChild.transform.SetParent(dst);
dstChild.transform.position = child.position; // copy world position
RemoveScaleAndRotationRecursive(child, dstChild.transform, boneMap);
}
}
}
@@ -489,12 +506,12 @@ namespace UniGLTF.MeshUtility
/// <param name="bakeCurrentBlendShape">BlendShapeを0クリアするか否か。false の場合 BlendShape の現状を Bake する</param>
/// <param name="createAvatar">Avatarを作る関数</param>
/// <returns></returns>
public static (GameObject, Dictionary<Transform, Transform>) Execute(GameObject go, CreateAvatarFunc createAvatar)
public static (GameObject, Dictionary<Transform, Transform>) Execute(GameObject go)
{
//
// 正規化されたヒエラルキーを作る
//
var (normalized, boneMap) = NormalizeHierarchy(go, createAvatar);
var (normalized, boneMap) = CreateNormalizedHierarchy(go);
//
// 各メッシュから回転・スケールを取り除いてBinding行列を再計算する

View File

@@ -5,6 +5,7 @@ using UnityEngine;
using System;
using System.Linq;
using System.Collections.Generic;
using UniGLTF.Utils;
namespace UniHumanoid
@@ -271,5 +272,42 @@ namespace UniHumanoid
return false;
}
#endif
public static void AddAnimator(GameObject _src,
GameObject dst,
IDictionary<Transform, Transform> boneMap,
Action<AvatarDescription> modAvatarDesc = null)
{
var src = _src.GetComponent<Animator>();
var srcHumanBones = CachedEnum.GetValues<HumanBodyBones>()
.Where(x => x != HumanBodyBones.LastBone)
.Select(x => new { Key = x, Value = src.GetBoneTransform(x) })
.Where(x => x.Value != null)
;
var map =
srcHumanBones
.Where(x => boneMap.ContainsKey(x.Value))
.ToDictionary(x => x.Key, x => boneMap[x.Value])
;
var animator = dst.AddComponent<Animator>();
if (animator == null)
{
animator = dst.AddComponent<Animator>();
}
var avatarDescription = UniHumanoid.AvatarDescription.Create();
if (modAvatarDesc != null)
{
modAvatarDesc(avatarDescription);
}
avatarDescription.SetHumanBones(map);
var avatar = avatarDescription.CreateAvatar(dst.transform);
avatar.name = dst.name;
animator.avatar = avatar;
}
}
}

View File

@@ -65,31 +65,13 @@ namespace VRM
}
}
//
// 正規化されたヒエラルキーを作る
//
var (normalized, bMap) = BoneNormalizer.Execute(go, (_src, dst, boneMap) =>
var (normalized, bMap) = BoneNormalizer.Execute(go);
// 新しいヒエラルキーからAvatarを作る
UniHumanoid.AvatarDescription.AddAnimator(go, normalized, bMap, avatarDescription =>
{
var src = _src.GetComponent<Animator>();
var srcHumanBones = CachedEnum.GetValues<HumanBodyBones>()
.Where(x => x != HumanBodyBones.LastBone)
.Select(x => new { Key = x, Value = src.GetBoneTransform(x) })
.Where(x => x.Value != null)
;
var map =
srcHumanBones
.Where(x => boneMap.ContainsKey(x.Value))
.ToDictionary(x => x.Key, x => boneMap[x.Value])
;
if (dst.GetComponent<Animator>() == null)
{
var animator = dst.AddComponent<Animator>();
}
var vrmHuman = go.GetComponent<VRMHumanoidDescription>();
var avatarDescription = AvatarDescription.Create();
if (vrmHuman != null && vrmHuman.Description != null)
{
avatarDescription.armStretch = vrmHuman.Description.armStretch;
@@ -101,9 +83,6 @@ namespace VRM
avatarDescription.feetSpacing = vrmHuman.Description.feetSpacing;
avatarDescription.hasTranslationDoF = vrmHuman.Description.hasTranslationDoF;
}
avatarDescription.SetHumanBones(map);
var avatar = avatarDescription.CreateAvatar(dst.transform);
return avatar;
});
CopyVRMComponents(go, normalized, bMap);

View File

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

View File

@@ -0,0 +1,96 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
namespace UniVRM10
{
class MeshIntegrationTab
{
bool _modified = false;
Vrm10MeshUtility _meshUti;
Splitter _splitter;
ReorderableList _groupList;
ReorderableList _rendererList;
public List<Renderer> _renderers = new List<Renderer>();
int _selected = -1;
int Selected
{
set
{
if (_selected == value)
{
return;
}
if (value < 0 || value >= _meshUti.MeshIntegrationGroups.Count)
{
return;
}
_selected = value;
_renderers.Clear();
_renderers.AddRange(_meshUti.MeshIntegrationGroups[_selected].Renderers);
}
}
public MeshIntegrationTab(EditorWindow editor, Vrm10MeshUtility meshUtility)
{
_meshUti = meshUtility;
_splitter = new VerticalSplitter(editor, 50, 50);
_groupList = new ReorderableList(_meshUti.MeshIntegrationGroups, typeof(Vrm10MeshUtility.MeshIntegrationGroup));
_groupList.drawHeaderCallback = (Rect rect) =>
{
GUI.Label(rect, "Integration group");
};
_groupList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
{
var group = _meshUti.MeshIntegrationGroups[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;
_meshUti.MeshIntegrationGroups.Clear();
_meshUti.IntegrateFirstPerson(root);
Selected = 0;
}
private void ShowGroup(Rect rect)
{
_groupList.DoList(rect);
}
private void ShowSelected(Rect rect)
{
_rendererList.DoList(rect);
}
public bool OnGui(Rect rect)
{
_modified = false;
_splitter.OnGUI(
rect,
ShowGroup,
ShowSelected);
return _modified;
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 86cbd6470ebef2f4c976410efaf45638
guid: 07d031dea01a55c43b1ec68cd10bf461
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -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<Rect> mainDelegate, Action<Rect> 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,
};
}
}
}

View File

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

View File

@@ -0,0 +1,170 @@
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,
MeshSplitter,
}
Tabs _tab;
public static void OpenWindow()
{
var window =
(Vrm10MeshUtilityDialog)EditorWindow.GetWindow(typeof(Vrm10MeshUtilityDialog));
window.titleContent = new GUIContent(TITLE);
window.Show();
}
Vrm10MeshUtility _meshUtility = new Vrm10MeshUtility();
List<Validation> _validations = new List<Validation>();
private void Validate()
{
_validations.Clear();
if (_exportTarget == null)
{
_validations.Add(Validation.Error("set vrm1"));
return;
}
if (_exportTarget.GetComponent<Vrm10Instance>() == null)
{
_validations.Add(Validation.Error("target is not vrm1"));
return;
}
}
bool IsValid => !_validations.Any(v => !v.CanExport);
Vector2 _scrollPos;
GameObject _exportTarget;
MeshIntegrationTab _meshIntegration;
void OnEnable()
{
_meshIntegration = new MeshIntegrationTab(this, _meshUtility);
}
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;
}
case Tabs.MeshSplitter:
{
if (MeshSplitGui())
{
modified = true;
}
break;
}
}
EditorGUILayout.EndScrollView();
if (modified)
{
Validate();
}
GUI.enabled = IsValid;
var pressed = GUILayout.Button("Process", GUILayout.MinWidth(100));
GUI.enabled = true;
if (pressed)
{
_meshUtility.Process(exportTarget);
// Show Result ?
// Close();
// GUIUtility.ExitGUI();
}
}
bool ToggleIsModified(string label, ref bool value)
{
var newValue = EditorGUILayout.Toggle(label, value);
if (newValue == value)
{
return false;
}
value = newValue;
return true;
}
bool MeshBakeGui()
{
var blendShape = ToggleIsModified("BlendShape", ref _meshUtility.FreezeBlendShape);
var scale = ToggleIsModified("Scale", ref _meshUtility.FreezeScaling);
var rotation = ToggleIsModified("Rotation", ref _meshUtility.FreezeRotation);
return blendShape || scale || rotation;
}
bool MeshIntegrateGui()
{
var firstPerson = ToggleIsModified("FirstPerson == AUTO の生成", ref _meshUtility.GenerateMeshForFirstPersonAuto);
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
// process button の高さ
- 30
};
var mod = _meshIntegration.OnGui(rect);
return firstPerson || mod;
}
bool MeshSplitGui()
{
return ToggleIsModified("Separate by BlendShape", ref _meshUtility.SplitByBlendShape);
}
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,141 @@
using System.Collections.Generic;
using System.Linq;
using UniGLTF.MeshUtility;
using UniHumanoid;
using UnityEngine;
namespace UniVRM10
{
/// <summary>
/// - Freeze
/// - Integration
/// - Split
///
/// - Implement runtime logic => Process a hierarchy in scene. Do not process prefab.
/// - Implement undo
///
/// </summary>
public class Vrm10MeshUtility
{
/// <summary>
/// Same as VRM-0 normalization
/// - Mesh
/// - Node
/// - InverseBindMatrices
/// </summary>
public bool FreezeBlendShape = false;
/// <summary>
/// Same as VRM-0 normalization
/// - Mesh
/// - Node
/// - InverseBindMatrices
/// </summary>
public bool FreezeScaling = true;
/// <summary>
/// Same as VRM-0 normalization
/// - Mesh
/// - Node
/// - InverseBindMatrices
/// </summary>
public bool FreezeRotation = false;
public class MeshIntegrationGroup
{
/// <summary>
/// FirstPerson flag
/// TODO: enum
/// </summary>
public string Name;
public List<Renderer> Renderers = new List<Renderer>();
}
public List<MeshIntegrationGroup> MeshIntegrationGroups = new List<MeshIntegrationGroup>();
/// <summary>
/// Create a headless model and solve VRM.FirstPersonFlag.Auto
/// </summary>
public bool GenerateMeshForFirstPersonAuto = false;
/// <summary>
/// Split into having and not having BlendShape
/// </summary>
public bool SplitByBlendShape = false;
public void IntegrateAll(GameObject root)
{
if (root == null)
{
return;
}
MeshIntegrationGroups.Add(new MeshIntegrationGroup
{
Name = "ALL",
Renderers = root.GetComponentsInChildren<Renderer>().ToList(),
});
}
MeshIntegrationGroup GetOrCreateGroup(string name)
{
foreach (var g in MeshIntegrationGroups)
{
if (g.Name == name)
{
return g;
}
}
MeshIntegrationGroups.Add(new MeshIntegrationGroup
{
Name = name,
});
return MeshIntegrationGroups.Last();
}
public void IntegrateFirstPerson(GameObject root)
{
if (root == null)
{
return;
}
var vrm1 = root.GetComponent<Vrm10Instance>();
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));
}
}
public void Process(GameObject go)
{
// TODO: UNDO
// 正規化されたヒエラルキーを作る
var (normalized, boneMap) = BoneNormalizer.CreateNormalizedHierarchy(go,
removeScaling: FreezeScaling,
removeRotation: FreezeRotation);
// TODO: update: spring
// TODO: update: constraint
// TODO: update: firstPoint offset
AvatarDescription.AddAnimator(go, normalized, boneMap);
// TODO: write back normalized transform to boneMap
}
}
}

View File

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