mirror of
https://github.com/vrm-c/UniVRM.git
synced 2026-09-07 19:38:13 -05:00
Merge pull request #1642 from ousttrue/fix/update_meshutility
MeshIntegrator と VrmMeshIntegrator で BlendShape 分割しないモードを追加する
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
36
Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogEditor.cs
Normal file
36
Assets/UniGLTF/Editor/MeshUtility/MeshProcessDialogEditor.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9701e1f6aaed8d14283243a7a50be2df
|
||||
guid: 04fa1873cf1c0374aab1670a24d741b5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Assets/UniGLTF/Editor/MeshUtility/Tabs.cs
Normal file
9
Assets/UniGLTF/Editor/MeshUtility/Tabs.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace UniGLTF.MeshUtility
|
||||
{
|
||||
enum Tabs
|
||||
{
|
||||
MeshSeparator,
|
||||
MeshIntegrator,
|
||||
BoneMeshEraser,
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5cdbec3de528c61448aeb739ffe1f79e
|
||||
guid: 57ace5e28eb8e7746b00eaa9405197a9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace UniGLTF.MeshUtility
|
||||
{
|
||||
public enum MeshEnumerateOption
|
||||
{
|
||||
OnlyWithBlendShape,
|
||||
OnlyWithoutBlendShape,
|
||||
All,
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c1bbe850b95e44740bbbb44064e17d25
|
||||
guid: 862f202d0be779744a4954a77cb88a89
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -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>();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d66a91f4b51e42a6b165fe39fdb48d64
|
||||
timeCreated: 1559541677
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 648920943c26bde4b920119bdbb38f70
|
||||
timeCreated: 1518194696
|
||||
licenseType: Free
|
||||
guid: 0c6fe206e704d2148be9fd6ede8ca4f0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
105
Assets/VRM/Editor/SkinnedMeshUtility/VrmPrefabUtility.cs
Normal file
105
Assets/VRM/Editor/SkinnedMeshUtility/VrmPrefabUtility.cs
Normal 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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6d3e24cadf1393841988d0c53830b67e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user