Merge pull request #1642 from ousttrue/fix/update_meshutility

MeshIntegrator と VrmMeshIntegrator で BlendShape 分割しないモードを追加する
This commit is contained in:
ousttrue
2022-05-13 16:44:49 +09:00
committed by GitHub
27 changed files with 856 additions and 898 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,
}
/// <summary>
/// 対象のヒエラルキーに含まれるすべての SkinnedMeshRenderer に対して、
/// BlendShape を含む Mesh と 含まない Mesh への分割を実施する。
///
/// 各 SkinnedMeshRenderer(smr) は、
///
/// smr - mesh(with blendshape)
/// + smr(without) - mesh(without blendshape)
///
/// のように変化する。
/// </summary>
/// <param name="go"></param>
/// <return>(Mesh 分割前, Mesh BlendShape有り、Mesh BlendShape無し)のリストを返す</return>
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<SkinnedMeshRenderer>();
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<int, int>();
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<int, int[]>();
var submeshesWithoutBlendShape = new Dictionary<int, int[]>();
var vertexIndexWithBlendShape = new Dictionary<int, int>();
var vertexCounterWithBlendShape = 0;
var vertexIndexWithoutBlendShape = new Dictionary<int, int>();
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<int>();
var submeshWithoutBlendShape = new List<int>();
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<SkinnedMeshRenderer>();
// 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<int, int> newVerticesListLookUp, int[] triangleList, int index,
List<int> 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<int, int> newIndexLookUpDict,
Dictionary<int, int[]> subMeshes, BlendShapeLogic blendShapeLabel)
{
// get original mesh data
var materialList = new List<Material>();
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<Material>();
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;
}
/// <summary>
/// from dialog
/// </summary>
/// <param name="go"></param>
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<MeshFilter>();
filter.sharedMesh = meshWithMaterials.Mesh;
var renderer = meshObject.AddComponent<MeshRenderer>();
renderer.sharedMaterials = meshWithMaterials.Materials;
}
#endif
/// <summary>
/// from dialog
/// </summary>
/// <param name="go"></param>
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<SkinnedMeshRenderer>();
var normalMeshes = outputObject.GetComponentsInChildren<MeshFilter>();
// destroy integrated meshes in the source
// ?
foreach (var skinnedMesh in go.GetComponentsInChildren<SkinnedMeshRenderer>())
{
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<MeshRenderer>())
{
GameObject.DestroyImmediate(normalMesh.gameObject.GetComponent<MeshRenderer>());
}
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);
}
}
}

View File

@@ -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<SkinnedMeshRenderer>();
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<SkinnedMeshRenderer>())
foreach (var skinnedMesh in root.GetComponentsInChildren<SkinnedMeshRenderer>())
{
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<SkinnedMeshRenderer>())
{
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<SkinnedMeshRenderer>();
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
}
}
}

View File

@@ -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<SkinnedMeshRenderer>().Length == 0 && go.GetComponentsInChildren<MeshFilter>().Length == 0)
if (root.GetComponentsInChildren<SkinnedMeshRenderer>().Length == 0 && root.GetComponentsInChildren<MeshFilter>().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<SkinnedMeshRenderer>();
// destroy integrated meshes in the source
foreach (var skinnedMesh in root.GetComponentsInChildren<SkinnedMeshRenderer>())
{
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<MeshFilter>();
foreach (var normalMesh in normalMeshes)
{
if (normalMesh.sharedMesh.name != MeshIntegratorUtility.INTEGRATED_MESH_NAME)
{
if (normalMesh.gameObject.GetComponent<MeshRenderer>())
{
GameObject.DestroyImmediate(normalMesh.gameObject.GetComponent<MeshRenderer>());
}
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);
}
}
}

View File

@@ -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
/// </summary>
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<SkinnedMeshRenderer>().Length == 0)
if (root.GetComponentsInChildren<SkinnedMeshRenderer>().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);
}
/// <summary>
/// 対象のヒエラルキーに含まれるすべての SkinnedMeshRenderer に対して、
/// BlendShape を含む Mesh と 含まない Mesh への分割を実施する。
///
/// 各 SkinnedMeshRenderer(smr) は、
///
/// smr - mesh(with blendshape)
/// + smr(without) - mesh(without blendshape)
///
/// のように変化する。
/// </summary>
/// <param name="go"></param>
/// <return>(Mesh 分割前, Mesh BlendShape有り、Mesh BlendShape無し)のリストを返す</return>
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<SkinnedMeshRenderer>();
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<int, int>();
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<int, int[]>();
var submeshesWithoutBlendShape = new Dictionary<int, int[]>();
var vertexIndexWithBlendShape = new Dictionary<int, int>();
var vertexCounterWithBlendShape = 0;
var vertexIndexWithoutBlendShape = new Dictionary<int, int>();
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<int>();
var submeshWithoutBlendShape = new List<int>();
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<SkinnedMeshRenderer>();
// 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<int, int> newVerticesListLookUp, int[] triangleList, int index,
List<int> 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<int, int> newIndexLookUpDict,
Dictionary<int, int[]> subMeshes, BlendShapeLogic blendShapeLabel)
{
// get original mesh data
var materialList = new List<Material>();
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<Material>();
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;
}
}
}

View File

@@ -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<MeshFilter>().Length == 0)
{
EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_STATIC_MESH.Msg(), "ok");
return false;
}
MeshUtility.IntegrateSelected(go);
return true;
}
}
}

View File

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

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 5cdbec3de528c61448aeb739ffe1f79e
guid: 57ace5e28eb8e7746b00eaa9405197a9
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -0,0 +1,9 @@
namespace UniGLTF.MeshUtility
{
public enum MeshEnumerateOption
{
OnlyWithBlendShape,
OnlyWithoutBlendShape,
All,
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: c1bbe850b95e44740bbbb44064e17d25
guid: 862f202d0be779744a4954a77cb88a89
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

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

View File

@@ -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;
/// <summary>
/// go を root としたヒエラルキーから Renderer を集めて、統合された Mesh 作成する
/// </summary>
/// <param name="go"></param>
/// <param name="onlyBlendShapeRenderers">BlendShapeを保持するSkinnedMeshRendererのみ/BlendShapeを保持しないSkinnedMeshRenderer + MeshRenderer</param>
/// <param name="onlyBlendShapeRenderers">
/// true: BlendShapeを保持するSkinnedMeshRendererのみ
/// false: BlendShapeを保持しないSkinnedMeshRenderer + MeshRenderer
/// null: すべてのSkinnedMeshRenderer + MeshRenderer
/// </param>
/// <returns></returns>
public static MeshIntegrationResult Integrate(GameObject go, bool onlyBlendShapeRenderers, IEnumerable<Mesh> excludes = null)
public static MeshIntegrationResult Integrate(GameObject go, MeshEnumerateOption onlyBlendShapeRenderers, IEnumerable<Mesh> 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<SkinnedMeshRenderer> EnumerateSkinnedMeshRenderer(Transform root, bool hasBlendShape)
public static IEnumerable<SkinnedMeshRenderer> 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;
}
}
}
}
}

View File

@@ -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
{
/// <summary>
/// 複数のメッシュを統合する。
/// VRM の場合、BlendShapeClip の調整が必用なのでこれも実行する。
/// </summary>
[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<MeshIntegrationResult> Integrate(GameObject prefab, UniGLTF.UnityPath writeAssetPath, IEnumerable<Mesh> excludes)
{
Undo.RecordObject(prefab, "Mesh Integration");
var instance = SkinnedMeshUtility.InstantiatePrefab(prefab);
var clips = new List<BlendShapeClip>();
var proxy = instance.GetComponent<VRMBlendShapeProxy>();
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<VRMBlendShapeProxy>();
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<VRMBlendShapeProxy>().BlendShapeAvatar = dstAvatar;
SkinnedMeshUtility.ApplyChangesToPrefab(dstInstance);
Object.DestroyImmediate(dstInstance);
}
private static T BackupAsset<T>(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<T>();
}
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;
}
}
}

View File

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

View File

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

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: d66a91f4b51e42a6b165fe39fdb48d64
timeCreated: 1559541677

View File

@@ -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>("MeshIntegratorWizard", "Integrate and close window", "Integrate");
ScriptableWizard.DisplayWizard<VrmMeshIntegratorWizard>("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<BlendShapeClip>();
var proxy = instance.GetComponent<VRMBlendShapeProxy>();
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<MeshIntegrationResult> Integrate(GameObject prefab, UniGLTF.UnityPath writeAssetPath, IEnumerable<Mesh> 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();
}
}

View File

@@ -1,7 +1,5 @@
fileFormatVersion: 2
guid: 648920943c26bde4b920119bdbb38f70
timeCreated: 1518194696
licenseType: Free
guid: 0c6fe206e704d2148be9fd6ede8ca4f0
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -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
}
/// <summary>
/// VRM prefab を ${prefab_dir}/MeshIntegratorBackup/ に複製する。
///
/// * prefab
/// * BlendShapeAvatar
/// * BlendShapeClip
///
/// が複製される。
/// </summary>
/// <param name="rootPrefab"></param>
public static void BackupVrmPrefab(GameObject rootPrefab)
{
var proxy = rootPrefab.GetComponent<VRMBlendShapeProxy>();
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<VRMBlendShapeProxy>().BlendShapeAvatar = dstAvatar;
ApplyChangesToPrefab(dstInstance);
Object.DestroyImmediate(dstInstance);
}
/// <summary>
/// asset を ${prefab_dir}/MeshIntegratorBackup/ にコピーし、コピーしたアセットをロードして返す
/// </summary>
/// <param name="asset"></param>
/// <param name="rootPrefab"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
private static T BackupAsset<T>(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<T>();
}
}
}

View File

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

View File

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

View File

@@ -11,49 +11,32 @@ namespace VRM
/// </summary>
public static class VRMMeshIntegratorUtility
{
public static bool IntegrateRuntime(GameObject vrmRootObject)
{
if (vrmRootObject == null) return false;
var proxy = vrmRootObject.GetComponent<VRMBlendShapeProxy>();
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<UniGLTF.MeshUtility.MeshIntegrationResult> Integrate(GameObject root, List<BlendShapeClip> blendshapeClips, IEnumerable<Mesh> excludes)
public static List<UniGLTF.MeshUtility.MeshIntegrationResult> Integrate(GameObject root, List<BlendShapeClip> blendshapeClips, IEnumerable<Mesh> excludes, bool separateByBlendShape)
{
var result = new List<UniGLTF.MeshUtility.MeshIntegrationResult>();
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;