mirror of
https://github.com/vrm-c/UniVRM.git
synced 2026-10-01 23:32:18 -05:00
Merge pull request #2177 from ousttrue/fix/impl_remove_scaling
[vrm1.0] impl remove scaling
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 04fa1873cf1c0374aab1670a24d741b5
|
||||
guid: 2021859c0d7255643bd93c95ab3fcf3d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace UniGLTF.MeshUtility
|
||||
{
|
||||
public enum MeshProcessDialogTabs
|
||||
{
|
||||
MeshSeparator,
|
||||
MeshIntegrator,
|
||||
BoneMeshEraser,
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 575d573f28f0e4a48a2a6509e69b7f6f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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();
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: da24ccbbed21eb94d902d42337df3699
|
||||
guid: 1bdcb928351f92047a91c17c3d629853
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -2,7 +2,7 @@ using UniGLTF.M17N;
|
||||
|
||||
namespace UniGLTF.MeshUtility
|
||||
{
|
||||
public enum MeshProcessingMessages
|
||||
public enum MeshUtilityMessages
|
||||
{
|
||||
[LangMsg(Languages.ja, "ターゲットオブジェクト")]
|
||||
[LangMsg(Languages.en, "TargetObject")]
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 829a678062065f04582189619babae79
|
||||
guid: abaf50b8dc367d4448cd5d7c99a4eea3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -5,7 +5,6 @@ using UnityEngine;
|
||||
|
||||
namespace UniGLTF.MeshUtility
|
||||
{
|
||||
|
||||
public static class TabBar
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -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行列を再計算する
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
8
Assets/VRM10/Editor/MeshUtility.meta
Normal file
8
Assets/VRM10/Editor/MeshUtility.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6fb2795d5ca5ce9449de94ea5fb94dde
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
96
Assets/VRM10/Editor/MeshUtility/MeshIntegrationTab.cs
Normal file
96
Assets/VRM10/Editor/MeshUtility/MeshIntegrationTab.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 86cbd6470ebef2f4c976410efaf45638
|
||||
guid: 07d031dea01a55c43b1ec68cd10bf461
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
183
Assets/VRM10/Editor/MeshUtility/Splitter.cs
Normal file
183
Assets/VRM10/Editor/MeshUtility/Splitter.cs
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/VRM10/Editor/MeshUtility/Splitter.cs.meta
Normal file
11
Assets/VRM10/Editor/MeshUtility/Splitter.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 385274edf555ed84a9f3706ca2a99023
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
170
Assets/VRM10/Editor/MeshUtility/Vrm10MeshUtilityDialog.cs
Normal file
170
Assets/VRM10/Editor/MeshUtility/Vrm10MeshUtilityDialog.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f3af8a1b265d8a4da7ad8c1c29d2585
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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();
|
||||
|
||||
|
||||
8
Assets/VRM10/Runtime/MeshUtility.meta
Normal file
8
Assets/VRM10/Runtime/MeshUtility.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4eeaac509504ff94e9743df73d3dae42
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
141
Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs
Normal file
141
Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs.meta
Normal file
11
Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0ef8f213cebb99f418c39baa9419296e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user