mirror of
https://github.com/vrm-c/UniVRM.git
synced 2026-05-19 17:27:56 -05:00
Merge pull request #1638 from ousttrue/fix/MeshIntegrator_update
Fix/mesh integrator update
This commit is contained in:
commit
b40096ccab
19
Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserGUI.cs
Normal file
19
Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserGUI.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserGUI.cs.meta
Normal file
11
Assets/UniGLTF/Editor/MeshUtility/BoneMeshEraserGUI.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5cdbec3de528c61448aeb739ffe1f79e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
|
@ -9,31 +7,13 @@ using UniGLTF.M17N;
|
|||
|
||||
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 animator = serializedObject.FindProperty("_cAnimator");
|
||||
EditorGUILayout.PropertyField(animator, new GUIContent("Animator"), false);
|
||||
var eraseRoot = serializedObject.FindProperty("_cEraseRoot");
|
||||
EditorGUILayout.PropertyField(eraseRoot, new GUIContent("Erase Root"), false);
|
||||
var list = serializedObject.FindProperty("_eraseBones");
|
||||
EditorGUILayout.PropertyField(list, new GUIContent("Erase Bones"), true);
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
|
||||
public class MeshProcessDialog : EditorWindow
|
||||
{
|
||||
enum Tabs
|
||||
{
|
||||
MeshSeparator,
|
||||
MeshIntegrator,
|
||||
StaticMeshIntegrator,
|
||||
// StaticMeshIntegrator,
|
||||
BoneMeshEraser,
|
||||
}
|
||||
private Tabs _tab;
|
||||
|
|
@ -47,68 +27,25 @@ namespace UniGLTF.MeshUtility
|
|||
|
||||
[SerializeField]
|
||||
private SkinnedMeshRenderer _cSkinnedMesh = null;
|
||||
[SerializeField]
|
||||
|
||||
private Animator _cAnimator = null;
|
||||
[SerializeField]
|
||||
private Transform _cEraseRoot = null;
|
||||
|
||||
[SerializeField]
|
||||
private BoneMeshEraser.EraseBone[] _eraseBones;
|
||||
|
||||
private MethodInfo _processFunction;
|
||||
private bool _isInvokeSuccess = false;
|
||||
|
||||
GUIStyle _tabButtonStyle => "LargeButton";
|
||||
GUI.ToolbarButtonSize _tabButtonSize => GUI.ToolbarButtonSize.Fixed;
|
||||
|
||||
private enum MeshProcessingMessages
|
||||
|
||||
public static void OpenWindow()
|
||||
{
|
||||
[LangMsg(Languages.ja, "ターゲットオブジェクト")]
|
||||
[LangMsg(Languages.en, "TargetObject")]
|
||||
TARGET_OBJECT,
|
||||
|
||||
[LangMsg(Languages.ja, "BlendShapeを含むメッシュは分割されます")]
|
||||
[LangMsg(Languages.en, "Meshes containing BlendShape will be split")]
|
||||
MESH_SEPARATOR,
|
||||
|
||||
[LangMsg(Languages.ja, "メッシュを統合します。BlendShapeを含むメッシュは独立して統合されます")]
|
||||
[LangMsg(Languages.en, "Generate a single mesh. Meshes w/ BlendShape will be grouped into another one")]
|
||||
MESH_INTEGRATOR,
|
||||
|
||||
[LangMsg(Languages.ja, "静的メッシュを一つに統合します")]
|
||||
[LangMsg(Languages.en, "Integrate static meshes into one")]
|
||||
STATIC_MESH_INTEGRATOR,
|
||||
|
||||
[LangMsg(Languages.ja, "ボーン(Erase Rootのヒエラルキー)に関連するメッシュを削除します")]
|
||||
[LangMsg(Languages.en, "Eliminate meshes associated with the bones in EraseRoot hierarchy")]
|
||||
BONE_MESH_ERASER,
|
||||
|
||||
[LangMsg(Languages.ja, "Skinned Meshを選んでください")]
|
||||
[LangMsg(Languages.en, "Select a skinned mesh")]
|
||||
SELECT_SKINNED_MESH,
|
||||
|
||||
[LangMsg(Languages.ja, "Erase Rootを選んでください")]
|
||||
[LangMsg(Languages.en, "Select a erase root")]
|
||||
SELECT_ERASE_ROOT,
|
||||
|
||||
[LangMsg(Languages.ja, "GameObjectを選んでください")]
|
||||
[LangMsg(Languages.en, "Select a GameObject first")]
|
||||
NO_GAMEOBJECT_SELECTED,
|
||||
|
||||
[LangMsg(Languages.ja, "GameObjectにスキンメッシュが含まれていません")]
|
||||
[LangMsg(Languages.en, "No skinned mesh is contained")]
|
||||
NO_SKINNED_MESH,
|
||||
|
||||
[LangMsg(Languages.ja, "GameObjectに静的メッシュが含まれていません")]
|
||||
[LangMsg(Languages.en, "No static mesh is contained")]
|
||||
NO_STATIC_MESH,
|
||||
|
||||
[LangMsg(Languages.ja, "GameObjectにスキンメッシュ・静的メッシュが含まれていません")]
|
||||
[LangMsg(Languages.en, "Skinned/Static mesh is not contained")]
|
||||
NO_MESH,
|
||||
|
||||
[LangMsg(Languages.ja, "ターゲットオブジェクトはVRMモデルです。`VRM0-> MeshIntegrator`を使ってください")]
|
||||
[LangMsg(Languages.en, "Target object is VRM model, use `VRM0 -> MeshIntegrator` instead")]
|
||||
VRM_DETECTED,
|
||||
var window =
|
||||
(MeshProcessDialog)EditorWindow.GetWindow(typeof(MeshProcessDialog));
|
||||
window.titleContent = new GUIContent("Mesh Processing Window");
|
||||
window.Show();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
|
|
@ -126,202 +63,64 @@ namespace UniGLTF.MeshUtility
|
|||
// lang
|
||||
LanguageGetter.OnGuiSelectLang();
|
||||
|
||||
_tab = TabBar.OnGUI(_tab, _tabButtonStyle, _tabButtonSize);
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
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())
|
||||
{
|
||||
_exportTarget = Selection.activeObject as GameObject;
|
||||
}
|
||||
}
|
||||
|
||||
// tab
|
||||
_tab = TabBar.OnGUI(_tab, _tabButtonStyle, _tabButtonSize);
|
||||
var processed = false;
|
||||
switch (_tab)
|
||||
{
|
||||
case Tabs.MeshSeparator:
|
||||
EditorGUILayout.LabelField(MeshProcessingMessages.MESH_SEPARATOR.Msg());
|
||||
EditorGUILayout.HelpBox(MeshProcessingMessages.MESH_SEPARATOR.Msg(), MessageType.Info);
|
||||
processed = TabMeshSeparator.OnGUI(_exportTarget);
|
||||
break;
|
||||
|
||||
case Tabs.MeshIntegrator:
|
||||
EditorGUILayout.LabelField(MeshProcessingMessages.MESH_INTEGRATOR.Msg());
|
||||
break;
|
||||
case Tabs.StaticMeshIntegrator:
|
||||
EditorGUILayout.LabelField(MeshProcessingMessages.STATIC_MESH_INTEGRATOR.Msg());
|
||||
EditorGUILayout.HelpBox(MeshProcessingMessages.MESH_INTEGRATOR.Msg(), MessageType.Info);
|
||||
processed = TabMeshIntegrator.OnGUI(_exportTarget);
|
||||
break;
|
||||
|
||||
// MeshIntegrator と機能が重複しているのと正常に動作しなかった
|
||||
// case Tabs.StaticMeshIntegrator:
|
||||
// EditorGUILayout.HelpBox(MeshProcessingMessages.STATIC_MESH_INTEGRATOR.Msg(), MessageType.Info);
|
||||
// processed = TabStaticMeshIntegrator.OnGUI(_exportTarget);
|
||||
// break;
|
||||
|
||||
case Tabs.BoneMeshEraser:
|
||||
EditorGUILayout.LabelField(MeshProcessingMessages.BONE_MESH_ERASER.Msg());
|
||||
break;
|
||||
}
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
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())
|
||||
{
|
||||
_exportTarget = Selection.activeObject as GameObject;
|
||||
}
|
||||
|
||||
if (_tab == Tabs.BoneMeshEraser)
|
||||
{
|
||||
if (_boneMeshEraserEditor)
|
||||
{
|
||||
_boneMeshEraserEditor.OnInspectorGUI();
|
||||
}
|
||||
// any better way we can detect component change?
|
||||
if (_cSkinnedMesh != _pSkinnedMesh || _cAnimator != _pAnimator || _cEraseRoot != _pEraseRoot)
|
||||
{
|
||||
BoneMeshEraserValidate();
|
||||
}
|
||||
_pSkinnedMesh = _cSkinnedMesh;
|
||||
_pAnimator = _cAnimator;
|
||||
_pEraseRoot = _cEraseRoot;
|
||||
}
|
||||
|
||||
// Create Other Buttons
|
||||
{
|
||||
GUILayout.BeginVertical();
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
if (GUILayout.Button("Process", GUILayout.MinWidth(100)))
|
||||
EditorGUILayout.HelpBox(MeshProcessingMessages.BONE_MESH_ERASER.Msg(), MessageType.Info);
|
||||
if (_boneMeshEraserEditor)
|
||||
{
|
||||
switch (_tab)
|
||||
{
|
||||
case Tabs.MeshSeparator:
|
||||
_isInvokeSuccess = InvokeWizardUpdate("MeshSeparator");
|
||||
break;
|
||||
case Tabs.MeshIntegrator:
|
||||
_isInvokeSuccess = InvokeWizardUpdate("MeshIntegrator");
|
||||
break;
|
||||
case Tabs.StaticMeshIntegrator:
|
||||
_isInvokeSuccess = InvokeWizardUpdate("StaticMeshIntegrator");
|
||||
break;
|
||||
case Tabs.BoneMeshEraser:
|
||||
_isInvokeSuccess = InvokeWizardUpdate("BoneMeshRemover");
|
||||
break;
|
||||
}
|
||||
if (_isInvokeSuccess)
|
||||
{
|
||||
Close();
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
_boneMeshEraserEditor.OnInspectorGUI();
|
||||
}
|
||||
GUI.enabled = true;
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
GUILayout.EndVertical();
|
||||
// any better way we can detect component change?
|
||||
if (_cSkinnedMesh != _pSkinnedMesh || _cAnimator != _pAnimator || _cEraseRoot != _pEraseRoot)
|
||||
{
|
||||
BoneMeshEraserValidate();
|
||||
}
|
||||
_pSkinnedMesh = _cSkinnedMesh;
|
||||
_pAnimator = _cAnimator;
|
||||
_pEraseRoot = _cEraseRoot;
|
||||
processed = TabBoneMeshRemover.OnGUI(_exportTarget, _cSkinnedMesh, _eraseBones);
|
||||
break;
|
||||
}
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
private bool InvokeWizardUpdate(string processFuntion)
|
||||
{
|
||||
const BindingFlags kInstanceInvokeFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy;
|
||||
_processFunction = GetType().GetMethod(processFuntion, kInstanceInvokeFlags);
|
||||
if (_processFunction != null)
|
||||
if (processed)
|
||||
{
|
||||
return (Boolean)_processFunction.Invoke(this, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("This function has not been implemented in script");
|
||||
return false;
|
||||
Close();
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
}
|
||||
|
||||
private bool GameObjectNull()
|
||||
{
|
||||
EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_GAMEOBJECT_SELECTED.Msg(), "ok");
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool MeshSeparator()
|
||||
{
|
||||
if (_exportTarget == null) return GameObjectNull();
|
||||
var go = _exportTarget;
|
||||
|
||||
if (go.GetComponentsInChildren<SkinnedMeshRenderer>().Length > 0)
|
||||
{
|
||||
// copy
|
||||
var outputObject = GameObject.Instantiate(go);
|
||||
outputObject.name = outputObject.name + "_mesh_separation";
|
||||
// 改変と asset の作成
|
||||
var list = MeshUtility.SeparationProcessing(outputObject);
|
||||
foreach (var (src, with, without) in list)
|
||||
{
|
||||
// asset の永続化
|
||||
MeshUtility.SaveMesh(src, with, MeshUtility.BlendShapeLogic.WithBlendShape);
|
||||
MeshUtility.SaveMesh(src, without, MeshUtility.BlendShapeLogic.WithoutBlendShape);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_SKINNED_MESH.Msg(), "ok");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool MeshIntegrator()
|
||||
{
|
||||
if (_exportTarget == null) return GameObjectNull();
|
||||
var go = _exportTarget;
|
||||
|
||||
Component[] allComponents = go.GetComponents(typeof(Component));
|
||||
var keyWord = "VRMMeta";
|
||||
|
||||
foreach (var component in allComponents)
|
||||
{
|
||||
if (component == null) continue;
|
||||
var sourceString = component.ToString();
|
||||
if (sourceString.Contains(keyWord))
|
||||
{
|
||||
EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.VRM_DETECTED.Msg(), "ok");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (go.GetComponentsInChildren<SkinnedMeshRenderer>().Length > 0 || go.GetComponentsInChildren<MeshFilter>().Length > 0)
|
||||
{
|
||||
MeshUtility.MeshIntegrator(go);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_MESH.Msg(), "ok");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool StaticMeshIntegrator()
|
||||
{
|
||||
if (_exportTarget == null) return GameObjectNull();
|
||||
var go = _exportTarget;
|
||||
if (go.GetComponentsInChildren<MeshFilter>().Length > 0)
|
||||
{
|
||||
MeshUtility.IntegrateSelected(go);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_STATIC_MESH.Msg(), "ok");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool BoneMeshRemover()
|
||||
{
|
||||
if (_exportTarget == null) return GameObjectNull();
|
||||
var go = _exportTarget;
|
||||
|
||||
if (_cSkinnedMesh == null)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.SELECT_SKINNED_MESH.Msg(), "ok");
|
||||
return false;
|
||||
}
|
||||
else if (_cEraseRoot == null)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.SELECT_ERASE_ROOT.Msg(), "ok");
|
||||
return false;
|
||||
}
|
||||
BoneMeshRemove(go);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void BoneMeshEraserValidate()
|
||||
{
|
||||
|
|
@ -361,68 +160,5 @@ namespace UniGLTF.MeshUtility
|
|||
})
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private void BoneMeshRemove(GameObject go)
|
||||
{
|
||||
var renderer = Remove(go);
|
||||
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);
|
||||
if (prefab != null)
|
||||
{
|
||||
var prefabPath = AssetDatabase.GetAssetPath(prefab);
|
||||
assetPath = string.Format("{0}/{1}{2}",
|
||||
Path.GetDirectoryName(prefabPath),
|
||||
Path.GetFileNameWithoutExtension(prefabPath),
|
||||
MeshUtility.ASSET_SUFFIX
|
||||
);
|
||||
}
|
||||
|
||||
Debug.LogFormat("CreateAsset: {0}", assetPath);
|
||||
AssetDatabase.CreateAsset(renderer.sharedMesh, assetPath);
|
||||
|
||||
// destroy BoneMeshEraser in the source
|
||||
foreach (var skinnedMesh in go.GetComponentsInChildren<SkinnedMeshRenderer>())
|
||||
{
|
||||
if (skinnedMesh.gameObject.name == BoneMeshEraserWizard.BONE_MESH_ERASER_NAME)
|
||||
{
|
||||
GameObject.DestroyImmediate(skinnedMesh.gameObject);
|
||||
}
|
||||
}
|
||||
// destroy the original mesh in the copied GameObject
|
||||
foreach (var skinnedMesh in outputObject.GetComponentsInChildren<SkinnedMeshRenderer>())
|
||||
{
|
||||
if (skinnedMesh.sharedMesh == _cSkinnedMesh.sharedMesh)
|
||||
{
|
||||
GameObject.DestroyImmediate(skinnedMesh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private SkinnedMeshRenderer Remove(GameObject go)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
82
Assets/UniGLTF/Editor/MeshUtility/MeshProcessingMessages.cs
Normal file
82
Assets/UniGLTF/Editor/MeshUtility/MeshProcessingMessages.cs
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
using UniGLTF.M17N;
|
||||
|
||||
namespace UniGLTF.MeshUtility
|
||||
{
|
||||
public enum MeshProcessingMessages
|
||||
{
|
||||
[LangMsg(Languages.ja, "ターゲットオブジェクト")]
|
||||
[LangMsg(Languages.en, "TargetObject")]
|
||||
TARGET_OBJECT,
|
||||
|
||||
[LangMsg(Languages.ja, @"ターゲットオブジェクト下の SkinnedMeshRenderer にアタッチされたメッシュを、 BlendShape の有無で分割します。
|
||||
|
||||
* Asset: 新しい Mesh Asset が元と同じフォルダに作成されます。例: Original -> Original_WithBlendShape.mesh & Original_WithoutBlendShape.mesh
|
||||
* Scene: コピーされたヒエラルキーでは、分割された Mesh は BlendShape のある Mesh で置き換えられて、BlendShape の無い Mesh を使った SkinnedMeshRenderer が追加されます。
|
||||
")]
|
||||
[LangMsg(Languages.en, @"Separate the mesh attached to the SkinnedMeshRenderer under the target object with or without BlendShape.
|
||||
|
||||
* Asset: A new Mesh Asset will be created in the same folder as the original. Example: Original-> Original_WithBlendShape.mesh & Original_WithoutBlendShape.mesh
|
||||
* Scene: In the copied hierarchy, the split Mesh is replaced with a Mesh that holds the BlendShape, and a SkinnedMeshRenderer with a Mesh without BlendShape is added.
|
||||
")]
|
||||
MESH_SEPARATOR,
|
||||
|
||||
[LangMsg(Languages.ja, @"ターゲットオブジェクト下の SkinnedMeshRenderer または MeshFilter にアタッチされたメッシュをを統合します。BlendShape の有無で2つ作成されます。
|
||||
|
||||
* Asset: Assets/MeshIntegrated.mesh が作成されます(上書きされるので注意してください)。
|
||||
* Scene: コピーされたヒエラルキーでは、統合された Mesh は除去されます。新しく MeshIntegrator ノードが追加されます。
|
||||
* VRMではBlendShapeClipの統合など追加の処理が必要でる。VRMの統合機能を使ってください。
|
||||
")]
|
||||
[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.
|
||||
|
||||
* 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.
|
||||
")]
|
||||
MESH_INTEGRATOR,
|
||||
|
||||
// [LangMsg(Languages.ja, "静的メッシュを一つに統合します")]
|
||||
// [LangMsg(Languages.en, "Integrate static meshes into one")]
|
||||
// STATIC_MESH_INTEGRATOR,
|
||||
|
||||
[LangMsg(Languages.ja, @"指定された SkinnedMeshRenderer から、指定されたボーンに対する Weight を保持する三角形を除去します。
|
||||
|
||||
* Asset: 元の Mesh と同じフォルダに、三角形を除去した Mesh を保存します。
|
||||
* Scene: コピーされたヒエラルキーでは、三角形が除去された Mesh に差し替えられます。
|
||||
")]
|
||||
[LangMsg(Languages.en, @"Removes the triangle that holds the weight for the specified bone from the specified SkinnedMeshRenderer.
|
||||
|
||||
* Assets: Save the mesh with the triangles removed in the same folder as the original mesh.
|
||||
* Scene: In the copied hierarchy, it will be replaced with a Mesh with the triangles removed.
|
||||
")]
|
||||
|
||||
BONE_MESH_ERASER,
|
||||
|
||||
[LangMsg(Languages.ja, "Skinned Meshを選んでください")]
|
||||
[LangMsg(Languages.en, "Select a skinned mesh")]
|
||||
SELECT_SKINNED_MESH,
|
||||
|
||||
[LangMsg(Languages.ja, "Erase Rootを選んでください")]
|
||||
[LangMsg(Languages.en, "Select a erase root")]
|
||||
SELECT_ERASE_ROOT,
|
||||
|
||||
[LangMsg(Languages.ja, "GameObjectを選んでください")]
|
||||
[LangMsg(Languages.en, "Select a GameObject first")]
|
||||
NO_GAMEOBJECT_SELECTED,
|
||||
|
||||
[LangMsg(Languages.ja, "GameObjectにスキンメッシュが含まれていません")]
|
||||
[LangMsg(Languages.en, "No skinned mesh is contained")]
|
||||
NO_SKINNED_MESH,
|
||||
|
||||
[LangMsg(Languages.ja, "GameObjectに静的メッシュが含まれていません")]
|
||||
[LangMsg(Languages.en, "No static mesh is contained")]
|
||||
NO_STATIC_MESH,
|
||||
|
||||
[LangMsg(Languages.ja, "GameObjectにスキンメッシュ・静的メッシュが含まれていません")]
|
||||
[LangMsg(Languages.en, "Skinned/Static mesh is not contained")]
|
||||
NO_MESH,
|
||||
|
||||
[LangMsg(Languages.ja, "ターゲットオブジェクトはVRMモデルです。`VRM0-> MeshIntegrator`を使ってください")]
|
||||
[LangMsg(Languages.en, "Target object is VRM model, use `VRM0 -> MeshIntegrator` instead")]
|
||||
VRM_DETECTED,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 575d573f28f0e4a48a2a6509e69b7f6f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -264,6 +264,10 @@ namespace UniGLTF.MeshUtility
|
|||
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);
|
||||
|
|
@ -319,6 +323,11 @@ namespace UniGLTF.MeshUtility
|
|||
renderer.sharedMaterials = meshWithMaterials.Materials;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// from dialog
|
||||
/// </summary>
|
||||
/// <param name="go"></param>
|
||||
public static void MeshIntegrator(GameObject go)
|
||||
{
|
||||
MeshIntegratorUtility.Integrate(go, onlyBlendShapeRenderers: true);
|
||||
|
|
@ -330,6 +339,7 @@ namespace UniGLTF.MeshUtility
|
|||
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 ||
|
||||
|
|
|
|||
111
Assets/UniGLTF/Editor/MeshUtility/TabBoneMeshRemover.cs
Normal file
111
Assets/UniGLTF/Editor/MeshUtility/TabBoneMeshRemover.cs
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UniGLTF.M17N;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniGLTF.MeshUtility
|
||||
{
|
||||
public static class TabBoneMeshRemover
|
||||
{
|
||||
public static bool OnGUI(GameObject _exportTarget, SkinnedMeshRenderer _cSkinnedMesh, BoneMeshEraser.EraseBone[] _eraseBones)
|
||||
{
|
||||
var _isInvokeSuccess = false;
|
||||
GUILayout.BeginVertical();
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
if (GUILayout.Button("Process", GUILayout.MinWidth(100)))
|
||||
{
|
||||
_isInvokeSuccess = TabBoneMeshRemover.Execute(_exportTarget, _cSkinnedMesh, _eraseBones);
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
GUILayout.EndVertical();
|
||||
return _isInvokeSuccess;
|
||||
}
|
||||
|
||||
public static bool Execute(GameObject _exportTarget, SkinnedMeshRenderer _cSkinnedMesh, BoneMeshEraser.EraseBone[] _eraseBones)
|
||||
{
|
||||
if (_exportTarget == null)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_GAMEOBJECT_SELECTED.Msg(), "ok");
|
||||
return false;
|
||||
}
|
||||
var go = _exportTarget;
|
||||
|
||||
if (_cSkinnedMesh == null)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.SELECT_SKINNED_MESH.Msg(), "ok");
|
||||
return false;
|
||||
}
|
||||
|
||||
BoneMeshRemove(go, _cSkinnedMesh, _eraseBones);
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
if (prefab != null)
|
||||
{
|
||||
var prefabPath = AssetDatabase.GetAssetPath(prefab);
|
||||
assetPath = string.Format("{0}/{1}{2}",
|
||||
Path.GetDirectoryName(prefabPath),
|
||||
Path.GetFileNameWithoutExtension(prefabPath),
|
||||
MeshUtility.ASSET_SUFFIX
|
||||
);
|
||||
}
|
||||
|
||||
Debug.LogFormat("CreateAsset: {0}", assetPath);
|
||||
AssetDatabase.CreateAsset(renderer.sharedMesh, assetPath);
|
||||
|
||||
// destroy BoneMeshEraser in the source
|
||||
foreach (var skinnedMesh in go.GetComponentsInChildren<SkinnedMeshRenderer>())
|
||||
{
|
||||
if (skinnedMesh.gameObject.name == BoneMeshEraserWizard.BONE_MESH_ERASER_NAME)
|
||||
{
|
||||
GameObject.DestroyImmediate(skinnedMesh.gameObject);
|
||||
}
|
||||
}
|
||||
// destroy the original mesh in the copied GameObject
|
||||
foreach (var skinnedMesh in outputObject.GetComponentsInChildren<SkinnedMeshRenderer>())
|
||||
{
|
||||
if (skinnedMesh.sharedMesh == _cSkinnedMesh.sharedMesh)
|
||||
{
|
||||
GameObject.DestroyImmediate(skinnedMesh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static SkinnedMeshRenderer Remove(GameObject go, SkinnedMeshRenderer _cSkinnedMesh, BoneMeshEraser.EraseBone[] _eraseBones)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/UniGLTF/Editor/MeshUtility/TabBoneMeshRemover.cs.meta
Normal file
11
Assets/UniGLTF/Editor/MeshUtility/TabBoneMeshRemover.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4094f82d7ee108a4997d80a692116033
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
67
Assets/UniGLTF/Editor/MeshUtility/TabMeshIntegrator.cs
Normal file
67
Assets/UniGLTF/Editor/MeshUtility/TabMeshIntegrator.cs
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
using UniGLTF.M17N;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniGLTF.MeshUtility
|
||||
{
|
||||
public static class TabMeshIntegrator
|
||||
{
|
||||
public static bool OnGUI(GameObject _exportTarget)
|
||||
{
|
||||
var _isInvokeSuccess = false;
|
||||
GUILayout.BeginVertical();
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
if (GUILayout.Button("Process", GUILayout.MinWidth(100)))
|
||||
{
|
||||
_isInvokeSuccess = TabMeshIntegrator.Execute(_exportTarget);
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
GUILayout.EndVertical();
|
||||
return _isInvokeSuccess;
|
||||
}
|
||||
|
||||
static string VRM_META = "VRMMeta";
|
||||
static bool HasVrm(GameObject go)
|
||||
{
|
||||
var allComponents = go.GetComponents(typeof(Component));
|
||||
foreach (var component in allComponents)
|
||||
{
|
||||
if (component == null) continue;
|
||||
var sourceString = component.ToString();
|
||||
if (sourceString.Contains(VRM_META))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool Execute(GameObject _exportTarget)
|
||||
{
|
||||
if (_exportTarget == null)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_GAMEOBJECT_SELECTED.Msg(), "ok");
|
||||
return false;
|
||||
}
|
||||
var go = _exportTarget;
|
||||
|
||||
if (HasVrm(go))
|
||||
{
|
||||
EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.VRM_DETECTED.Msg(), "ok");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (go.GetComponentsInChildren<SkinnedMeshRenderer>().Length == 0 && go.GetComponentsInChildren<MeshFilter>().Length == 0)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_MESH.Msg(), "ok");
|
||||
return false;
|
||||
}
|
||||
|
||||
MeshUtility.MeshIntegrator(go);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/UniGLTF/Editor/MeshUtility/TabMeshIntegrator.cs.meta
Normal file
11
Assets/UniGLTF/Editor/MeshUtility/TabMeshIntegrator.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 65a227dcf3cb5f34085bd6829894fb64
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
59
Assets/UniGLTF/Editor/MeshUtility/TabMeshSeparator.cs
Normal file
59
Assets/UniGLTF/Editor/MeshUtility/TabMeshSeparator.cs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
using UniGLTF.M17N;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniGLTF.MeshUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// BlendShape の有無で Mesh を分割する
|
||||
/// </summary>
|
||||
public static class TabMeshSeparator
|
||||
{
|
||||
public static bool OnGUI(GameObject _exportTarget)
|
||||
{
|
||||
var _isInvokeSuccess = false;
|
||||
GUILayout.BeginVertical();
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
if (GUILayout.Button("Process", GUILayout.MinWidth(100)))
|
||||
{
|
||||
_isInvokeSuccess = TabMeshSeparator.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<SkinnedMeshRenderer>().Length == 0)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_SKINNED_MESH.Msg(), "ok");
|
||||
return false;
|
||||
}
|
||||
|
||||
// copy
|
||||
var outputObject = GameObject.Instantiate(go);
|
||||
outputObject.name = outputObject.name + "_mesh_separation";
|
||||
|
||||
// 改変と asset の作成
|
||||
var list = MeshUtility.SeparationProcessing(outputObject);
|
||||
foreach (var (src, with, without) in list)
|
||||
{
|
||||
// asset の永続化
|
||||
MeshUtility.SaveMesh(src, with, MeshUtility.BlendShapeLogic.WithBlendShape);
|
||||
MeshUtility.SaveMesh(src, without, MeshUtility.BlendShapeLogic.WithoutBlendShape);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/UniGLTF/Editor/MeshUtility/TabMeshSeparator.cs.meta
Normal file
11
Assets/UniGLTF/Editor/MeshUtility/TabMeshSeparator.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8921d732b66ae134db97faa11eaa9b17
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
45
Assets/UniGLTF/Editor/MeshUtility/TabStaticMeshIntegrator.cs
Normal file
45
Assets/UniGLTF/Editor/MeshUtility/TabStaticMeshIntegrator.cs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9701e1f6aaed8d14283243a7a50be2df
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -26,10 +26,7 @@ namespace UniGLTF
|
|||
private static void ImportGltfFile() => TopMenuImplementation.ImportGltfFileToGameObject();
|
||||
|
||||
[MenuItem(UserMeshUtilityPrefix + "/MeshProcessing Wizard", priority = 10)]
|
||||
private static void OpenMeshProcessingWindow() => TopMenuImplementation.OpenMeshProcessingWindow();
|
||||
|
||||
[MenuItem(UserMeshUtilityPrefix + "/Open Documents", priority = 11)]
|
||||
private static void MeshUtilityDocs() => Application.OpenURL("https://vrm.dev/en/docs/univrm/gltf/mesh_utility/");
|
||||
private static void OpenMeshProcessingWindow() => MeshUtility.MeshProcessDialog.OpenWindow();
|
||||
|
||||
#if VRM_DEVELOP
|
||||
[MenuItem(DevelopmentMenuPrefix + "/Generate Serialization Code", priority = 20)]
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ namespace UniGLTF
|
|||
{
|
||||
public static void ExportGameObjectToGltfFile()
|
||||
{
|
||||
var window = (GltfExportWindow) GltfExportWindow.GetWindow(typeof(GltfExportWindow));
|
||||
var window = (GltfExportWindow)GltfExportWindow.GetWindow(typeof(GltfExportWindow));
|
||||
window.titleContent = new GUIContent("Gltf Exporter");
|
||||
window.Show();
|
||||
}
|
||||
|
|
@ -92,19 +92,10 @@ namespace UniGLTF
|
|||
Selection.activeObject = asset;
|
||||
}
|
||||
|
||||
public static void OpenMeshProcessingWindow()
|
||||
{
|
||||
var window =
|
||||
(MeshUtility.MeshProcessDialog) EditorWindow.GetWindowWithRect(typeof(MeshUtility.MeshProcessDialog),
|
||||
new Rect(0, 0, 650, 500));
|
||||
window.titleContent = new GUIContent("Mesh Processing Window");
|
||||
window.Show();
|
||||
}
|
||||
|
||||
public static void GenerateSerializationCode()
|
||||
{
|
||||
SerializerGenerator.GenerateSerializer();
|
||||
DeserializerGenerator.GenerateSerializer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user