diff --git a/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserGUI.cs b/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserGUI.cs
deleted file mode 100644
index 897bfad0e..000000000
--- a/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserGUI.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using UnityEditor;
-using UnityEngine;
-
-namespace UniGLTF.MeshUtility
-{
- [CustomEditor(typeof(MeshProcessDialog), true)]
- public class BoneMeshEraserGUI : Editor
- {
- public override void OnInspectorGUI()
- {
- serializedObject.Update();
- var skinnedMesh = serializedObject.FindProperty("_cSkinnedMesh");
- EditorGUILayout.PropertyField(skinnedMesh, new GUIContent("Skinned Mesh"), true);
- var list = serializedObject.FindProperty("_eraseBones");
- EditorGUILayout.PropertyField(list, new GUIContent("Erase Bones"), true);
- serializedObject.ApplyModifiedProperties();
- }
- }
-}
diff --git a/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserWizard.cs b/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserWizard.cs
index a3650f74a..4e0c71201 100644
--- a/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserWizard.cs
+++ b/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserWizard.cs
@@ -138,6 +138,15 @@ namespace UniGLTF.MeshUtility
return erased;
}
+ public static UnityEngine.Object GetPrefab(GameObject instance)
+ {
+#if UNITY_2018_2_OR_NEWER
+ return PrefabUtility.GetCorrespondingObjectFromSource(instance);
+#else
+ return PrefabUtility.GetPrefabParent(go);
+#endif
+ }
+
void Erase()
{
var go = Selection.activeGameObject;
@@ -149,7 +158,7 @@ namespace UniGLTF.MeshUtility
// save mesh to Assets
var assetPath = string.Format("{0}{1}", go.name, ASSET_SUFFIX);
- var prefab = MeshUtility.GetPrefab(go);
+ var prefab = GetPrefab(go);
if (prefab != null)
{
var prefabPath = AssetDatabase.GetAssetPath(prefab);
diff --git a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialog.cs b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialog.cs
index d7c352b6c..a5a4c97e7 100644
--- a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialog.cs
+++ b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialog.cs
@@ -9,17 +9,12 @@ namespace UniGLTF.MeshUtility
{
public class MeshProcessDialog : EditorWindow
{
- enum Tabs
- {
- MeshSeparator,
- MeshIntegrator,
- // StaticMeshIntegrator,
- BoneMeshEraser,
- }
private Tabs _tab;
private GameObject _exportTarget;
- private Editor _boneMeshEraserEditor;
+
+ private MeshProcessDialogEditor _boneMeshEraserEditor;
+
private SkinnedMeshRenderer _pSkinnedMesh;
private Animator _pAnimator;
private Transform _pEraseRoot;
@@ -28,6 +23,9 @@ namespace UniGLTF.MeshUtility
[SerializeField]
private SkinnedMeshRenderer _cSkinnedMesh = null;
+ [SerializeField]
+ private bool _separateByBlendShape = true;
+
private Animator _cAnimator = null;
private Transform _cEraseRoot = null;
@@ -52,14 +50,19 @@ namespace UniGLTF.MeshUtility
{
if (!_boneMeshEraserEditor)
{
- _boneMeshEraserEditor = Editor.CreateEditor(this);
+ _boneMeshEraserEditor = (MeshProcessDialogEditor)Editor.CreateEditor(this);
}
}
+ static bool IsGameObjectSelected()
+ {
+ return Selection.activeObject != null && Selection.activeObject is GameObject;
+ }
+
private void OnGUI()
{
_scrollPos = EditorGUILayout.BeginScrollView(_scrollPos);
- EditorGUIUtility.labelWidth = 150;
+ EditorGUIUtility.labelWidth = 300;
// lang
LanguageGetter.OnGuiSelectLang();
@@ -68,7 +71,7 @@ namespace UniGLTF.MeshUtility
EditorGUILayout.LabelField(MeshProcessingMessages.TARGET_OBJECT.Msg(), GUILayout.MaxWidth(146.0f));
_exportTarget = (GameObject)EditorGUILayout.ObjectField(_exportTarget, typeof(GameObject), true);
EditorGUILayout.EndHorizontal();
- if (_exportTarget == null && MeshUtility.IsGameObjectSelected())
+ if (_exportTarget == null && IsGameObjectSelected())
{
_exportTarget = Selection.activeObject as GameObject;
}
@@ -86,17 +89,17 @@ namespace UniGLTF.MeshUtility
case Tabs.MeshIntegrator:
EditorGUILayout.HelpBox(MeshProcessingMessages.MESH_INTEGRATOR.Msg(), MessageType.Info);
- processed = TabMeshIntegrator.OnGUI(_exportTarget);
+ _boneMeshEraserEditor.Tabs = _tab;
+ if (_boneMeshEraserEditor)
+ {
+ _boneMeshEraserEditor.OnInspectorGUI();
+ }
+ processed = TabMeshIntegrator.OnGUI(_exportTarget, _separateByBlendShape);
break;
- // MeshIntegrator と機能が重複しているのと正常に動作しなかった
- // case Tabs.StaticMeshIntegrator:
- // EditorGUILayout.HelpBox(MeshProcessingMessages.STATIC_MESH_INTEGRATOR.Msg(), MessageType.Info);
- // processed = TabStaticMeshIntegrator.OnGUI(_exportTarget);
- // break;
-
case Tabs.BoneMeshEraser:
EditorGUILayout.HelpBox(MeshProcessingMessages.BONE_MESH_ERASER.Msg(), MessageType.Info);
+ _boneMeshEraserEditor.Tabs = _tab;
if (_boneMeshEraserEditor)
{
_boneMeshEraserEditor.OnInspectorGUI();
diff --git a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogEditor.cs b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogEditor.cs
new file mode 100644
index 000000000..db7929a0a
--- /dev/null
+++ b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogEditor.cs
@@ -0,0 +1,36 @@
+using UniGLTF.M17N;
+using UnityEditor;
+using UnityEngine;
+
+namespace UniGLTF.MeshUtility
+{
+ [CustomEditor(typeof(MeshProcessDialog), true)]
+ class MeshProcessDialogEditor : Editor
+ {
+ public Tabs Tabs;
+
+ public override void OnInspectorGUI()
+ {
+ serializedObject.Update();
+ switch (Tabs)
+ {
+ case Tabs.MeshIntegrator:
+ {
+ var skinnedMesh = serializedObject.FindProperty("_separateByBlendShape");
+ EditorGUILayout.PropertyField(skinnedMesh, new GUIContent(MeshProcessingMessages.MESH_SEPARATOR_BY_BLENDSHAPE.Msg()));
+ break;
+ }
+
+ case Tabs.BoneMeshEraser:
+ {
+ var skinnedMesh = serializedObject.FindProperty("_cSkinnedMesh");
+ EditorGUILayout.PropertyField(skinnedMesh, new GUIContent("Skinned Mesh"), true);
+ var list = serializedObject.FindProperty("_eraseBones");
+ EditorGUILayout.PropertyField(list, new GUIContent("Erase Bones"), true);
+ break;
+ }
+ }
+ serializedObject.ApplyModifiedProperties();
+ }
+ }
+}
diff --git a/Assets/UniGLTF/Editor/MeshUtility/TabStaticMeshIntegrator.cs.meta b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogEditor.cs.meta
similarity index 83%
rename from Assets/UniGLTF/Editor/MeshUtility/TabStaticMeshIntegrator.cs.meta
rename to Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogEditor.cs.meta
index 4c0e30384..de1678682 100644
--- a/Assets/UniGLTF/Editor/MeshUtility/TabStaticMeshIntegrator.cs.meta
+++ b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogEditor.cs.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: 9701e1f6aaed8d14283243a7a50be2df
+guid: 04fa1873cf1c0374aab1670a24d741b5
MonoImporter:
externalObjects: {}
serializedVersion: 2
diff --git a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessingMessages.cs b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessingMessages.cs
index 3fe4dcfdb..3b63cc538 100644
--- a/Assets/UniGLTF/Editor/MeshUtility/MeshProcessingMessages.cs
+++ b/Assets/UniGLTF/Editor/MeshUtility/MeshProcessingMessages.cs
@@ -20,17 +20,22 @@ namespace UniGLTF.MeshUtility
")]
MESH_SEPARATOR,
- [LangMsg(Languages.ja, @"ターゲットオブジェクト下の SkinnedMeshRenderer または MeshFilter にアタッチされたメッシュをを統合します。BlendShape の有無で2つ作成されます。
+
+ [LangMsg(Languages.ja, "ブレンドシェイプの有無で分割する")]
+ [LangMsg(Languages.en, "Divide by the presence or absence of `blendshape`")]
+ MESH_SEPARATOR_BY_BLENDSHAPE,
+
+ [LangMsg(Languages.ja, @"ターゲットオブジェクト下の SkinnedMeshRenderer または MeshFilter にアタッチされたメッシュを統合します。
* Asset: Assets/MeshIntegrated.mesh が作成されます(上書きされるので注意してください)。
* Scene: コピーされたヒエラルキーでは、統合された Mesh は除去されます。新しく MeshIntegrator ノードが追加されます。
-* VRMではBlendShapeClipの統合など追加の処理が必要でる。VRMの統合機能を使ってください。
+* VRMではBlendShapeClipの統合など追加の処理が必要です。`VRM0-MeshIntegratorWizard` を使ってください。
")]
- [LangMsg(Languages.en, @"Integrates the attached mesh into the SkinnedMeshRenderer or MeshFilter under the target object. A mesh that holds BlendShape and a mesh that does not hold BlendShape are created.
+ [LangMsg(Languages.en, @"Integrates the attached mesh into the SkinnedMeshRenderer or MeshFilter under the target object.
* Asset: Assets/MeshIntegrated.mesh is created (note that it will be overwritten).
* Scene: In the copied hierarchy, the integrated mesh is removed. A new MeshIntegrator node is added.
-* VRM requires additional processing such as BlendShapeClip integration. Use the VRM integration feature.
+* VRM requires additional processing such as BlendShapeClip integration. Use the `VRM0-MeshIntegratorWizard` integration feature.
")]
MESH_INTEGRATOR,
diff --git a/Assets/UniGLTF/Editor/MeshUtility/MeshUtility.cs b/Assets/UniGLTF/Editor/MeshUtility/MeshUtility.cs
deleted file mode 100644
index fd45864d8..000000000
--- a/Assets/UniGLTF/Editor/MeshUtility/MeshUtility.cs
+++ /dev/null
@@ -1,419 +0,0 @@
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using UnityEngine;
-using UnityEditor;
-
-namespace UniGLTF.MeshUtility
-{
- public class MeshUtility
- {
- public const string ASSET_SUFFIX = ".mesh.asset";
- private static readonly Vector3 ZERO_MOVEMENT = Vector3.zero;
-
- public static Object GetPrefab(GameObject instance)
- {
-#if UNITY_2018_2_OR_NEWER
- return PrefabUtility.GetCorrespondingObjectFromSource(instance);
-#else
- return PrefabUtility.GetPrefabParent(go);
-#endif
- }
-
- public enum BlendShapeLogic
- {
- WithBlendShape,
- WithoutBlendShape,
- }
-
- ///
- /// 対象のヒエラルキーに含まれるすべての SkinnedMeshRenderer に対して、
- /// BlendShape を含む Mesh と 含まない Mesh への分割を実施する。
- ///
- /// 各 SkinnedMeshRenderer(smr) は、
- ///
- /// smr - mesh(with blendshape)
- /// + smr(without) - mesh(without blendshape)
- ///
- /// のように変化する。
- ///
- ///
- /// (Mesh 分割前, Mesh BlendShape有り、Mesh BlendShape無し)のリストを返す
- public static List<(Mesh Src, Mesh With, Mesh Without)> SeparationProcessing(GameObject go)
- {
- var list = new List<(Mesh Src, Mesh With, Mesh Without)>();
- var skinnedMeshRenderers = go.GetComponentsInChildren();
- foreach (var skinnedMeshRenderer in skinnedMeshRenderers)
- {
- if (skinnedMeshRenderer.sharedMesh.blendShapeCount > 0)
- {
- var (mesh, with, without) = SeparatePolyWithBlendShape(skinnedMeshRenderer);
- if (mesh != null)
- {
- list.Add((mesh, with, without));
- }
- }
- }
- return list;
- }
-
- private static (Mesh mesh, Mesh With, Mesh Without) SeparatePolyWithBlendShape(SkinnedMeshRenderer skinnedMeshRendererInput)
- {
- var indicesUsedByBlendShape = new Dictionary();
- var mesh = skinnedMeshRendererInput.sharedMesh;
-
- // retrieve the original BlendShape data
- for (int i = 0; i < mesh.blendShapeCount; ++i)
- {
- var deltaVertices = new Vector3[mesh.vertexCount];
- var deltaNormals = new Vector3[mesh.vertexCount];
- var deltaTangents = new Vector3[mesh.vertexCount];
- mesh.GetBlendShapeFrameVertices(i, 0, deltaVertices, deltaNormals, deltaTangents);
-
- for (int j = 0; j < deltaVertices.Length; j++)
- {
- if (!deltaVertices[j].Equals(ZERO_MOVEMENT))
- {
- if (!indicesUsedByBlendShape.Values.Contains(j))
- {
- indicesUsedByBlendShape.Add(indicesUsedByBlendShape.Count, j);
- }
- }
- }
- }
-
- var subMeshCount = mesh.subMeshCount;
- var submeshesWithBlendShape = new Dictionary();
- var submeshesWithoutBlendShape = new Dictionary();
- var vertexIndexWithBlendShape = new Dictionary();
- var vertexCounterWithBlendShape = 0;
- var vertexIndexWithoutBlendShape = new Dictionary();
- var vertexCounterWithoutBlendShape = 0;
-
- // check blendshape's vertex index from submesh
- for (int i = 0; i < subMeshCount; i++)
- {
- var triangle = mesh.GetTriangles(i);
- var submeshWithBlendShape = new List();
- var submeshWithoutBlendShape = new List();
-
- for (int j = 0; j < triangle.Length; j += 3)
- {
- if (indicesUsedByBlendShape.Values.Contains(triangle[j]) ||
- indicesUsedByBlendShape.Values.Contains(triangle[j + 1]) ||
- indicesUsedByBlendShape.Values.Contains(triangle[j + 2]))
- {
- BuildNewTriangleList(vertexIndexWithBlendShape, triangle, j, submeshWithBlendShape, ref vertexCounterWithBlendShape);
- }
- else
- {
- BuildNewTriangleList(vertexIndexWithoutBlendShape, triangle, j, submeshWithoutBlendShape, ref vertexCounterWithoutBlendShape);
- }
- }
- if (submeshWithBlendShape.Count > 0)
- submeshesWithBlendShape.Add(i, submeshWithBlendShape.ToArray());
- if (submeshWithoutBlendShape.Count > 0)
- submeshesWithoutBlendShape.Add(i, submeshWithoutBlendShape.ToArray()); ;
- }
-
- // check if any BlendShape exists
- if (submeshesWithoutBlendShape.Count > 0)
- {
- // put the mesh without BlendShape in a new SkinnedMeshRenderer
- var srcGameObject = skinnedMeshRendererInput.gameObject;
- var srcTransform = skinnedMeshRendererInput.transform.parent;
- var targetObjectForMeshWithoutBS = GameObject.Instantiate(srcGameObject);
- targetObjectForMeshWithoutBS.name = srcGameObject.name + "_WithoutBlendShape";
- targetObjectForMeshWithoutBS.transform.SetParent(srcTransform);
- var skinnedMeshRendererWithoutBS = targetObjectForMeshWithoutBS.GetComponent();
-
- // build meshes with/without BlendShape
- var with = BuildNewMesh(skinnedMeshRendererInput, vertexIndexWithBlendShape, submeshesWithBlendShape, BlendShapeLogic.WithBlendShape);
- var without = BuildNewMesh(skinnedMeshRendererWithoutBS, vertexIndexWithoutBlendShape, submeshesWithoutBlendShape, BlendShapeLogic.WithoutBlendShape);
- return (mesh, with, without);
- }
- else
- {
- return default;
- }
- }
-
- private static void BuildNewTriangleList(Dictionary newVerticesListLookUp, int[] triangleList, int index,
- List newTriangleList, ref int vertexCounter)
- {
- // build new vertex list and triangle list
- // vertex 1
- if (!newVerticesListLookUp.Keys.Contains(triangleList[index]))
- {
- newVerticesListLookUp.Add(triangleList[index], vertexCounter);
- newTriangleList.Add(vertexCounter);
- vertexCounter++;
- }
- else
- {
- var newVertexIndex = newVerticesListLookUp[triangleList[index]];
- newTriangleList.Add(newVertexIndex);
- }
- // vertex 2
- if (!newVerticesListLookUp.Keys.Contains(triangleList[index + 1]))
- {
- newVerticesListLookUp.Add(triangleList[index + 1], vertexCounter);
- newTriangleList.Add(vertexCounter);
- vertexCounter++;
- }
- else
- {
- var newVertexIndex = newVerticesListLookUp[triangleList[index + 1]];
- newTriangleList.Add(newVertexIndex);
- }
- // vertex 3
- if (!newVerticesListLookUp.Keys.Contains(triangleList[index + 2]))
- {
- newVerticesListLookUp.Add(triangleList[index + 2], vertexCounter);
- newTriangleList.Add(vertexCounter);
- vertexCounter++;
- }
- else
- {
- var newVertexIndex = newVerticesListLookUp[triangleList[index + 2]];
- newTriangleList.Add(newVertexIndex);
- }
- }
-
- private static Mesh BuildNewMesh(SkinnedMeshRenderer skinnedMeshRenderer, Dictionary newIndexLookUpDict,
- Dictionary subMeshes, BlendShapeLogic blendShapeLabel)
- {
- // get original mesh data
- var materialList = new List();
- skinnedMeshRenderer.GetSharedMaterials(materialList);
- var mesh = skinnedMeshRenderer.sharedMesh;
- var meshVertices = mesh.vertices;
- var meshNormals = mesh.normals;
- var meshTangents = mesh.tangents;
- var meshColors = mesh.colors;
- var meshBoneWeights = mesh.boneWeights;
- var meshUVs = mesh.uv;
-
- // build new mesh
- var materialListNew = new List();
- var newMesh = new Mesh();
-
- if (mesh.vertexCount > ushort.MaxValue)
- {
-#if UNITY_2017_3_OR_NEWER
- Debug.LogFormat("exceed 65535 vertices: {0}", mesh.vertexCount);
- newMesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
-#else
- throw new NotImplementedException(String.Format("exceed 65535 vertices: {0}", integrator.Positions.Count.ToString()));
-#endif
- }
-
- var newDataLength = newIndexLookUpDict.Count;
- var newIndexLookUp = newIndexLookUpDict.Keys.ToArray();
-
- newMesh.vertices = newIndexLookUp.Select(x => meshVertices[x]).ToArray();
- if (meshNormals.Length > 0) newMesh.normals = newIndexLookUp.Select(x => meshNormals[x]).ToArray();
- if (meshTangents.Length > 0) newMesh.tangents = newIndexLookUp.Select(x => meshTangents[x]).ToArray();
- if (meshColors.Length > 0) newMesh.colors = newIndexLookUp.Select(x => meshColors[x]).ToArray();
- if (meshBoneWeights.Length > 0) newMesh.boneWeights = newIndexLookUp.Select(x => meshBoneWeights[x]).ToArray();
- if (meshUVs.Length > 0) newMesh.uv = newIndexLookUp.Select(x => meshUVs[x]).ToArray();
- newMesh.bindposes = mesh.bindposes;
-
- // add BlendShape data
- if (blendShapeLabel == BlendShapeLogic.WithBlendShape)
- {
- for (int i = 0; i < mesh.blendShapeCount; i++)
- {
- // get original BlendShape data
- var srcVertices = new Vector3[mesh.vertexCount];
- var srcNormals = new Vector3[mesh.vertexCount];
- var srcTangents = new Vector3[mesh.vertexCount];
- mesh.GetBlendShapeFrameVertices(i, 0, srcVertices, srcNormals, srcTangents);
-
- // declare the size for the destination array
- var dstVertices = new Vector3[newDataLength];
- var dstNormals = new Vector3[newDataLength];
- var dstTangents = new Vector3[newDataLength];
-
- dstVertices = newIndexLookUp.Select(x => srcVertices[x]).ToArray();
- dstNormals = newIndexLookUp.Select(x => srcNormals[x]).ToArray();
- dstTangents = newIndexLookUp.Select(x => srcTangents[x]).ToArray();
- newMesh.AddBlendShapeFrame(mesh.GetBlendShapeName(i), mesh.GetBlendShapeFrameWeight(i, 0),
- dstVertices, dstNormals, dstTangents);
- }
- }
-
- newMesh.subMeshCount = subMeshes.Count;
- var cosMaterialIndex = subMeshes.Keys.ToArray();
-
- // build material list
- for (int i = 0; i < subMeshes.Count; i++)
- {
- newMesh.SetTriangles(subMeshes[cosMaterialIndex[i]], i);
- materialListNew.Add(materialList[cosMaterialIndex[i]]);
- }
- skinnedMeshRenderer.sharedMaterials = materialListNew.ToArray();
- skinnedMeshRenderer.sharedMesh = newMesh;
-
- return newMesh;
- }
-
-#if UNITY_EDITOR
- public static bool IsGameObjectSelected()
- {
- return Selection.activeObject != null && Selection.activeObject is GameObject;
- }
-
- ///
- /// from dialog
- ///
- ///
- public static void IntegrateSelected(GameObject go)
- {
- var meshWithMaterials = StaticMeshIntegrator.Integrate(go.transform);
-
- // save as asset
- var assetPath = "";
-#if UNITY_2018_2_OR_NEWER
- var prefab = PrefabUtility.GetCorrespondingObjectFromSource(go);
-#else
- var prefab = PrefabUtility.GetPrefabParent(go);
-#endif
- if (prefab != null)
- {
- var prefabPath = AssetDatabase.GetAssetPath(prefab);
- assetPath = string.Format("{0}/{1}_{2}{3}",
- Path.GetDirectoryName(prefabPath),
- Path.GetFileNameWithoutExtension(prefabPath),
- go.name,
- ASSET_SUFFIX
- );
- }
- else
- {
- var path = EditorUtility.SaveFilePanel(
- "Save mesh",
- "Assets",
- go.name + ".asset",
- "asset");
- if (string.IsNullOrEmpty(path))
- {
- return;
- }
- assetPath = UnityPath.FromFullpath(path).Value;
- }
-
- assetPath = AssetDatabase.GenerateUniqueAssetPath(assetPath);
- Debug.LogFormat("CreateAsset: {0}", assetPath);
- AssetDatabase.CreateAsset(meshWithMaterials.Mesh, assetPath);
-
- // add component
- var meshObject = new GameObject(go.name + ".static_meshes_integrated");
- if (go.transform.parent != null)
- {
- meshObject.transform.SetParent(go.transform.parent, false);
- }
- meshObject.transform.localPosition = go.transform.localPosition;
- meshObject.transform.localRotation = go.transform.localRotation;
- meshObject.transform.localScale = go.transform.localScale;
-
- var filter = meshObject.AddComponent();
- filter.sharedMesh = meshWithMaterials.Mesh;
- var renderer = meshObject.AddComponent();
- renderer.sharedMaterials = meshWithMaterials.Materials;
- }
-#endif
-
- ///
- /// from dialog
- ///
- ///
- public static void MeshIntegrator(GameObject go)
- {
- MeshIntegratorUtility.Integrate(go, onlyBlendShapeRenderers: true);
- MeshIntegratorUtility.Integrate(go, onlyBlendShapeRenderers: false);
-
- var outputObject = GameObject.Instantiate(go);
- outputObject.name = outputObject.name + "_mesh_integration";
- var skinnedMeshes = outputObject.GetComponentsInChildren();
- var normalMeshes = outputObject.GetComponentsInChildren();
-
- // destroy integrated meshes in the source
- // ?
- foreach (var skinnedMesh in go.GetComponentsInChildren())
- {
- if (skinnedMesh.sharedMesh.name == MeshIntegratorUtility.INTEGRATED_MESH_NAME ||
- skinnedMesh.sharedMesh.name == MeshIntegratorUtility.INTEGRATED_MESH_BLENDSHAPE_NAME)
- {
- GameObject.DestroyImmediate(skinnedMesh.gameObject);
- }
- }
- foreach (var skinnedMesh in skinnedMeshes)
- {
- // destroy original meshes in the copied GameObject
- if (!(skinnedMesh.sharedMesh.name == MeshIntegratorUtility.INTEGRATED_MESH_NAME ||
- skinnedMesh.sharedMesh.name == MeshIntegratorUtility.INTEGRATED_MESH_BLENDSHAPE_NAME))
- {
- GameObject.DestroyImmediate(skinnedMesh);
- }
- // check if the integrated mesh is empty
- else if (skinnedMesh.sharedMesh.subMeshCount == 0)
- {
- GameObject.DestroyImmediate(skinnedMesh.gameObject);
- }
- // save mesh data
- else if (skinnedMesh.sharedMesh.name == MeshIntegratorUtility.INTEGRATED_MESH_NAME ||
- skinnedMesh.sharedMesh.name == MeshIntegratorUtility.INTEGRATED_MESH_BLENDSHAPE_NAME)
- {
- SaveMeshData(skinnedMesh.sharedMesh);
- }
- }
- foreach (var normalMesh in normalMeshes)
- {
- if (normalMesh.sharedMesh.name != MeshIntegratorUtility.INTEGRATED_MESH_NAME)
- {
- if (normalMesh.gameObject.GetComponent())
- {
- GameObject.DestroyImmediate(normalMesh.gameObject.GetComponent());
- }
- GameObject.DestroyImmediate(normalMesh);
- }
- }
- }
-
- public static void SaveMesh(Mesh mesh, Mesh newMesh, BlendShapeLogic blendShapeLabel)
- {
- // save mesh as asset
- var assetPath = string.Format("{0}{1}", Path.GetFileNameWithoutExtension(mesh.name), ASSET_SUFFIX);
- Debug.Log(assetPath);
- if (!string.IsNullOrEmpty((AssetDatabase.GetAssetPath(mesh))))
- {
- var directory = Path.GetDirectoryName(AssetDatabase.GetAssetPath(mesh)).Replace("\\", "/");
- assetPath = string.Format("{0}/{1}{2}", directory, Path.GetFileNameWithoutExtension(mesh.name) + "_" + blendShapeLabel.ToString(), ASSET_SUFFIX);
- }
- else
- {
- assetPath = string.Format("Assets/{0}{1}", Path.GetFileNameWithoutExtension(mesh.name) + "_" + blendShapeLabel.ToString(), ASSET_SUFFIX);
- }
- Debug.LogFormat("CreateAsset: {0}", assetPath);
- AssetDatabase.CreateAsset(newMesh, assetPath);
- }
-
- private static void SaveMeshData(Mesh mesh)
- {
- var assetPath = string.Format("{0}{1}", Path.GetFileNameWithoutExtension(mesh.name), ASSET_SUFFIX);
- Debug.Log(assetPath);
- if (!string.IsNullOrEmpty((AssetDatabase.GetAssetPath(mesh))))
- {
- var directory = Path.GetDirectoryName(AssetDatabase.GetAssetPath(mesh)).Replace("\\", "/");
- assetPath = string.Format("{0}/{1}{2}", directory, Path.GetFileNameWithoutExtension(mesh.name), ASSET_SUFFIX);
- }
- else
- {
- assetPath = string.Format("Assets/{0}{1}", Path.GetFileNameWithoutExtension(mesh.name), ASSET_SUFFIX);
- }
- Debug.LogFormat("CreateAsset: {0}", assetPath);
- AssetDatabase.CreateAsset(mesh, assetPath);
- }
- }
-}
\ No newline at end of file
diff --git a/Assets/UniGLTF/Editor/MeshUtility/TabBoneMeshRemover.cs b/Assets/UniGLTF/Editor/MeshUtility/TabBoneMeshRemover.cs
index c0dc39b59..812fd8a52 100644
--- a/Assets/UniGLTF/Editor/MeshUtility/TabBoneMeshRemover.cs
+++ b/Assets/UniGLTF/Editor/MeshUtility/TabBoneMeshRemover.cs
@@ -9,7 +9,9 @@ namespace UniGLTF.MeshUtility
{
public static class TabBoneMeshRemover
{
- public static bool OnGUI(GameObject _exportTarget, SkinnedMeshRenderer _cSkinnedMesh, BoneMeshEraser.EraseBone[] _eraseBones)
+ const string ASSET_SUFFIX = ".mesh.asset";
+
+ public static bool OnGUI(GameObject root, SkinnedMeshRenderer smr, BoneMeshEraser.EraseBone[] eraseBones)
{
var _isInvokeSuccess = false;
GUILayout.BeginVertical();
@@ -18,7 +20,7 @@ namespace UniGLTF.MeshUtility
GUILayout.FlexibleSpace();
if (GUILayout.Button("Process", GUILayout.MinWidth(100)))
{
- _isInvokeSuccess = TabBoneMeshRemover.Execute(_exportTarget, _cSkinnedMesh, _eraseBones);
+ _isInvokeSuccess = TabBoneMeshRemover.Execute(root, smr, eraseBones);
}
GUILayout.EndHorizontal();
}
@@ -26,86 +28,79 @@ namespace UniGLTF.MeshUtility
return _isInvokeSuccess;
}
- public static bool Execute(GameObject _exportTarget, SkinnedMeshRenderer _cSkinnedMesh, BoneMeshEraser.EraseBone[] _eraseBones)
+ private static bool Execute(GameObject root, SkinnedMeshRenderer smr, BoneMeshEraser.EraseBone[] eraseBones)
{
- if (_exportTarget == null)
+ if (root == null)
{
EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_GAMEOBJECT_SELECTED.Msg(), "ok");
return false;
}
- var go = _exportTarget;
- if (_cSkinnedMesh == null)
+ if (smr == null)
{
EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.SELECT_SKINNED_MESH.Msg(), "ok");
return false;
}
- BoneMeshRemove(go, _cSkinnedMesh, _eraseBones);
- return true;
- }
+ var bones = smr.bones;
+
+ var meshNode = new GameObject(BoneMeshEraserWizard.BONE_MESH_ERASER_NAME);
+ meshNode.transform.SetParent(root.transform, false);
+
+ var erased = meshNode.AddComponent();
+ erased.sharedMesh = BoneMeshEraser.CreateErasedMesh(smr.sharedMesh, eraseBones
+ .Where(x => x.Erase)
+ .Select(x => Array.IndexOf(bones, x.Bone))
+ .ToArray());
+ erased.sharedMaterials = smr.sharedMaterials;
+ erased.bones = smr.bones;
- private static void BoneMeshRemove(GameObject go, SkinnedMeshRenderer _cSkinnedMesh, BoneMeshEraser.EraseBone[] _eraseBones)
- {
- var renderer = Remove(go, _cSkinnedMesh, _eraseBones);
- var outputObject = GameObject.Instantiate(go);
- outputObject.name = outputObject.name + "_bone_mesh_erase";
- if (renderer == null)
- {
- return;
- }
// save mesh to Assets
- var assetPath = string.Format("{0}{1}", go.name, MeshUtility.ASSET_SUFFIX);
- var prefab = MeshUtility.GetPrefab(go);
+ var assetPath = string.Format("{0}{1}", root.name, ASSET_SUFFIX);
+ var prefab = GetPrefab(root);
if (prefab != null)
{
var prefabPath = AssetDatabase.GetAssetPath(prefab);
assetPath = string.Format("{0}/{1}{2}",
Path.GetDirectoryName(prefabPath),
Path.GetFileNameWithoutExtension(prefabPath),
- MeshUtility.ASSET_SUFFIX
+ ASSET_SUFFIX
);
}
-
Debug.LogFormat("CreateAsset: {0}", assetPath);
- AssetDatabase.CreateAsset(renderer.sharedMesh, assetPath);
+ AssetDatabase.CreateAsset(erased.sharedMesh, assetPath);
// destroy BoneMeshEraser in the source
- foreach (var skinnedMesh in go.GetComponentsInChildren())
+ foreach (var skinnedMesh in root.GetComponentsInChildren())
{
if (skinnedMesh.gameObject.name == BoneMeshEraserWizard.BONE_MESH_ERASER_NAME)
{
GameObject.DestroyImmediate(skinnedMesh.gameObject);
}
}
+
// destroy the original mesh in the copied GameObject
+ var outputObject = GameObject.Instantiate(root);
+ outputObject.name = outputObject.name + "_bone_mesh_erase";
foreach (var skinnedMesh in outputObject.GetComponentsInChildren())
{
- if (skinnedMesh.sharedMesh == _cSkinnedMesh.sharedMesh)
+ if (skinnedMesh.sharedMesh == smr.sharedMesh)
{
GameObject.DestroyImmediate(skinnedMesh);
}
}
+
+ return true;
}
- private static SkinnedMeshRenderer Remove(GameObject go, SkinnedMeshRenderer _cSkinnedMesh, BoneMeshEraser.EraseBone[] _eraseBones)
+ public static UnityEngine.Object GetPrefab(GameObject instance)
{
- var bones = _cSkinnedMesh.bones;
- var eraseBones = _eraseBones
- .Where(x => x.Erase)
- .Select(x => Array.IndexOf(bones, x.Bone))
- .ToArray();
-
- var meshNode = new GameObject(BoneMeshEraserWizard.BONE_MESH_ERASER_NAME);
- meshNode.transform.SetParent(go.transform, false);
-
- var erased = meshNode.AddComponent();
- erased.sharedMesh = BoneMeshEraser.CreateErasedMesh(_cSkinnedMesh.sharedMesh, eraseBones);
- erased.sharedMaterials = _cSkinnedMesh.sharedMaterials;
- erased.bones = _cSkinnedMesh.bones;
-
- return erased;
+#if UNITY_2018_2_OR_NEWER
+ return PrefabUtility.GetCorrespondingObjectFromSource(instance);
+#else
+ return PrefabUtility.GetPrefabParent(go);
+#endif
}
}
}
\ No newline at end of file
diff --git a/Assets/UniGLTF/Editor/MeshUtility/TabMeshIntegrator.cs b/Assets/UniGLTF/Editor/MeshUtility/TabMeshIntegrator.cs
index 839d425a8..ed366f531 100644
--- a/Assets/UniGLTF/Editor/MeshUtility/TabMeshIntegrator.cs
+++ b/Assets/UniGLTF/Editor/MeshUtility/TabMeshIntegrator.cs
@@ -1,3 +1,4 @@
+using System.IO;
using UniGLTF.M17N;
using UnityEditor;
using UnityEngine;
@@ -6,7 +7,9 @@ namespace UniGLTF.MeshUtility
{
public static class TabMeshIntegrator
{
- public static bool OnGUI(GameObject _exportTarget)
+ const string ASSET_SUFFIX = ".mesh.asset";
+
+ public static bool OnGUI(GameObject root, bool onlyBlendShapeRenderers)
{
var _isInvokeSuccess = false;
GUILayout.BeginVertical();
@@ -15,7 +18,7 @@ namespace UniGLTF.MeshUtility
GUILayout.FlexibleSpace();
if (GUILayout.Button("Process", GUILayout.MinWidth(100)))
{
- _isInvokeSuccess = TabMeshIntegrator.Execute(_exportTarget);
+ _isInvokeSuccess = TabMeshIntegrator.Execute(root, onlyBlendShapeRenderers);
}
GUILayout.EndHorizontal();
}
@@ -23,10 +26,10 @@ namespace UniGLTF.MeshUtility
return _isInvokeSuccess;
}
- static string VRM_META = "VRMMeta";
- static bool HasVrm(GameObject go)
+ const string VRM_META = "VRMMeta";
+ static bool HasVrm(GameObject root)
{
- var allComponents = go.GetComponents(typeof(Component));
+ var allComponents = root.GetComponents(typeof(Component));
foreach (var component in allComponents)
{
if (component == null) continue;
@@ -39,29 +42,107 @@ namespace UniGLTF.MeshUtility
return false;
}
- static bool Execute(GameObject _exportTarget)
+ static bool Execute(GameObject root, bool onlyBlendShapeRenderers)
{
- if (_exportTarget == null)
+ if (root == null)
{
EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_GAMEOBJECT_SELECTED.Msg(), "ok");
return false;
}
- var go = _exportTarget;
- if (HasVrm(go))
+ if (HasVrm(root))
{
EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.VRM_DETECTED.Msg(), "ok");
return false;
}
- if (go.GetComponentsInChildren().Length == 0 && go.GetComponentsInChildren().Length == 0)
+ if (root.GetComponentsInChildren().Length == 0 && root.GetComponentsInChildren().Length == 0)
{
EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_MESH.Msg(), "ok");
return false;
}
- MeshUtility.MeshIntegrator(go);
+ if (onlyBlendShapeRenderers)
+ {
+ MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithBlendShape);
+ MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithoutBlendShape);
+ }
+ else
+ {
+ MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.All);
+ }
+
+ CopyAndSaveAssetEtc(root);
+
return true;
}
+
+ static void CopyAndSaveAssetEtc(GameObject root)
+ {
+ // copy hierarchy
+ var outputObject = GameObject.Instantiate(root);
+ outputObject.name = outputObject.name + "_mesh_integration";
+ var skinnedMeshes = outputObject.GetComponentsInChildren();
+
+ // destroy integrated meshes in the source
+ foreach (var skinnedMesh in root.GetComponentsInChildren())
+ {
+ if (skinnedMesh.sharedMesh.name == MeshIntegratorUtility.INTEGRATED_MESH_NAME ||
+ skinnedMesh.sharedMesh.name == MeshIntegratorUtility.INTEGRATED_MESH_BLENDSHAPE_NAME)
+ {
+ GameObject.DestroyImmediate(skinnedMesh.gameObject);
+ }
+ }
+ foreach (var skinnedMesh in skinnedMeshes)
+ {
+ // destroy original meshes in the copied GameObject
+ if (!(skinnedMesh.sharedMesh.name == MeshIntegratorUtility.INTEGRATED_MESH_NAME ||
+ skinnedMesh.sharedMesh.name == MeshIntegratorUtility.INTEGRATED_MESH_BLENDSHAPE_NAME))
+ {
+ GameObject.DestroyImmediate(skinnedMesh);
+ }
+ // check if the integrated mesh is empty
+ else if (skinnedMesh.sharedMesh.subMeshCount == 0)
+ {
+ GameObject.DestroyImmediate(skinnedMesh.gameObject);
+ }
+ // save mesh data
+ else if (skinnedMesh.sharedMesh.name == MeshIntegratorUtility.INTEGRATED_MESH_NAME ||
+ skinnedMesh.sharedMesh.name == MeshIntegratorUtility.INTEGRATED_MESH_BLENDSHAPE_NAME)
+ {
+ SaveMeshData(skinnedMesh.sharedMesh);
+ }
+ }
+
+ var normalMeshes = outputObject.GetComponentsInChildren();
+ foreach (var normalMesh in normalMeshes)
+ {
+ if (normalMesh.sharedMesh.name != MeshIntegratorUtility.INTEGRATED_MESH_NAME)
+ {
+ if (normalMesh.gameObject.GetComponent())
+ {
+ GameObject.DestroyImmediate(normalMesh.gameObject.GetComponent());
+ }
+ GameObject.DestroyImmediate(normalMesh);
+ }
+ }
+ }
+
+ static void SaveMeshData(Mesh mesh)
+ {
+ var assetPath = string.Format("{0}{1}", Path.GetFileNameWithoutExtension(mesh.name), ASSET_SUFFIX);
+ Debug.Log(assetPath);
+ if (!string.IsNullOrEmpty((AssetDatabase.GetAssetPath(mesh))))
+ {
+ var directory = Path.GetDirectoryName(AssetDatabase.GetAssetPath(mesh)).Replace("\\", "/");
+ assetPath = string.Format("{0}/{1}{2}", directory, Path.GetFileNameWithoutExtension(mesh.name), ASSET_SUFFIX);
+ }
+ else
+ {
+ assetPath = string.Format("Assets/{0}{1}", Path.GetFileNameWithoutExtension(mesh.name), ASSET_SUFFIX);
+ }
+ Debug.LogFormat("CreateAsset: {0}", assetPath);
+ AssetDatabase.CreateAsset(mesh, assetPath);
+ }
}
}
diff --git a/Assets/UniGLTF/Editor/MeshUtility/TabMeshSeparator.cs b/Assets/UniGLTF/Editor/MeshUtility/TabMeshSeparator.cs
index bef557520..6e8caf398 100644
--- a/Assets/UniGLTF/Editor/MeshUtility/TabMeshSeparator.cs
+++ b/Assets/UniGLTF/Editor/MeshUtility/TabMeshSeparator.cs
@@ -1,3 +1,6 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
using UniGLTF.M17N;
using UnityEditor;
using UnityEngine;
@@ -9,7 +12,16 @@ namespace UniGLTF.MeshUtility
///
public static class TabMeshSeparator
{
- public static bool OnGUI(GameObject _exportTarget)
+ private static readonly Vector3 ZERO_MOVEMENT = Vector3.zero;
+ const string ASSET_SUFFIX = ".mesh.asset";
+
+ enum BlendShapeLogic
+ {
+ WithBlendShape,
+ WithoutBlendShape,
+ }
+
+ public static bool OnGUI(GameObject root)
{
var _isInvokeSuccess = false;
GUILayout.BeginVertical();
@@ -18,7 +30,7 @@ namespace UniGLTF.MeshUtility
GUILayout.FlexibleSpace();
if (GUILayout.Button("Process", GUILayout.MinWidth(100)))
{
- _isInvokeSuccess = TabMeshSeparator.Execute(_exportTarget);
+ _isInvokeSuccess = TabMeshSeparator.Execute(root);
}
GUILayout.EndHorizontal();
}
@@ -26,34 +38,283 @@ namespace UniGLTF.MeshUtility
return _isInvokeSuccess;
}
- static bool Execute(GameObject _exportTarget)
+ static bool Execute(GameObject root)
{
- if (_exportTarget == null)
+ if (root == null)
{
EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_GAMEOBJECT_SELECTED.Msg(), "ok");
return false;
}
- var go = _exportTarget;
- if (go.GetComponentsInChildren().Length == 0)
+ if (root.GetComponentsInChildren().Length == 0)
{
EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_SKINNED_MESH.Msg(), "ok");
return false;
}
// copy
- var outputObject = GameObject.Instantiate(go);
+ var outputObject = GameObject.Instantiate(root);
outputObject.name = outputObject.name + "_mesh_separation";
// 改変と asset の作成
- var list = MeshUtility.SeparationProcessing(outputObject);
+ var list = SeparationProcessing(outputObject);
foreach (var (src, with, without) in list)
{
// asset の永続化
- MeshUtility.SaveMesh(src, with, MeshUtility.BlendShapeLogic.WithBlendShape);
- MeshUtility.SaveMesh(src, without, MeshUtility.BlendShapeLogic.WithoutBlendShape);
+ SaveMesh(src, with, BlendShapeLogic.WithBlendShape);
+ SaveMesh(src, without, BlendShapeLogic.WithoutBlendShape);
}
return true;
}
+
+ static void SaveMesh(Mesh mesh, Mesh newMesh, BlendShapeLogic blendShapeLabel)
+ {
+ // save mesh as asset
+ var assetPath = string.Format("{0}{1}", Path.GetFileNameWithoutExtension(mesh.name), ASSET_SUFFIX);
+ Debug.Log(assetPath);
+ if (!string.IsNullOrEmpty((AssetDatabase.GetAssetPath(mesh))))
+ {
+ var directory = Path.GetDirectoryName(AssetDatabase.GetAssetPath(mesh)).Replace("\\", "/");
+ assetPath = string.Format("{0}/{1}{2}", directory, Path.GetFileNameWithoutExtension(mesh.name) + "_" + blendShapeLabel.ToString(), ASSET_SUFFIX);
+ }
+ else
+ {
+ assetPath = string.Format("Assets/{0}{1}", Path.GetFileNameWithoutExtension(mesh.name) + "_" + blendShapeLabel.ToString(), ASSET_SUFFIX);
+ }
+ Debug.LogFormat("CreateAsset: {0}", assetPath);
+ AssetDatabase.CreateAsset(newMesh, assetPath);
+ }
+
+ ///
+ /// 対象のヒエラルキーに含まれるすべての SkinnedMeshRenderer に対して、
+ /// BlendShape を含む Mesh と 含まない Mesh への分割を実施する。
+ ///
+ /// 各 SkinnedMeshRenderer(smr) は、
+ ///
+ /// smr - mesh(with blendshape)
+ /// + smr(without) - mesh(without blendshape)
+ ///
+ /// のように変化する。
+ ///
+ ///
+ /// (Mesh 分割前, Mesh BlendShape有り、Mesh BlendShape無し)のリストを返す
+ public static List<(Mesh Src, Mesh With, Mesh Without)> SeparationProcessing(GameObject go)
+ {
+ var list = new List<(Mesh Src, Mesh With, Mesh Without)>();
+ var skinnedMeshRenderers = go.GetComponentsInChildren();
+ foreach (var skinnedMeshRenderer in skinnedMeshRenderers)
+ {
+ if (skinnedMeshRenderer.sharedMesh.blendShapeCount > 0)
+ {
+ var (mesh, with, without) = SeparatePolyWithBlendShape(skinnedMeshRenderer);
+ if (mesh != null)
+ {
+ list.Add((mesh, with, without));
+ }
+ }
+ }
+ return list;
+ }
+
+ private static (Mesh mesh, Mesh With, Mesh Without) SeparatePolyWithBlendShape(SkinnedMeshRenderer skinnedMeshRendererInput)
+ {
+ var indicesUsedByBlendShape = new Dictionary();
+ var mesh = skinnedMeshRendererInput.sharedMesh;
+
+ // retrieve the original BlendShape data
+ for (int i = 0; i < mesh.blendShapeCount; ++i)
+ {
+ var deltaVertices = new Vector3[mesh.vertexCount];
+ var deltaNormals = new Vector3[mesh.vertexCount];
+ var deltaTangents = new Vector3[mesh.vertexCount];
+ mesh.GetBlendShapeFrameVertices(i, 0, deltaVertices, deltaNormals, deltaTangents);
+
+ for (int j = 0; j < deltaVertices.Length; j++)
+ {
+ if (!deltaVertices[j].Equals(ZERO_MOVEMENT))
+ {
+ if (!indicesUsedByBlendShape.Values.Contains(j))
+ {
+ indicesUsedByBlendShape.Add(indicesUsedByBlendShape.Count, j);
+ }
+ }
+ }
+ }
+
+ var subMeshCount = mesh.subMeshCount;
+ var submeshesWithBlendShape = new Dictionary();
+ var submeshesWithoutBlendShape = new Dictionary();
+ var vertexIndexWithBlendShape = new Dictionary();
+ var vertexCounterWithBlendShape = 0;
+ var vertexIndexWithoutBlendShape = new Dictionary();
+ var vertexCounterWithoutBlendShape = 0;
+
+ // check blendshape's vertex index from submesh
+ for (int i = 0; i < subMeshCount; i++)
+ {
+ var triangle = mesh.GetTriangles(i);
+ var submeshWithBlendShape = new List();
+ var submeshWithoutBlendShape = new List();
+
+ for (int j = 0; j < triangle.Length; j += 3)
+ {
+ if (indicesUsedByBlendShape.Values.Contains(triangle[j]) ||
+ indicesUsedByBlendShape.Values.Contains(triangle[j + 1]) ||
+ indicesUsedByBlendShape.Values.Contains(triangle[j + 2]))
+ {
+ BuildNewTriangleList(vertexIndexWithBlendShape, triangle, j, submeshWithBlendShape, ref vertexCounterWithBlendShape);
+ }
+ else
+ {
+ BuildNewTriangleList(vertexIndexWithoutBlendShape, triangle, j, submeshWithoutBlendShape, ref vertexCounterWithoutBlendShape);
+ }
+ }
+ if (submeshWithBlendShape.Count > 0)
+ submeshesWithBlendShape.Add(i, submeshWithBlendShape.ToArray());
+ if (submeshWithoutBlendShape.Count > 0)
+ submeshesWithoutBlendShape.Add(i, submeshWithoutBlendShape.ToArray()); ;
+ }
+
+ // check if any BlendShape exists
+ if (submeshesWithoutBlendShape.Count > 0)
+ {
+ // put the mesh without BlendShape in a new SkinnedMeshRenderer
+ var srcGameObject = skinnedMeshRendererInput.gameObject;
+ var srcTransform = skinnedMeshRendererInput.transform.parent;
+ var targetObjectForMeshWithoutBS = GameObject.Instantiate(srcGameObject);
+ targetObjectForMeshWithoutBS.name = srcGameObject.name + "_WithoutBlendShape";
+ targetObjectForMeshWithoutBS.transform.SetParent(srcTransform);
+ var skinnedMeshRendererWithoutBS = targetObjectForMeshWithoutBS.GetComponent();
+
+ // build meshes with/without BlendShape
+ var with = BuildNewMesh(skinnedMeshRendererInput, vertexIndexWithBlendShape, submeshesWithBlendShape, BlendShapeLogic.WithBlendShape);
+ var without = BuildNewMesh(skinnedMeshRendererWithoutBS, vertexIndexWithoutBlendShape, submeshesWithoutBlendShape, BlendShapeLogic.WithoutBlendShape);
+ return (mesh, with, without);
+ }
+ else
+ {
+ return default;
+ }
+ }
+
+ private static void BuildNewTriangleList(Dictionary newVerticesListLookUp, int[] triangleList, int index,
+ List newTriangleList, ref int vertexCounter)
+ {
+ // build new vertex list and triangle list
+ // vertex 1
+ if (!newVerticesListLookUp.Keys.Contains(triangleList[index]))
+ {
+ newVerticesListLookUp.Add(triangleList[index], vertexCounter);
+ newTriangleList.Add(vertexCounter);
+ vertexCounter++;
+ }
+ else
+ {
+ var newVertexIndex = newVerticesListLookUp[triangleList[index]];
+ newTriangleList.Add(newVertexIndex);
+ }
+ // vertex 2
+ if (!newVerticesListLookUp.Keys.Contains(triangleList[index + 1]))
+ {
+ newVerticesListLookUp.Add(triangleList[index + 1], vertexCounter);
+ newTriangleList.Add(vertexCounter);
+ vertexCounter++;
+ }
+ else
+ {
+ var newVertexIndex = newVerticesListLookUp[triangleList[index + 1]];
+ newTriangleList.Add(newVertexIndex);
+ }
+ // vertex 3
+ if (!newVerticesListLookUp.Keys.Contains(triangleList[index + 2]))
+ {
+ newVerticesListLookUp.Add(triangleList[index + 2], vertexCounter);
+ newTriangleList.Add(vertexCounter);
+ vertexCounter++;
+ }
+ else
+ {
+ var newVertexIndex = newVerticesListLookUp[triangleList[index + 2]];
+ newTriangleList.Add(newVertexIndex);
+ }
+ }
+
+ private static Mesh BuildNewMesh(SkinnedMeshRenderer skinnedMeshRenderer, Dictionary newIndexLookUpDict,
+ Dictionary subMeshes, BlendShapeLogic blendShapeLabel)
+ {
+ // get original mesh data
+ var materialList = new List();
+ skinnedMeshRenderer.GetSharedMaterials(materialList);
+ var mesh = skinnedMeshRenderer.sharedMesh;
+ var meshVertices = mesh.vertices;
+ var meshNormals = mesh.normals;
+ var meshTangents = mesh.tangents;
+ var meshColors = mesh.colors;
+ var meshBoneWeights = mesh.boneWeights;
+ var meshUVs = mesh.uv;
+
+ // build new mesh
+ var materialListNew = new List();
+ var newMesh = new Mesh();
+
+ if (mesh.vertexCount > ushort.MaxValue)
+ {
+#if UNITY_2017_3_OR_NEWER
+ Debug.LogFormat("exceed 65535 vertices: {0}", mesh.vertexCount);
+ newMesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
+#else
+ throw new NotImplementedException(String.Format("exceed 65535 vertices: {0}", integrator.Positions.Count.ToString()));
+#endif
+ }
+
+ var newDataLength = newIndexLookUpDict.Count;
+ var newIndexLookUp = newIndexLookUpDict.Keys.ToArray();
+
+ newMesh.vertices = newIndexLookUp.Select(x => meshVertices[x]).ToArray();
+ if (meshNormals.Length > 0) newMesh.normals = newIndexLookUp.Select(x => meshNormals[x]).ToArray();
+ if (meshTangents.Length > 0) newMesh.tangents = newIndexLookUp.Select(x => meshTangents[x]).ToArray();
+ if (meshColors.Length > 0) newMesh.colors = newIndexLookUp.Select(x => meshColors[x]).ToArray();
+ if (meshBoneWeights.Length > 0) newMesh.boneWeights = newIndexLookUp.Select(x => meshBoneWeights[x]).ToArray();
+ if (meshUVs.Length > 0) newMesh.uv = newIndexLookUp.Select(x => meshUVs[x]).ToArray();
+ newMesh.bindposes = mesh.bindposes;
+
+ // add BlendShape data
+ if (blendShapeLabel == BlendShapeLogic.WithBlendShape)
+ {
+ for (int i = 0; i < mesh.blendShapeCount; i++)
+ {
+ // get original BlendShape data
+ var srcVertices = new Vector3[mesh.vertexCount];
+ var srcNormals = new Vector3[mesh.vertexCount];
+ var srcTangents = new Vector3[mesh.vertexCount];
+ mesh.GetBlendShapeFrameVertices(i, 0, srcVertices, srcNormals, srcTangents);
+
+ // declare the size for the destination array
+ var dstVertices = new Vector3[newDataLength];
+ var dstNormals = new Vector3[newDataLength];
+ var dstTangents = new Vector3[newDataLength];
+
+ dstVertices = newIndexLookUp.Select(x => srcVertices[x]).ToArray();
+ dstNormals = newIndexLookUp.Select(x => srcNormals[x]).ToArray();
+ dstTangents = newIndexLookUp.Select(x => srcTangents[x]).ToArray();
+ newMesh.AddBlendShapeFrame(mesh.GetBlendShapeName(i), mesh.GetBlendShapeFrameWeight(i, 0),
+ dstVertices, dstNormals, dstTangents);
+ }
+ }
+
+ newMesh.subMeshCount = subMeshes.Count;
+ var cosMaterialIndex = subMeshes.Keys.ToArray();
+
+ // build material list
+ for (int i = 0; i < subMeshes.Count; i++)
+ {
+ newMesh.SetTriangles(subMeshes[cosMaterialIndex[i]], i);
+ materialListNew.Add(materialList[cosMaterialIndex[i]]);
+ }
+ skinnedMeshRenderer.sharedMaterials = materialListNew.ToArray();
+ skinnedMeshRenderer.sharedMesh = newMesh;
+
+ return newMesh;
+ }
}
}
diff --git a/Assets/UniGLTF/Editor/MeshUtility/TabStaticMeshIntegrator.cs b/Assets/UniGLTF/Editor/MeshUtility/TabStaticMeshIntegrator.cs
deleted file mode 100644
index 9cdfd3e3f..000000000
--- a/Assets/UniGLTF/Editor/MeshUtility/TabStaticMeshIntegrator.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-using UniGLTF.M17N;
-using UnityEditor;
-using UnityEngine;
-
-namespace UniGLTF.MeshUtility
-{
- public static class TabStaticMeshIntegrator
- {
- public static bool OnGUI(GameObject _exportTarget)
- {
- var _isInvokeSuccess = false;
- GUILayout.BeginVertical();
- {
- GUILayout.BeginHorizontal();
- GUILayout.FlexibleSpace();
- if (GUILayout.Button("Process", GUILayout.MinWidth(100)))
- {
- _isInvokeSuccess = TabStaticMeshIntegrator.Execute(_exportTarget);
- }
- GUILayout.EndHorizontal();
- }
- GUILayout.EndVertical();
- return _isInvokeSuccess;
- }
-
- static bool Execute(GameObject _exportTarget)
- {
- if (_exportTarget == null)
- {
- EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_GAMEOBJECT_SELECTED.Msg(), "ok");
- return false;
- }
- var go = _exportTarget;
-
- if (go.GetComponentsInChildren().Length == 0)
- {
- EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_STATIC_MESH.Msg(), "ok");
- return false;
- }
-
- MeshUtility.IntegrateSelected(go);
- return true;
- }
- }
-}
diff --git a/Assets/UniGLTF/Editor/MeshUtility/Tabs.cs b/Assets/UniGLTF/Editor/MeshUtility/Tabs.cs
new file mode 100644
index 000000000..dbc064734
--- /dev/null
+++ b/Assets/UniGLTF/Editor/MeshUtility/Tabs.cs
@@ -0,0 +1,9 @@
+namespace UniGLTF.MeshUtility
+{
+ enum Tabs
+ {
+ MeshSeparator,
+ MeshIntegrator,
+ BoneMeshEraser,
+ }
+}
diff --git a/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserGUI.cs.meta b/Assets/UniGLTF/Editor/MeshUtility/Tabs.cs.meta
similarity index 83%
rename from Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserGUI.cs.meta
rename to Assets/UniGLTF/Editor/MeshUtility/Tabs.cs.meta
index eddf8f87d..485c04f70 100644
--- a/Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserGUI.cs.meta
+++ b/Assets/UniGLTF/Editor/MeshUtility/Tabs.cs.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: 5cdbec3de528c61448aeb739ffe1f79e
+guid: 57ace5e28eb8e7746b00eaa9405197a9
MonoImporter:
externalObjects: {}
serializedVersion: 2
diff --git a/Assets/UniGLTF/Runtime/MeshUtility/MeshEnumerateOption.cs b/Assets/UniGLTF/Runtime/MeshUtility/MeshEnumerateOption.cs
new file mode 100644
index 000000000..a4021e5a7
--- /dev/null
+++ b/Assets/UniGLTF/Runtime/MeshUtility/MeshEnumerateOption.cs
@@ -0,0 +1,9 @@
+namespace UniGLTF.MeshUtility
+{
+ public enum MeshEnumerateOption
+ {
+ OnlyWithBlendShape,
+ OnlyWithoutBlendShape,
+ All,
+ }
+}
diff --git a/Assets/UniGLTF/Editor/MeshUtility/MeshUtility.cs.meta b/Assets/UniGLTF/Runtime/MeshUtility/MeshEnumerateOption.cs.meta
similarity index 83%
rename from Assets/UniGLTF/Editor/MeshUtility/MeshUtility.cs.meta
rename to Assets/UniGLTF/Runtime/MeshUtility/MeshEnumerateOption.cs.meta
index 2fb0aadd1..566aaa57a 100644
--- a/Assets/UniGLTF/Editor/MeshUtility/MeshUtility.cs.meta
+++ b/Assets/UniGLTF/Runtime/MeshUtility/MeshEnumerateOption.cs.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: c1bbe850b95e44740bbbb44064e17d25
+guid: 862f202d0be779744a4954a77cb88a89
MonoImporter:
externalObjects: {}
serializedVersion: 2
diff --git a/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrator.cs b/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrator.cs
index 569fa4bdf..4e2db9cdb 100644
--- a/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrator.cs
+++ b/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrator.cs
@@ -230,7 +230,7 @@ namespace UniGLTF.MeshUtility
}
}
- public void Intgrate(bool onlyBlendShapeRenderers)
+ public void Intgrate(MeshEnumerateOption onlyBlendShapeRenderers)
{
var mesh = new Mesh();
@@ -252,24 +252,36 @@ namespace UniGLTF.MeshUtility
}
mesh.bindposes = BindPoses.ToArray();
- if (onlyBlendShapeRenderers)
+ switch (onlyBlendShapeRenderers)
{
- AddBlendShapesToMesh(mesh);
- mesh.name = INTEGRATED_MESH_BLENDSHAPE_NAME;
- }
- else
- {
- mesh.name = INTEGRATED_MESH_NAME;
+ case MeshEnumerateOption.OnlyWithBlendShape:
+ {
+ AddBlendShapesToMesh(mesh);
+ mesh.name = INTEGRATED_MESH_BLENDSHAPE_NAME;
+ break;
+ }
+ case MeshEnumerateOption.OnlyWithoutBlendShape:
+ case MeshEnumerateOption.All:
+ {
+ mesh.name = INTEGRATED_MESH_NAME;
+ break;
+ }
}
var meshNode = new GameObject();
- if (onlyBlendShapeRenderers)
+ switch (onlyBlendShapeRenderers)
{
- meshNode.name = "MeshIntegrator(BlendShape)";
- }
- else
- {
- meshNode.name = "MeshIntegrator";
+ case MeshEnumerateOption.OnlyWithBlendShape:
+ {
+ meshNode.name = "MeshIntegrator(BlendShape)";
+ break;
+ }
+ case MeshEnumerateOption.OnlyWithoutBlendShape:
+ case MeshEnumerateOption.All:
+ {
+ meshNode.name = "MeshIntegrator";
+ break;
+ }
}
var integrated = meshNode.AddComponent();
diff --git a/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegratorUtility.cs b/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegratorUtility.cs
index 48bffe5fe..87f44f189 100644
--- a/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegratorUtility.cs
+++ b/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegratorUtility.cs
@@ -8,48 +8,83 @@ namespace UniGLTF.MeshUtility
public static string INTEGRATED_MESH_NAME => MeshIntegrator.INTEGRATED_MESH_NAME;
public static string INTEGRATED_MESH_BLENDSHAPE_NAME => MeshIntegrator.INTEGRATED_MESH_BLENDSHAPE_NAME;
+
///
/// go を root としたヒエラルキーから Renderer を集めて、統合された Mesh 作成する
///
///
- /// BlendShapeを保持するSkinnedMeshRendererのみ/BlendShapeを保持しないSkinnedMeshRenderer + MeshRenderer
+ ///
+ /// true: BlendShapeを保持するSkinnedMeshRendererのみ
+ /// false: BlendShapeを保持しないSkinnedMeshRenderer + MeshRenderer
+ /// null: すべてのSkinnedMeshRenderer + MeshRenderer
+ ///
///
- public static MeshIntegrationResult Integrate(GameObject go, bool onlyBlendShapeRenderers, IEnumerable excludes = null)
+ public static MeshIntegrationResult Integrate(GameObject go, MeshEnumerateOption onlyBlendShapeRenderers, IEnumerable excludes = null)
{
var exclude = new MeshExclude(excludes);
var integrator = new MeshUtility.MeshIntegrator();
- if (onlyBlendShapeRenderers)
+ switch (onlyBlendShapeRenderers)
{
- foreach (var x in EnumerateSkinnedMeshRenderer(go.transform, true))
- {
- if (exclude.IsExcluded(x))
+ case MeshEnumerateOption.OnlyWithBlendShape:
{
- continue;
+ foreach (var x in EnumerateSkinnedMeshRenderer(go.transform, onlyBlendShapeRenderers))
+ {
+ if (exclude.IsExcluded(x))
+ {
+ continue;
+ }
+ integrator.Push(x);
+ }
+ break;
}
- integrator.Push(x);
- }
- }
- else
- {
- foreach (var x in EnumerateSkinnedMeshRenderer(go.transform, false))
- {
- if (exclude.IsExcluded(x))
- {
- continue;
- }
- integrator.Push(x);
- }
- foreach (var x in EnumerateMeshRenderer(go.transform))
- {
- if (exclude.IsExcluded(x))
+ case MeshEnumerateOption.OnlyWithoutBlendShape:
{
- continue;
+ foreach (var x in EnumerateSkinnedMeshRenderer(go.transform, onlyBlendShapeRenderers))
+ {
+ if (exclude.IsExcluded(x))
+ {
+ continue;
+ }
+ integrator.Push(x);
+ }
+
+ foreach (var x in EnumerateMeshRenderer(go.transform))
+ {
+ if (exclude.IsExcluded(x))
+ {
+ continue;
+ }
+ integrator.Push(x);
+ }
+
+ break;
+ }
+
+ case MeshEnumerateOption.All:
+ {
+ foreach (var x in EnumerateSkinnedMeshRenderer(go.transform, onlyBlendShapeRenderers))
+ {
+ if (exclude.IsExcluded(x))
+ {
+ continue;
+ }
+ integrator.Push(x);
+ }
+
+ foreach (var x in EnumerateMeshRenderer(go.transform))
+ {
+ if (exclude.IsExcluded(x))
+ {
+ continue;
+ }
+ integrator.Push(x);
+ }
+
+ break;
}
- integrator.Push(x);
- }
}
integrator.Intgrate(onlyBlendShapeRenderers);
@@ -57,7 +92,7 @@ namespace UniGLTF.MeshUtility
return integrator.Result;
}
- public static IEnumerable EnumerateSkinnedMeshRenderer(Transform root, bool hasBlendShape)
+ public static IEnumerable EnumerateSkinnedMeshRenderer(Transform root, MeshEnumerateOption hasBlendShape)
{
foreach (var x in Traverse(root))
{
@@ -65,10 +100,30 @@ namespace UniGLTF.MeshUtility
if (renderer != null &&
renderer.gameObject.activeInHierarchy &&
renderer.sharedMesh != null &&
- renderer.enabled &&
- renderer.sharedMesh.blendShapeCount > 0 == hasBlendShape)
+ renderer.enabled)
{
- yield return renderer;
+ switch (hasBlendShape)
+ {
+ case MeshEnumerateOption.OnlyWithBlendShape:
+ if (renderer.sharedMesh.blendShapeCount > 0)
+ {
+ yield return renderer;
+ }
+ break;
+
+ case MeshEnumerateOption.OnlyWithoutBlendShape:
+ if (renderer.sharedMesh.blendShapeCount == 0)
+ {
+ yield return renderer;
+ }
+ break;
+
+ case MeshEnumerateOption.All:
+ {
+ yield return renderer;
+ break;
+ }
+ }
}
}
}
diff --git a/Assets/VRM/Editor/SkinnedMeshUtility/MeshIntegratorEditor.cs b/Assets/VRM/Editor/SkinnedMeshUtility/MeshIntegratorEditor.cs
deleted file mode 100644
index 793972b4c..000000000
--- a/Assets/VRM/Editor/SkinnedMeshUtility/MeshIntegratorEditor.cs
+++ /dev/null
@@ -1,146 +0,0 @@
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using UniGLTF;
-using UniGLTF.MeshUtility;
-using UnityEditor;
-using UnityEngine;
-using Object = UnityEngine.Object;
-
-
-namespace VRM
-{
- ///
- /// 複数のメッシュを統合する。
- /// VRM の場合、BlendShapeClip の調整が必用なのでこれも実行する。
- ///
- [DisallowMultipleComponent]
- public static class MeshIntegratorEditor
- {
- const string ASSET_SUFFIX = ".mesh.asset";
-
- public static bool IntegrateValidation()
- {
- return Selection.activeObject != null &&
- Selection.activeObject is GameObject &&
- SkinnedMeshUtility.IsPrefab(Selection.activeObject);
- }
-
- public static List Integrate(GameObject prefab, UniGLTF.UnityPath writeAssetPath, IEnumerable excludes)
- {
- Undo.RecordObject(prefab, "Mesh Integration");
- var instance = SkinnedMeshUtility.InstantiatePrefab(prefab);
-
- var clips = new List();
- var proxy = instance.GetComponent();
- if (proxy != null && proxy.BlendShapeAvatar != null)
- {
- clips = proxy.BlendShapeAvatar.Clips;
- }
- foreach (var clip in clips)
- {
- Undo.RecordObject(clip, "Mesh Integration");
- }
-
- // Backup Exists
- BackupVrmPrefab(prefab);
-
- // Execute
- var results = VRMMeshIntegratorUtility.Integrate(instance, clips, excludes);
-
- // disable source renderer
- foreach (var res in results)
- {
- foreach (var renderer in res.SourceSkinnedMeshRenderers)
- {
- Undo.RecordObject(renderer.gameObject, "Deactivate old renderer");
- renderer.gameObject.SetActive(false);
- }
-
- foreach (var renderer in res.SourceMeshRenderers)
- {
- Undo.RecordObject(renderer.gameObject, "Deactivate old renderer");
- renderer.gameObject.SetActive(false);
- }
- }
-
- foreach (var result in results)
- {
- if (result.IntegratedRenderer == null) continue;
-
- var assetPath = writeAssetPath.Parent.Child($"{result.IntegratedRenderer.gameObject.name}{ASSET_SUFFIX}");
- Debug.LogFormat("CreateAsset: {0}", assetPath);
- assetPath.CreateAsset(result.IntegratedRenderer.sharedMesh);
- Undo.RegisterCreatedObjectUndo(result.IntegratedRenderer.gameObject, "Integrate Renderers");
- }
-
- // Apply to Prefab
- var prefabPath = UniGLTF.UnityPath.FromUnityPath(AssetDatabase.GetAssetPath(prefab));
- if (prefabPath.Equals(writeAssetPath))
- {
- SkinnedMeshUtility.ApplyChangesToPrefab(instance);
- }
- else
- {
- PrefabUtility.SaveAsPrefabAsset(instance, writeAssetPath.Value, out bool success);
- if (!success)
- {
- throw new System.Exception($"PrefabUtility.SaveAsPrefabAsset: {writeAssetPath}");
- }
- }
-
- // destroy source renderers
- Object.DestroyImmediate(instance);
-
- return results;
- }
-
- private static void BackupVrmPrefab(GameObject rootPrefab)
- {
- var proxy = rootPrefab.GetComponent();
-
- var srcAvatar = proxy.BlendShapeAvatar;
- var dstAvatar = (BlendShapeAvatar)BackupAsset(srcAvatar, rootPrefab);
-
- var clipMapper = srcAvatar.Clips.ToDictionary(x => x, x => (BlendShapeClip)BackupAsset(x, rootPrefab));
- dstAvatar.Clips = clipMapper.Values.ToList();
-
- var dstPrefab = BackupAsset(rootPrefab, rootPrefab);
- var dstInstance = SkinnedMeshUtility.InstantiatePrefab(dstPrefab);
- dstInstance.GetComponent().BlendShapeAvatar = dstAvatar;
- SkinnedMeshUtility.ApplyChangesToPrefab(dstInstance);
- Object.DestroyImmediate(dstInstance);
- }
-
- private static T BackupAsset(T asset, GameObject rootPrefab) where T : UnityEngine.Object
- {
- var srcAssetPath = UnityPath.FromAsset(asset);
- var assetName = srcAssetPath.FileName;
-
- var backupDir = "MeshIntegratorBackup";
- var backupPath = UnityPath.FromAsset(rootPrefab).Parent.Child(backupDir);
- backupPath.EnsureFolder();
- var dstAssetPath = backupPath.Child(assetName);
-
- AssetDatabase.CopyAsset(srcAssetPath.Value, dstAssetPath.Value);
- return dstAssetPath.LoadAsset();
- }
-
- private static string GetRootPrefabPath(GameObject go)
- {
- var prefab = SkinnedMeshUtility.IsPrefab(go) ? go : SkinnedMeshUtility.GetPrefab(go);
- var assetPath = "";
- if (prefab != null)
- {
- var prefabPath = AssetDatabase.GetAssetPath(prefab);
- assetPath = string.Format("{0}/", Path.GetDirectoryName(prefabPath));
- }
- else
- {
- assetPath = string.Format("Assets/");
- }
- assetPath = assetPath.Replace(@"\", @"/");
- return assetPath;
- }
- }
-}
diff --git a/Assets/VRM/Editor/SkinnedMeshUtility/MeshIntegratorWizard.cs.meta b/Assets/VRM/Editor/SkinnedMeshUtility/MeshIntegratorWizard.cs.meta
deleted file mode 100644
index 19c7bdf94..000000000
--- a/Assets/VRM/Editor/SkinnedMeshUtility/MeshIntegratorWizard.cs.meta
+++ /dev/null
@@ -1,13 +0,0 @@
-fileFormatVersion: 2
-guid: 744f66c7013807b4cae1d38d2ecfff38
-timeCreated: 1518503829
-licenseType: Free
-MonoImporter:
- externalObjects: {}
- serializedVersion: 2
- defaultReferences: []
- executionOrder: 0
- icon: {instanceID: 0}
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/Assets/VRM/Editor/SkinnedMeshUtility/SkinnedMeshUtility.cs b/Assets/VRM/Editor/SkinnedMeshUtility/SkinnedMeshUtility.cs
deleted file mode 100644
index 20c05a315..000000000
--- a/Assets/VRM/Editor/SkinnedMeshUtility/SkinnedMeshUtility.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-using System;
-using UnityEditor;
-using UnityEngine;
-using Object = UnityEngine.Object;
-
-namespace VRM
-{
- public static class SkinnedMeshUtility
- {
- public const string MENU_KEY = "GameObject/UnityEditorScripts/";
- public const int MENU_PRIORITY = 11;
-
- public static Object GetPrefab(GameObject instance)
- {
-#if UNITY_2018_2_OR_NEWER
- return PrefabUtility.GetCorrespondingObjectFromSource(instance);
-#else
- return PrefabUtility.GetPrefabParent(go);
-#endif
- }
-
- public static bool IsPrefab(Object instance)
- {
- if (instance == null)
- {
- return false;
- }
- if (PrefabUtility.GetPrefabAssetType(instance) != PrefabAssetType.Regular)
- {
- return false;
- }
- return true;
- }
-
- public static void ApplyChangesToPrefab(GameObject instance)
- {
- var prefab = GetPrefab(instance);
- if (prefab == null)
- {
- return;
- }
-
- var path = AssetDatabase.GetAssetPath(prefab);
- if (string.IsNullOrEmpty(path))
- {
- return;
- }
-
- PrefabUtility.SaveAsPrefabAssetAndConnect(instance, path, InteractionMode.AutomatedAction);
- }
-
- public static GameObject InstantiatePrefab(GameObject prefab)
- {
- if (!IsPrefab(prefab)) return null;
-
- return (GameObject)PrefabUtility.InstantiatePrefab(prefab);
- }
- }
-}
\ No newline at end of file
diff --git a/Assets/VRM/Editor/SkinnedMeshUtility/SkinnedMeshUtility.cs.meta b/Assets/VRM/Editor/SkinnedMeshUtility/SkinnedMeshUtility.cs.meta
deleted file mode 100644
index 6cda243d5..000000000
--- a/Assets/VRM/Editor/SkinnedMeshUtility/SkinnedMeshUtility.cs.meta
+++ /dev/null
@@ -1,3 +0,0 @@
-fileFormatVersion: 2
-guid: d66a91f4b51e42a6b165fe39fdb48d64
-timeCreated: 1559541677
\ No newline at end of file
diff --git a/Assets/VRM/Editor/SkinnedMeshUtility/MeshIntegratorWizard.cs b/Assets/VRM/Editor/SkinnedMeshUtility/VrmMeshIntegratorWizard.cs
similarity index 57%
rename from Assets/VRM/Editor/SkinnedMeshUtility/MeshIntegratorWizard.cs
rename to Assets/VRM/Editor/SkinnedMeshUtility/VrmMeshIntegratorWizard.cs
index 9a1df032a..3f991627b 100644
--- a/Assets/VRM/Editor/SkinnedMeshUtility/MeshIntegratorWizard.cs
+++ b/Assets/VRM/Editor/SkinnedMeshUtility/VrmMeshIntegratorWizard.cs
@@ -6,14 +6,34 @@ using System;
using System.Collections.Generic;
using UniGLTF.MeshUtility;
using System.IO;
+using UniGLTF.M17N;
namespace VRM
{
- public class MeshIntegratorWizard : ScriptableWizard
+ public class VrmMeshIntegratorWizard : ScriptableWizard
{
+ const string ASSET_SUFFIX = ".mesh.asset";
+
+ enum HelpMessage
+ {
+ Ready,
+ SetTarget,
+ InvalidTarget,
+ }
+
+ enum ValidationError
+ {
+ None,
+ NoTarget,
+ HasParent,
+ }
+
[SerializeField]
GameObject m_root;
+ [SerializeField]
+ bool m_separateByBlendShape = true;
+
[Header("Validation")]
[SerializeField]
Material[] m_uniqueMaterials;
@@ -74,11 +94,12 @@ namespace VRM
public static void CreateWizard()
{
- ScriptableWizard.DisplayWizard("MeshIntegratorWizard", "Integrate and close window", "Integrate");
+ ScriptableWizard.DisplayWizard("MeshIntegratorWizard", "Integrate and close window", "Integrate");
}
private void OnEnable()
{
+ Clear(HelpMessage.Ready, ValidationError.None);
m_root = Selection.activeGameObject;
OnValidate();
}
@@ -125,20 +146,33 @@ namespace VRM
return key;
}
+ void Clear(HelpMessage help, ValidationError error)
+ {
+ helpString = help.Msg();
+ errorString = error != ValidationError.None ? error.Msg() : null;
+ m_uniqueMaterials = new Material[] { };
+ m_duplicateMaterials = new MaterialList[] { };
+ m_excludes.Clear();
+ isValid = false;
+ }
+
void OnValidate()
{
- if (m_root == null
- || !PrefabUtility.IsPartOfAnyPrefab(m_root) || m_root.transform.parent != null)
+ if (m_root == null)
{
- Debug.LogWarning("Invalidate");
- m_uniqueMaterials = new Material[] { };
- m_duplicateMaterials = new MaterialList[] { };
- m_excludes.Clear();
+ Clear(HelpMessage.SetTarget, ValidationError.NoTarget);
return;
}
- Debug.Log("OnValidate");
- m_uniqueMaterials = MeshIntegratorUtility.EnumerateSkinnedMeshRenderer(m_root.transform, false)
+ if (m_root.transform.parent != null)
+ {
+ Clear(HelpMessage.InvalidTarget, ValidationError.HasParent);
+ return;
+ }
+
+ Clear(HelpMessage.Ready, ValidationError.None);
+ isValid = true;
+ m_uniqueMaterials = MeshIntegratorUtility.EnumerateSkinnedMeshRenderer(m_root.transform, MeshEnumerateOption.OnlyWithoutBlendShape)
.SelectMany(x => x.sharedMaterials)
.Distinct()
.ToArray();
@@ -180,18 +214,13 @@ namespace VRM
void OnWizardUpdate()
{
- helpString = "select target mesh root";
+ // helpString = "Set target gameobject `in scene`. Prefab not supported.";
}
void Integrate()
{
- if (m_root == null)
- {
- Debug.LogWarning("no root object");
- return;
- }
-
var prefabPath = AssetDatabase.GetAssetPath(m_root);
+ Debug.Log(prefabPath);
var path = EditorUtility.SaveFilePanel("save prefab", Path.GetDirectoryName(prefabPath), m_root.name, "prefab");
if (string.IsNullOrEmpty(path))
{
@@ -206,20 +235,81 @@ namespace VRM
}
var excludes = m_excludes.Where(x => x.Exclude).Select(x => x.Mesh);
- integrationResults = MeshIntegratorEditor.Integrate(m_root, assetPath, excludes).Select(x => x.MeshMap).ToArray();
+
+ // Backup Exists
+ VrmPrefabUtility.BackupVrmPrefab(m_root);
+
+ Undo.RecordObject(m_root, "Mesh Integration");
+ var instance = VrmPrefabUtility.InstantiatePrefab(m_root);
+
+ var clips = new List();
+ var proxy = instance.GetComponent();
+ if (proxy != null && proxy.BlendShapeAvatar != null)
+ {
+ clips = proxy.BlendShapeAvatar.Clips;
+ }
+ foreach (var clip in clips)
+ {
+ Undo.RecordObject(clip, "Mesh Integration");
+ }
+
+ // Execute
+ var results = VRMMeshIntegratorUtility.Integrate(instance, clips, excludes, m_separateByBlendShape);
+ // integrationResults = MeshIntegratorEditor.Integrate(m_root, assetPath, excludes, m_separateByBlendShape).Select(x => x.MeshMap).ToArray();
+ // public static List Integrate(GameObject prefab, UniGLTF.UnityPath writeAssetPath, IEnumerable excludes, bool separateByBlendShape)
+
+ // disable source renderer
+ foreach (var res in results)
+ {
+ foreach (var renderer in res.SourceSkinnedMeshRenderers)
+ {
+ Undo.RecordObject(renderer.gameObject, "Deactivate old renderer");
+ renderer.gameObject.SetActive(false);
+ }
+
+ foreach (var renderer in res.SourceMeshRenderers)
+ {
+ Undo.RecordObject(renderer.gameObject, "Deactivate old renderer");
+ renderer.gameObject.SetActive(false);
+ }
+ }
+
+ foreach (var result in results)
+ {
+ if (result.IntegratedRenderer == null) continue;
+
+ var childAssetPath = assetPath.Parent.Child($"{result.IntegratedRenderer.gameObject.name}{ASSET_SUFFIX}");
+ Debug.LogFormat("CreateAsset: {0}", childAssetPath);
+ childAssetPath.CreateAsset(result.IntegratedRenderer.sharedMesh);
+ Undo.RegisterCreatedObjectUndo(result.IntegratedRenderer.gameObject, "Integrate Renderers");
+ }
+
+ // Apply to Prefab
+ if (UniGLTF.UnityPath.FromUnityPath(AssetDatabase.GetAssetPath(m_root)).Equals(assetPath))
+ {
+ VrmPrefabUtility.ApplyChangesToPrefab(instance);
+ }
+ else
+ {
+ PrefabUtility.SaveAsPrefabAsset(instance, assetPath.Value, out bool success);
+ if (!success)
+ {
+ throw new System.Exception($"PrefabUtility.SaveAsPrefabAsset: {assetPath}");
+ }
+ }
+
+ // destroy source renderers
+ UnityEngine.Object.DestroyImmediate(instance);
}
void OnWizardCreate()
{
- Debug.Log("OnWizardCreate");
Integrate();
-
// close
}
void OnWizardOtherButton()
{
- Debug.Log("OnWizardOtherButton");
Integrate();
}
}
diff --git a/Assets/VRM/Editor/SkinnedMeshUtility/MeshIntegratorEditor.cs.meta b/Assets/VRM/Editor/SkinnedMeshUtility/VrmMeshIntegratorWizard.cs.meta
similarity index 71%
rename from Assets/VRM/Editor/SkinnedMeshUtility/MeshIntegratorEditor.cs.meta
rename to Assets/VRM/Editor/SkinnedMeshUtility/VrmMeshIntegratorWizard.cs.meta
index f87becc3d..aec3bc01f 100644
--- a/Assets/VRM/Editor/SkinnedMeshUtility/MeshIntegratorEditor.cs.meta
+++ b/Assets/VRM/Editor/SkinnedMeshUtility/VrmMeshIntegratorWizard.cs.meta
@@ -1,7 +1,5 @@
fileFormatVersion: 2
-guid: 648920943c26bde4b920119bdbb38f70
-timeCreated: 1518194696
-licenseType: Free
+guid: 0c6fe206e704d2148be9fd6ede8ca4f0
MonoImporter:
externalObjects: {}
serializedVersion: 2
diff --git a/Assets/VRM/Editor/SkinnedMeshUtility/VrmPrefabUtility.cs b/Assets/VRM/Editor/SkinnedMeshUtility/VrmPrefabUtility.cs
new file mode 100644
index 000000000..574c7121c
--- /dev/null
+++ b/Assets/VRM/Editor/SkinnedMeshUtility/VrmPrefabUtility.cs
@@ -0,0 +1,105 @@
+using System.Linq;
+using UniGLTF;
+using UnityEditor;
+using UnityEngine;
+
+namespace VRM
+{
+ public static class VrmPrefabUtility
+ {
+ const string BACKUP_DIR = "MeshIntegratorBackup";
+
+ public static GameObject InstantiatePrefab(GameObject prefab)
+ {
+ if (!IsPrefab(prefab)) return null;
+
+ return (GameObject)PrefabUtility.InstantiatePrefab(prefab);
+ }
+
+ static bool IsPrefab(Object instance)
+ {
+ if (instance == null)
+ {
+ return false;
+ }
+ if (PrefabUtility.GetPrefabAssetType(instance) != PrefabAssetType.Regular)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ public static void ApplyChangesToPrefab(GameObject instance)
+ {
+ var prefab = GetPrefab(instance);
+ if (prefab == null)
+ {
+ return;
+ }
+
+ var path = AssetDatabase.GetAssetPath(prefab);
+ if (string.IsNullOrEmpty(path))
+ {
+ return;
+ }
+
+ PrefabUtility.SaveAsPrefabAssetAndConnect(instance, path, InteractionMode.AutomatedAction);
+ }
+
+ static Object GetPrefab(GameObject instance)
+ {
+#if UNITY_2018_2_OR_NEWER
+ return PrefabUtility.GetCorrespondingObjectFromSource(instance);
+#else
+ return PrefabUtility.GetPrefabParent(go);
+#endif
+ }
+
+ ///
+ /// VRM prefab を ${prefab_dir}/MeshIntegratorBackup/ に複製する。
+ ///
+ /// * prefab
+ /// * BlendShapeAvatar
+ /// * BlendShapeClip
+ ///
+ /// が複製される。
+ ///
+ ///
+ public static void BackupVrmPrefab(GameObject rootPrefab)
+ {
+ var proxy = rootPrefab.GetComponent();
+
+ var srcAvatar = proxy.BlendShapeAvatar;
+ var dstAvatar = (BlendShapeAvatar)BackupAsset(srcAvatar, rootPrefab);
+
+ var clipMapper = srcAvatar.Clips.ToDictionary(x => x, x => (BlendShapeClip)BackupAsset(x, rootPrefab));
+ dstAvatar.Clips = clipMapper.Values.ToList();
+
+ var dstPrefab = BackupAsset(rootPrefab, rootPrefab);
+ var dstInstance = InstantiatePrefab(dstPrefab);
+ dstInstance.GetComponent().BlendShapeAvatar = dstAvatar;
+ ApplyChangesToPrefab(dstInstance);
+ Object.DestroyImmediate(dstInstance);
+ }
+
+ ///
+ /// asset を ${prefab_dir}/MeshIntegratorBackup/ にコピーし、コピーしたアセットをロードして返す
+ ///
+ ///
+ ///
+ ///
+ ///
+ private static T BackupAsset(T asset, GameObject rootPrefab) where T : UnityEngine.Object
+ {
+ var srcAssetPath = UnityPath.FromAsset(asset);
+ var assetName = srcAssetPath.FileName;
+
+ var backupPath = UnityPath.FromAsset(rootPrefab).Parent.Child(BACKUP_DIR);
+ backupPath.EnsureFolder();
+ var dstAssetPath = backupPath.Child(assetName);
+
+ AssetDatabase.CopyAsset(srcAssetPath.Value, dstAssetPath.Value);
+ return dstAssetPath.LoadAsset();
+ }
+ }
+}
diff --git a/Assets/VRM/Editor/SkinnedMeshUtility/VrmPrefabUtility.cs.meta b/Assets/VRM/Editor/SkinnedMeshUtility/VrmPrefabUtility.cs.meta
new file mode 100644
index 000000000..9534b545b
--- /dev/null
+++ b/Assets/VRM/Editor/SkinnedMeshUtility/VrmPrefabUtility.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 6d3e24cadf1393841988d0c53830b67e
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/VRM/Editor/VrmTopMenu.cs b/Assets/VRM/Editor/VrmTopMenu.cs
index a79dc302a..3ed060112 100644
--- a/Assets/VRM/Editor/VrmTopMenu.cs
+++ b/Assets/VRM/Editor/VrmTopMenu.cs
@@ -28,7 +28,7 @@ namespace VRM
private static void FreezeTPose() => VRMHumanoidNormalizerMenu.Normalize();
[MenuItem(UserMenuPrefix + "/MeshIntegratorWizard", priority = 21)]
- private static void OpenMeshIntegratorWizard() => MeshIntegratorWizard.CreateWizard();
+ private static void OpenMeshIntegratorWizard() => VrmMeshIntegratorWizard.CreateWizard();
[MenuItem(UserMenuPrefix + "/Save SpringBone to JSON", validate = true)]
private static bool SaveSpringBoneToJsonValidation() => VRMSpringBoneUtilityEditor.SaveSpringBoneToJsonValidation();
diff --git a/Assets/VRM/Runtime/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs b/Assets/VRM/Runtime/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs
index 81f61e0d5..fea51d76c 100644
--- a/Assets/VRM/Runtime/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs
+++ b/Assets/VRM/Runtime/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs
@@ -11,49 +11,32 @@ namespace VRM
///
public static class VRMMeshIntegratorUtility
{
- public static bool IntegrateRuntime(GameObject vrmRootObject)
- {
- if (vrmRootObject == null) return false;
- var proxy = vrmRootObject.GetComponent();
- if (proxy == null) return false;
- var avatar = proxy.BlendShapeAvatar;
- if (avatar == null) return false;
- var clips = avatar.Clips;
-
- var results = Integrate(vrmRootObject, clips, null);
- if (results.Any(x => x.IntegratedRenderer == null)) return false;
-
- foreach (var result in results)
- {
- foreach (var renderer in result.SourceSkinnedMeshRenderers)
- {
- Object.Destroy(renderer);
- }
-
- foreach (var renderer in result.SourceMeshRenderers)
- {
- Object.Destroy(renderer);
- }
- }
-
- return true;
- }
-
- public static List Integrate(GameObject root, List blendshapeClips, IEnumerable excludes)
+ public static List Integrate(GameObject root, List blendshapeClips, IEnumerable excludes, bool separateByBlendShape)
{
var result = new List();
- var withoutBlendShape = MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: false, excludes: excludes);
- if (withoutBlendShape.IntegratedRenderer != null)
+ if (separateByBlendShape)
{
- result.Add(withoutBlendShape);
- }
+ var withoutBlendShape = MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithoutBlendShape, excludes: excludes);
+ if (withoutBlendShape.IntegratedRenderer != null)
+ {
+ result.Add(withoutBlendShape);
+ }
- var onlyBlendShape = MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: true, excludes: excludes);
- if (onlyBlendShape.IntegratedRenderer != null)
+ var onlyBlendShape = MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithBlendShape, excludes: excludes);
+ if (onlyBlendShape.IntegratedRenderer != null)
+ {
+ result.Add(onlyBlendShape);
+ FollowBlendshapeRendererChange(blendshapeClips, onlyBlendShape, root);
+ }
+ }
+ else
{
- result.Add(onlyBlendShape);
- FollowBlendshapeRendererChange(blendshapeClips, onlyBlendShape, root);
+ var integrated = MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.All, excludes: excludes);
+ if (integrated.IntegratedRenderer != null)
+ {
+ result.Add(integrated);
+ }
}
return result;