mirror of
https://github.com/vrm-c/UniVRM.git
synced 2026-05-15 07:00:10 -05:00
Merge pull request #1647 from ousttrue/refactor_mesh_integrator
MeshIntegrator整理終わり
This commit is contained in:
commit
1173a6e05e
|
|
@ -1,190 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace UniGLTF.MeshUtility
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(BoneMeshEraser.EraseBone))]
|
||||
public class EraseBoneDrawer : PropertyDrawer
|
||||
{
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
//EditorGUI.BeginProperty(position, label, property);
|
||||
|
||||
var leftWidth = 0.6f;
|
||||
var rightWidth = 1.0f - leftWidth;
|
||||
|
||||
var leftSide = new Rect(position.x, position.y, position.width * leftWidth, position.height);
|
||||
var rightSide = new Rect(position.width * leftWidth, position.y, position.width * rightWidth, position.height);
|
||||
{
|
||||
EditorGUI.PropertyField(leftSide, property.FindPropertyRelative("Bone"), new GUIContent("", ""));
|
||||
EditorGUI.PropertyField(rightSide, property.FindPropertyRelative("Erase"));
|
||||
}
|
||||
|
||||
//EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
var height = base.GetPropertyHeight(property, label);
|
||||
return height;
|
||||
}
|
||||
}
|
||||
|
||||
public class BoneMeshEraserWizard : ScriptableWizard
|
||||
{
|
||||
public const string BONE_MESH_ERASER_NAME = "BoneMeshEraser";
|
||||
const string ASSET_SUFFIX = ".asset";
|
||||
|
||||
[SerializeField]
|
||||
SkinnedMeshRenderer m_skinnedMesh;
|
||||
|
||||
[SerializeField]
|
||||
Animator m_animator;
|
||||
|
||||
[SerializeField]
|
||||
Transform EraseRoot;
|
||||
|
||||
[SerializeField]
|
||||
BoneMeshEraser.EraseBone[] m_eraseBones;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
var root = Selection.activeGameObject;
|
||||
if (root != null)
|
||||
{
|
||||
m_animator = root.GetComponent<Animator>();
|
||||
m_skinnedMesh = root.GetComponent<SkinnedMeshRenderer>();
|
||||
OnValidate();
|
||||
}
|
||||
}
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
//Debug.Log("OnValidate");
|
||||
if (m_skinnedMesh == null)
|
||||
{
|
||||
m_eraseBones = new BoneMeshEraser.EraseBone[] { };
|
||||
return;
|
||||
}
|
||||
|
||||
if (EraseRoot == null)
|
||||
{
|
||||
if (m_animator != null)
|
||||
{
|
||||
EraseRoot = m_animator.GetBoneTransform(HumanBodyBones.Head);
|
||||
//Debug.LogFormat("head: {0}", EraseRoot);
|
||||
}
|
||||
}
|
||||
|
||||
m_eraseBones = m_skinnedMesh.bones.Select(x =>
|
||||
{
|
||||
var eb = new BoneMeshEraser.EraseBone
|
||||
{
|
||||
Bone = x,
|
||||
};
|
||||
|
||||
if (EraseRoot != null)
|
||||
{
|
||||
// 首の子孫を消去
|
||||
if (eb.Bone.Ancestor().Any(y => y == EraseRoot))
|
||||
{
|
||||
//Debug.LogFormat("erase {0}", x);
|
||||
eb.Erase = true;
|
||||
}
|
||||
}
|
||||
|
||||
return eb;
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
void OnWizardUpdate()
|
||||
{
|
||||
helpString = "select target skinnedMesh and animator";
|
||||
}
|
||||
|
||||
SkinnedMeshRenderer _Erase(GameObject go)
|
||||
{
|
||||
if (go == null)
|
||||
{
|
||||
Debug.LogWarning("select root object in hierarchy");
|
||||
return null;
|
||||
}
|
||||
if (m_skinnedMesh == null)
|
||||
{
|
||||
Debug.LogWarning("no skinnedmesh");
|
||||
return null;
|
||||
}
|
||||
|
||||
var bones = m_skinnedMesh.bones;
|
||||
var eraseBones = m_eraseBones
|
||||
.Where(x => x.Erase)
|
||||
.Select(x => Array.IndexOf(bones, x.Bone))
|
||||
.ToArray();
|
||||
|
||||
var meshNode = new GameObject(BONE_MESH_ERASER_NAME);
|
||||
meshNode.transform.SetParent(go.transform, false);
|
||||
|
||||
var erased = meshNode.AddComponent<SkinnedMeshRenderer>();
|
||||
erased.sharedMesh = BoneMeshEraser.CreateErasedMesh(m_skinnedMesh.sharedMesh, eraseBones);
|
||||
erased.sharedMaterials = m_skinnedMesh.sharedMaterials;
|
||||
erased.bones = m_skinnedMesh.bones;
|
||||
|
||||
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;
|
||||
var renderer = _Erase(go);
|
||||
if (renderer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// save mesh to Assets
|
||||
var assetPath = string.Format("{0}{1}", go.name, ASSET_SUFFIX);
|
||||
var prefab = GetPrefab(go);
|
||||
if (prefab != null)
|
||||
{
|
||||
var prefabPath = AssetDatabase.GetAssetPath(prefab);
|
||||
assetPath = string.Format("{0}/{1}{2}",
|
||||
Path.GetDirectoryName(prefabPath),
|
||||
Path.GetFileNameWithoutExtension(prefabPath),
|
||||
ASSET_SUFFIX
|
||||
);
|
||||
}
|
||||
|
||||
Debug.LogFormat("CreateAsset: {0}", assetPath);
|
||||
AssetDatabase.CreateAsset(renderer.sharedMesh, assetPath);
|
||||
}
|
||||
|
||||
void OnWizardCreate()
|
||||
{
|
||||
//Debug.Log("OnWizardCreate");
|
||||
Erase();
|
||||
|
||||
// close
|
||||
}
|
||||
|
||||
void OnWizardOtherButton()
|
||||
{
|
||||
//Debug.Log("OnWizardOtherButton");
|
||||
Erase();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 881b00db73f639c48a3f043a775fa61a
|
||||
timeCreated: 1518503829
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
57
Assets/UniGLTF/Editor/MeshUtility/GameObjectType.cs
Normal file
57
Assets/UniGLTF/Editor/MeshUtility/GameObjectType.cs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniGLTF.MeshUtility
|
||||
{
|
||||
public enum GameObjectType
|
||||
{
|
||||
Null,
|
||||
SceneInstance,
|
||||
PrefabInstance,
|
||||
AssetPrefab,
|
||||
}
|
||||
|
||||
public static class GameObjectTypeUtility
|
||||
{
|
||||
public static Object GetPrefab(this GameObject instance)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(instance)))
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
|
||||
#if UNITY_2018_2_OR_NEWER
|
||||
return PrefabUtility.GetCorrespondingObjectFromSource(instance);
|
||||
#else
|
||||
return PrefabUtility.GetPrefabParent(go);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static GameObjectType GetGameObjectType(this GameObject go)
|
||||
{
|
||||
if (go == null)
|
||||
{
|
||||
return GameObjectType.Null;
|
||||
}
|
||||
|
||||
if (!go.scene.IsValid())
|
||||
{
|
||||
if (PrefabUtility.IsPartOfAnyPrefab(go))
|
||||
{
|
||||
return GameObjectType.AssetPrefab;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new System.NotSupportedException("Unknown prefab status. The target does not exist in a valid scene and is not a prefab.");
|
||||
}
|
||||
}
|
||||
|
||||
if (PrefabUtility.IsPartOfAnyPrefab(go))
|
||||
{
|
||||
return GameObjectType.PrefabInstance;
|
||||
}
|
||||
|
||||
return GameObjectType.SceneInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 01c8541cb3fd27f4882e3d32c37a45aa
|
||||
guid: 95cda911ba335b449901767f9bee8ca8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
|
@ -1,48 +1,34 @@
|
|||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UniGLTF;
|
||||
using UniGLTF.M17N;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UniGLTF.MeshUtility
|
||||
{
|
||||
public class MeshProcessDialog : EditorWindow
|
||||
{
|
||||
private Tabs _tab;
|
||||
const string TITLE = "Mesh Processing Window";
|
||||
MeshProcessDialogTabs _tab;
|
||||
|
||||
private GameObject _exportTarget;
|
||||
|
||||
[SerializeField]
|
||||
public bool _separateByBlendShape = true;
|
||||
|
||||
[SerializeField]
|
||||
public SkinnedMeshRenderer _skinnedMeshRenderer = null;
|
||||
|
||||
[SerializeField]
|
||||
public List<BoneMeshEraser.EraseBone> _eraseBones;
|
||||
|
||||
private MeshProcessDialogEditor _boneMeshEraserEditor;
|
||||
|
||||
private SkinnedMeshRenderer _pSkinnedMesh;
|
||||
private Animator _pAnimator;
|
||||
private Transform _pEraseRoot;
|
||||
private Vector2 _scrollPos = new Vector2(0, 0);
|
||||
|
||||
[SerializeField]
|
||||
private SkinnedMeshRenderer _cSkinnedMesh = null;
|
||||
|
||||
[SerializeField]
|
||||
private bool _separateByBlendShape = true;
|
||||
|
||||
private Animator _cAnimator = null;
|
||||
private Transform _cEraseRoot = null;
|
||||
|
||||
[SerializeField]
|
||||
private BoneMeshEraser.EraseBone[] _eraseBones;
|
||||
|
||||
private MethodInfo _processFunction;
|
||||
|
||||
GUIStyle _tabButtonStyle => "LargeButton";
|
||||
GUI.ToolbarButtonSize _tabButtonSize => GUI.ToolbarButtonSize.Fixed;
|
||||
|
||||
|
||||
public static void OpenWindow()
|
||||
{
|
||||
var window =
|
||||
(MeshProcessDialog)EditorWindow.GetWindow(typeof(MeshProcessDialog));
|
||||
window.titleContent = new GUIContent("Mesh Processing Window");
|
||||
window.titleContent = new GUIContent(TITLE);
|
||||
window.Show();
|
||||
}
|
||||
|
||||
|
|
@ -54,66 +40,66 @@ namespace UniGLTF.MeshUtility
|
|||
}
|
||||
}
|
||||
|
||||
static bool IsGameObjectSelected()
|
||||
{
|
||||
return Selection.activeObject != null && Selection.activeObject is GameObject;
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
_scrollPos = EditorGUILayout.BeginScrollView(_scrollPos);
|
||||
EditorGUIUtility.labelWidth = 300;
|
||||
// lang
|
||||
EditorGUIUtility.labelWidth = 200;
|
||||
LanguageGetter.OnGuiSelectLang();
|
||||
_exportTarget = (GameObject)EditorGUILayout.ObjectField(MeshProcessingMessages.TARGET_OBJECT.Msg(), _exportTarget, typeof(GameObject), true);
|
||||
_tab = TabBar.OnGUI(_tab, "LargeButton", GUI.ToolbarButtonSize.Fixed);
|
||||
|
||||
{
|
||||
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 && IsGameObjectSelected())
|
||||
{
|
||||
_exportTarget = Selection.activeObject as GameObject;
|
||||
}
|
||||
}
|
||||
|
||||
// tab
|
||||
_tab = TabBar.OnGUI(_tab, _tabButtonStyle, _tabButtonSize);
|
||||
var processed = false;
|
||||
switch (_tab)
|
||||
{
|
||||
case Tabs.MeshSeparator:
|
||||
EditorGUILayout.HelpBox(MeshProcessingMessages.MESH_SEPARATOR.Msg(), MessageType.Info);
|
||||
processed = TabMeshSeparator.OnGUI(_exportTarget);
|
||||
break;
|
||||
case MeshProcessDialogTabs.MeshSeparator:
|
||||
{
|
||||
EditorGUILayout.HelpBox(MeshProcessingMessages.MESH_SEPARATOR.Msg(), MessageType.Info);
|
||||
if (TabMeshSeparator.TryExecutable(_exportTarget, out string msg))
|
||||
{
|
||||
processed = TabMeshSeparator.OnGUI(_exportTarget);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.HelpBox(msg, MessageType.Error);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Tabs.MeshIntegrator:
|
||||
EditorGUILayout.HelpBox(MeshProcessingMessages.MESH_INTEGRATOR.Msg(), MessageType.Info);
|
||||
_boneMeshEraserEditor.Tabs = _tab;
|
||||
if (_boneMeshEraserEditor)
|
||||
case MeshProcessDialogTabs.MeshIntegrator:
|
||||
{
|
||||
_boneMeshEraserEditor.OnInspectorGUI();
|
||||
EditorGUILayout.HelpBox(MeshProcessingMessages.MESH_INTEGRATOR.Msg(), MessageType.Info);
|
||||
_separateByBlendShape = EditorGUILayout.Toggle(MeshProcessingMessages.MESH_SEPARATOR_BY_BLENDSHAPE.Msg(), _separateByBlendShape);
|
||||
if (TabMeshIntegrator.TryExecutable(_exportTarget, out string msg))
|
||||
{
|
||||
if (GUILayout.Button("Process", GUILayout.MinWidth(100)))
|
||||
{
|
||||
processed = TabMeshIntegrator.Execute(_exportTarget, _separateByBlendShape);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.HelpBox(msg, MessageType.Error);
|
||||
}
|
||||
break;
|
||||
}
|
||||
processed = TabMeshIntegrator.OnGUI(_exportTarget, _separateByBlendShape);
|
||||
break;
|
||||
|
||||
case Tabs.BoneMeshEraser:
|
||||
EditorGUILayout.HelpBox(MeshProcessingMessages.BONE_MESH_ERASER.Msg(), MessageType.Info);
|
||||
_boneMeshEraserEditor.Tabs = _tab;
|
||||
if (_boneMeshEraserEditor)
|
||||
case MeshProcessDialogTabs.BoneMeshEraser:
|
||||
{
|
||||
_boneMeshEraserEditor.OnInspectorGUI();
|
||||
EditorGUILayout.HelpBox(MeshProcessingMessages.BONE_MESH_ERASER.Msg(), MessageType.Info);
|
||||
if (_boneMeshEraserEditor)
|
||||
{
|
||||
_boneMeshEraserEditor.OnInspectorGUI();
|
||||
}
|
||||
if (TabBoneMeshRemover.TryExecutable(_exportTarget, _skinnedMeshRenderer, out string msg))
|
||||
{
|
||||
processed = TabBoneMeshRemover.OnGUI(_exportTarget, _skinnedMeshRenderer, _eraseBones);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.HelpBox(msg, MessageType.Error);
|
||||
}
|
||||
break;
|
||||
}
|
||||
// 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();
|
||||
|
||||
|
|
@ -123,45 +109,5 @@ namespace UniGLTF.MeshUtility
|
|||
GUIUtility.ExitGUI();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void BoneMeshEraserValidate()
|
||||
{
|
||||
if (_cSkinnedMesh == null)
|
||||
{
|
||||
_eraseBones = new BoneMeshEraser.EraseBone[] { };
|
||||
return;
|
||||
}
|
||||
|
||||
if (_cEraseRoot == null)
|
||||
{
|
||||
if (_cAnimator != null)
|
||||
{
|
||||
_cEraseRoot = _cAnimator.GetBoneTransform(HumanBodyBones.Head);
|
||||
//Debug.LogFormat("head: {0}", EraseRoot);
|
||||
}
|
||||
}
|
||||
|
||||
_eraseBones = _cSkinnedMesh.bones.Select(x =>
|
||||
{
|
||||
var eb = new BoneMeshEraser.EraseBone
|
||||
{
|
||||
Bone = x,
|
||||
};
|
||||
|
||||
if (_cEraseRoot != null)
|
||||
{
|
||||
// 首の子孫を消去
|
||||
if (eb.Bone.Ancestor().Any(y => y == _cEraseRoot))
|
||||
{
|
||||
//Debug.LogFormat("erase {0}", x);
|
||||
eb.Erase = true;
|
||||
}
|
||||
}
|
||||
|
||||
return eb;
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,32 +4,39 @@ using UnityEngine;
|
|||
|
||||
namespace UniGLTF.MeshUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// BoneMeshRemover 向けのエディタ。
|
||||
///
|
||||
/// SerializedProperty 経由で ユーザー定義 struct のフィールド
|
||||
/// public List<BoneMeshEraser.EraseBone> _eraseBones;
|
||||
/// を EditorGUILayout.PropertyField するための細工である。
|
||||
///
|
||||
/// SerializedObject は UnityEngine.Object から作成するので、
|
||||
/// UnityEngine.Object を継承したクラスのフィールドに ユーザー定義 struct を配置する。
|
||||
/// 持ち主の SerializedObject を経由して EditorGUILayout.PropertyField してる。
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(MeshProcessDialog), true)]
|
||||
class MeshProcessDialogEditor : Editor
|
||||
{
|
||||
public Tabs Tabs;
|
||||
MeshProcessDialog _targetDialog;
|
||||
SerializedProperty _skinnedMesh;
|
||||
SerializedProperty _eraseBones;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
_targetDialog = target as MeshProcessDialog;
|
||||
if (_targetDialog)
|
||||
{
|
||||
_skinnedMesh = serializedObject.FindProperty(nameof(MeshProcessDialog._skinnedMeshRenderer));
|
||||
_eraseBones = serializedObject.FindProperty(nameof(MeshProcessDialog._eraseBones));
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
EditorGUILayout.PropertyField(_skinnedMesh);
|
||||
EditorGUILayout.PropertyField(_eraseBones);
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
namespace UniGLTF.MeshUtility
|
||||
{
|
||||
enum Tabs
|
||||
public enum MeshProcessDialogTabs
|
||||
{
|
||||
MeshSeparator,
|
||||
MeshIntegrator,
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5d049a3daed1255499aaec8b6d1a2afe
|
||||
guid: 829a678062065f04582189619babae79
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
|
@ -80,8 +80,8 @@ namespace UniGLTF.MeshUtility
|
|||
[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")]
|
||||
[LangMsg(Languages.ja, "BlendShapeClipが不整合を起こすので、`VRM0-> MeshIntegrator`を使ってください")]
|
||||
[LangMsg(Languages.en, "Because BlendShapeClip causes inconsistency , use `VRM0 -> MeshIntegrator` instead")]
|
||||
VRM_DETECTED,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UniGLTF.M17N;
|
||||
|
|
@ -9,9 +10,28 @@ namespace UniGLTF.MeshUtility
|
|||
{
|
||||
public static class TabBoneMeshRemover
|
||||
{
|
||||
const string BONE_MESH_ERASER_NAME = "BoneMeshEraser";
|
||||
const string ASSET_SUFFIX = ".mesh.asset";
|
||||
|
||||
public static bool OnGUI(GameObject root, SkinnedMeshRenderer smr, BoneMeshEraser.EraseBone[] eraseBones)
|
||||
public static bool TryExecutable(GameObject root, SkinnedMeshRenderer smr, out string msg)
|
||||
{
|
||||
if (root == null)
|
||||
{
|
||||
msg = MeshProcessingMessages.NO_GAMEOBJECT_SELECTED.Msg();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (smr == null)
|
||||
{
|
||||
msg = MeshProcessingMessages.SELECT_SKINNED_MESH.Msg();
|
||||
return false;
|
||||
}
|
||||
|
||||
msg = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool OnGUI(GameObject root, SkinnedMeshRenderer smr, List<BoneMeshEraser.EraseBone> eraseBones)
|
||||
{
|
||||
var _isInvokeSuccess = false;
|
||||
GUILayout.BeginVertical();
|
||||
|
|
@ -28,23 +48,11 @@ namespace UniGLTF.MeshUtility
|
|||
return _isInvokeSuccess;
|
||||
}
|
||||
|
||||
private static bool Execute(GameObject root, SkinnedMeshRenderer smr, BoneMeshEraser.EraseBone[] eraseBones)
|
||||
private static bool Execute(GameObject root, SkinnedMeshRenderer smr, List<BoneMeshEraser.EraseBone> eraseBones)
|
||||
{
|
||||
if (root == null)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_GAMEOBJECT_SELECTED.Msg(), "ok");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (smr == null)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.SELECT_SKINNED_MESH.Msg(), "ok");
|
||||
return false;
|
||||
}
|
||||
|
||||
var bones = smr.bones;
|
||||
|
||||
var meshNode = new GameObject(BoneMeshEraserWizard.BONE_MESH_ERASER_NAME);
|
||||
var meshNode = new GameObject(BONE_MESH_ERASER_NAME);
|
||||
meshNode.transform.SetParent(root.transform, false);
|
||||
|
||||
var erased = meshNode.AddComponent<SkinnedMeshRenderer>();
|
||||
|
|
@ -74,7 +82,7 @@ namespace UniGLTF.MeshUtility
|
|||
// destroy BoneMeshEraser in the source
|
||||
foreach (var skinnedMesh in root.GetComponentsInChildren<SkinnedMeshRenderer>())
|
||||
{
|
||||
if (skinnedMesh.gameObject.name == BoneMeshEraserWizard.BONE_MESH_ERASER_NAME)
|
||||
if (skinnedMesh.gameObject.name == BONE_MESH_ERASER_NAME)
|
||||
{
|
||||
GameObject.DestroyImmediate(skinnedMesh.gameObject);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using UniGLTF.M17N;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
|
@ -7,23 +7,29 @@ namespace UniGLTF.MeshUtility
|
|||
{
|
||||
public static class TabMeshIntegrator
|
||||
{
|
||||
const string ASSET_SUFFIX = ".mesh.asset";
|
||||
|
||||
public static bool OnGUI(GameObject root, bool onlyBlendShapeRenderers)
|
||||
public static bool TryExecutable(GameObject root, out string msg)
|
||||
{
|
||||
var _isInvokeSuccess = false;
|
||||
GUILayout.BeginVertical();
|
||||
// check
|
||||
if (root == null)
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
if (GUILayout.Button("Process", GUILayout.MinWidth(100)))
|
||||
{
|
||||
_isInvokeSuccess = TabMeshIntegrator.Execute(root, onlyBlendShapeRenderers);
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
msg = MeshProcessingMessages.NO_GAMEOBJECT_SELECTED.Msg();
|
||||
return false;
|
||||
}
|
||||
GUILayout.EndVertical();
|
||||
return _isInvokeSuccess;
|
||||
|
||||
if (HasVrm(root))
|
||||
{
|
||||
msg = MeshProcessingMessages.VRM_DETECTED.Msg();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (root.GetComponentsInChildren<SkinnedMeshRenderer>().Length == 0 && root.GetComponentsInChildren<MeshFilter>().Length == 0)
|
||||
{
|
||||
msg = MeshProcessingMessages.NO_MESH.Msg();
|
||||
return false;
|
||||
}
|
||||
|
||||
msg = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
const string VRM_META = "VRMMeta";
|
||||
|
|
@ -42,107 +48,57 @@ namespace UniGLTF.MeshUtility
|
|||
return false;
|
||||
}
|
||||
|
||||
static bool Execute(GameObject root, bool onlyBlendShapeRenderers)
|
||||
/// <param name="src">GameObject instance in scene or prefab</param>
|
||||
public static bool Execute(GameObject src, bool onlyBlendShapeRenderers)
|
||||
{
|
||||
if (root == null)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_GAMEOBJECT_SELECTED.Msg(), "ok");
|
||||
return false;
|
||||
}
|
||||
var results = new List<MeshIntegrationResult>();
|
||||
|
||||
if (HasVrm(root))
|
||||
{
|
||||
EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.VRM_DETECTED.Msg(), "ok");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (root.GetComponentsInChildren<SkinnedMeshRenderer>().Length == 0 && root.GetComponentsInChildren<MeshFilter>().Length == 0)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_MESH.Msg(), "ok");
|
||||
return false;
|
||||
}
|
||||
// instance or prefab => copy
|
||||
var copy = GameObject.Instantiate(src);
|
||||
copy.name = copy.name + "_mesh_integration";
|
||||
|
||||
// integrate
|
||||
if (onlyBlendShapeRenderers)
|
||||
{
|
||||
MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithBlendShape);
|
||||
MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithoutBlendShape);
|
||||
results.Add(MeshIntegratorUtility.Integrate(copy, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithBlendShape));
|
||||
results.Add(MeshIntegratorUtility.Integrate(copy, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithoutBlendShape));
|
||||
}
|
||||
else
|
||||
{
|
||||
MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.All);
|
||||
results.Add(MeshIntegratorUtility.Integrate(copy, onlyBlendShapeRenderers: MeshEnumerateOption.All));
|
||||
}
|
||||
|
||||
CopyAndSaveAssetEtc(root);
|
||||
// replace
|
||||
MeshIntegratorUtility.ReplaceMeshWithResults(copy, results);
|
||||
|
||||
// write mesh asset.
|
||||
foreach (var result in results)
|
||||
{
|
||||
var mesh = result.IntegratedRenderer.sharedMesh;
|
||||
var assetPath = MeshIntegratorUtility.GetMeshWritePath(mesh);
|
||||
Debug.LogFormat("CreateAsset: {0}", assetPath);
|
||||
AssetDatabase.CreateAsset(mesh, assetPath);
|
||||
}
|
||||
|
||||
if (src.GetGameObjectType() == GameObjectType.AssetPrefab)
|
||||
{
|
||||
// write prefab.
|
||||
{
|
||||
var prefabPath = UnityPath.FromAsset(src);
|
||||
prefabPath = prefabPath.Parent.Child($"{prefabPath.FileNameWithoutExtension}_integrated.prefab");
|
||||
Debug.LogFormat("WritePrefab: {0}", prefabPath);
|
||||
PrefabUtility.SaveAsPrefabAsset(copy, prefabPath.Value);
|
||||
}
|
||||
|
||||
// destroy copy in scene.
|
||||
GameObject.DestroyImmediate(copy);
|
||||
}
|
||||
else
|
||||
{
|
||||
// do nothing. keep copy.
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,15 +12,32 @@ namespace UniGLTF.MeshUtility
|
|||
/// </summary>
|
||||
public static class TabMeshSeparator
|
||||
{
|
||||
private static readonly Vector3 ZERO_MOVEMENT = Vector3.zero;
|
||||
const string ASSET_SUFFIX = ".mesh.asset";
|
||||
private const string ASSET_SUFFIX = ".mesh.asset";
|
||||
|
||||
enum BlendShapeLogic
|
||||
private enum BlendShapeLogic
|
||||
{
|
||||
WithBlendShape,
|
||||
WithoutBlendShape,
|
||||
}
|
||||
|
||||
public static bool TryExecutable(GameObject root, out string msg)
|
||||
{
|
||||
if (root == null)
|
||||
{
|
||||
msg = MeshProcessingMessages.NO_GAMEOBJECT_SELECTED.Msg();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (root.GetComponentsInChildren<SkinnedMeshRenderer>().Length == 0)
|
||||
{
|
||||
msg = MeshProcessingMessages.NO_SKINNED_MESH.Msg();
|
||||
return false;
|
||||
}
|
||||
|
||||
msg = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool OnGUI(GameObject root)
|
||||
{
|
||||
var _isInvokeSuccess = false;
|
||||
|
|
@ -38,36 +55,25 @@ namespace UniGLTF.MeshUtility
|
|||
return _isInvokeSuccess;
|
||||
}
|
||||
|
||||
static bool Execute(GameObject root)
|
||||
private static bool Execute(GameObject root)
|
||||
{
|
||||
if (root == null)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_GAMEOBJECT_SELECTED.Msg(), "ok");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (root.GetComponentsInChildren<SkinnedMeshRenderer>().Length == 0)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Failed", MeshProcessingMessages.NO_SKINNED_MESH.Msg(), "ok");
|
||||
return false;
|
||||
}
|
||||
|
||||
// copy
|
||||
var outputObject = GameObject.Instantiate(root);
|
||||
outputObject.name = outputObject.name + "_mesh_separation";
|
||||
|
||||
// 改変と asset の作成
|
||||
var list = SeparationProcessing(outputObject);
|
||||
|
||||
// asset の永続化
|
||||
foreach (var (src, with, without) in list)
|
||||
{
|
||||
// asset の永続化
|
||||
SaveMesh(src, with, BlendShapeLogic.WithBlendShape);
|
||||
SaveMesh(src, without, BlendShapeLogic.WithoutBlendShape);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void SaveMesh(Mesh mesh, Mesh newMesh, BlendShapeLogic blendShapeLabel)
|
||||
private 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);
|
||||
|
|
@ -98,7 +104,7 @@ namespace UniGLTF.MeshUtility
|
|||
/// </summary>
|
||||
/// <param name="go"></param>
|
||||
/// <return>(Mesh 分割前, Mesh BlendShape有り、Mesh BlendShape無し)のリストを返す</return>
|
||||
public static List<(Mesh Src, Mesh With, Mesh Without)> SeparationProcessing(GameObject go)
|
||||
private 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>();
|
||||
|
|
@ -131,7 +137,7 @@ namespace UniGLTF.MeshUtility
|
|||
|
||||
for (int j = 0; j < deltaVertices.Length; j++)
|
||||
{
|
||||
if (!deltaVertices[j].Equals(ZERO_MOVEMENT))
|
||||
if (!deltaVertices[j].Equals(Vector3.zero))
|
||||
{
|
||||
if (!indicesUsedByBlendShape.Values.Contains(j))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 57ace5e28eb8e7746b00eaa9405197a9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using VRMShaders;
|
||||
using ColorSpace = VRMShaders.ColorSpace;
|
||||
|
||||
namespace UniGLTF.MeshUtility
|
||||
{
|
||||
public static class EditorChangeTextureType
|
||||
{
|
||||
public static void SaveAsPng(bool sRGB)
|
||||
{
|
||||
var texture = Selection.activeObject as Texture2D;
|
||||
var path = SaveDialog(AssetsPath.FromAsset(texture));
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var (tex, mime) = new EditorTextureSerializer().ExportBytesWithMime(texture, sRGB ? ColorSpace.sRGB : ColorSpace.Linear);
|
||||
|
||||
File.WriteAllBytes(path, tex);
|
||||
Debug.Log($"save: {path}");
|
||||
|
||||
var assetsPath = AssetsPath.FromFullpath(path);
|
||||
|
||||
EditorApplication.delayCall += () =>
|
||||
{
|
||||
assetsPath.ImportAsset();
|
||||
var importer = assetsPath.GetImporter<TextureImporter>();
|
||||
if (importer == null)
|
||||
{
|
||||
Debug.LogWarningFormat("fail to get TextureImporter: {0}", assetsPath);
|
||||
}
|
||||
importer.sRGBTexture = sRGB;
|
||||
importer.SaveAndReimport();
|
||||
Debug.Log($"sRGB: {sRGB}");
|
||||
};
|
||||
}
|
||||
|
||||
private static string m_lastExportDir;
|
||||
static string SaveDialog(AssetsPath assetsPath)
|
||||
{
|
||||
// save dialog
|
||||
var path = EditorUtility.SaveFilePanel(
|
||||
"Save png",
|
||||
assetsPath.Parent.FullPath,
|
||||
$"{assetsPath.FileNameWithoutExtension}.png",
|
||||
"png");
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
m_lastExportDir = Path.GetDirectoryName(path).Replace("\\", "/");
|
||||
}
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,8 +6,9 @@ namespace UniGLTF.MeshUtility
|
|||
{
|
||||
public class MeshIntegrator
|
||||
{
|
||||
public const string INTEGRATED_MESH_NAME = "MeshesIntegrated";
|
||||
public const string INTEGRATED_MESH_BLENDSHAPE_NAME = "MeshesBlendShapeIntegrated";
|
||||
public const string INTEGRATED_MESH_WITHOUT_BLENDSHAPE_NAME = "Integrated(WithoutBlendShape)";
|
||||
public const string INTEGRATED_MESH_WITH_BLENDSHAPE_NAME = "Integrated(WithBlendShape)";
|
||||
public const string INTEGRATED_MESH_ALL_NAME = "Integrated(All)";
|
||||
|
||||
struct SubMesh
|
||||
{
|
||||
|
|
@ -25,7 +26,7 @@ namespace UniGLTF.MeshUtility
|
|||
public Vector3[] Tangents;
|
||||
}
|
||||
|
||||
public MeshIntegrationResult Result { get; } = new MeshIntegrationResult();
|
||||
MeshIntegrationResult Result { get; } = new MeshIntegrationResult();
|
||||
List<Vector3> Positions { get; } = new List<Vector3>();
|
||||
List<Vector3> Normals { get; } = new List<Vector3>();
|
||||
List<Vector2> UV { get; } = new List<Vector2>();
|
||||
|
|
@ -230,7 +231,7 @@ namespace UniGLTF.MeshUtility
|
|||
}
|
||||
}
|
||||
|
||||
public void Intgrate(MeshEnumerateOption onlyBlendShapeRenderers)
|
||||
public MeshIntegrationResult Integrate(MeshEnumerateOption onlyBlendShapeRenderers)
|
||||
{
|
||||
var mesh = new Mesh();
|
||||
|
||||
|
|
@ -252,34 +253,47 @@ namespace UniGLTF.MeshUtility
|
|||
}
|
||||
mesh.bindposes = BindPoses.ToArray();
|
||||
|
||||
// blendshape
|
||||
switch (onlyBlendShapeRenderers)
|
||||
{
|
||||
case MeshEnumerateOption.OnlyWithBlendShape:
|
||||
{
|
||||
AddBlendShapesToMesh(mesh);
|
||||
mesh.name = INTEGRATED_MESH_BLENDSHAPE_NAME;
|
||||
mesh.name = INTEGRATED_MESH_WITH_BLENDSHAPE_NAME;
|
||||
break;
|
||||
}
|
||||
case MeshEnumerateOption.OnlyWithoutBlendShape:
|
||||
|
||||
case MeshEnumerateOption.All:
|
||||
{
|
||||
mesh.name = INTEGRATED_MESH_NAME;
|
||||
AddBlendShapesToMesh(mesh);
|
||||
mesh.name = INTEGRATED_MESH_ALL_NAME;
|
||||
break;
|
||||
}
|
||||
|
||||
case MeshEnumerateOption.OnlyWithoutBlendShape:
|
||||
{
|
||||
mesh.name = INTEGRATED_MESH_WITHOUT_BLENDSHAPE_NAME;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// meshName
|
||||
var meshNode = new GameObject();
|
||||
switch (onlyBlendShapeRenderers)
|
||||
{
|
||||
case MeshEnumerateOption.OnlyWithBlendShape:
|
||||
{
|
||||
meshNode.name = "MeshIntegrator(BlendShape)";
|
||||
meshNode.name = INTEGRATED_MESH_WITH_BLENDSHAPE_NAME;
|
||||
break;
|
||||
}
|
||||
case MeshEnumerateOption.OnlyWithoutBlendShape:
|
||||
{
|
||||
meshNode.name = INTEGRATED_MESH_WITHOUT_BLENDSHAPE_NAME;
|
||||
break;
|
||||
}
|
||||
case MeshEnumerateOption.All:
|
||||
{
|
||||
meshNode.name = "MeshIntegrator";
|
||||
meshNode.name = INTEGRATED_MESH_ALL_NAME;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -290,6 +304,7 @@ namespace UniGLTF.MeshUtility
|
|||
integrated.bones = Bones.ToArray();
|
||||
Result.IntegratedRenderer = integrated;
|
||||
Result.MeshMap.Integrated = mesh;
|
||||
return Result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniGLTF.MeshUtility
|
||||
{
|
||||
public static class MeshIntegratorUtility
|
||||
{
|
||||
public static string INTEGRATED_MESH_NAME => MeshIntegrator.INTEGRATED_MESH_NAME;
|
||||
public static string INTEGRATED_MESH_BLENDSHAPE_NAME => MeshIntegrator.INTEGRATED_MESH_BLENDSHAPE_NAME;
|
||||
|
||||
const string ASSET_SUFFIX = ".mesh.asset";
|
||||
|
||||
/// <summary>
|
||||
/// go を root としたヒエラルキーから Renderer を集めて、統合された Mesh 作成する
|
||||
|
|
@ -19,7 +19,9 @@ namespace UniGLTF.MeshUtility
|
|||
/// null: すべてのSkinnedMeshRenderer + MeshRenderer
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
public static MeshIntegrationResult Integrate(GameObject go, MeshEnumerateOption onlyBlendShapeRenderers, IEnumerable<Mesh> excludes = null)
|
||||
public static MeshIntegrationResult Integrate(GameObject go, MeshEnumerateOption onlyBlendShapeRenderers,
|
||||
IEnumerable<Mesh> excludes = null,
|
||||
bool destroyIntegratedRenderer = false)
|
||||
{
|
||||
var exclude = new MeshExclude(excludes);
|
||||
|
||||
|
|
@ -87,9 +89,7 @@ namespace UniGLTF.MeshUtility
|
|||
}
|
||||
}
|
||||
|
||||
integrator.Intgrate(onlyBlendShapeRenderers);
|
||||
integrator.Result.IntegratedRenderer.transform.SetParent(go.transform, false);
|
||||
return integrator.Result;
|
||||
return integrator.Integrate(onlyBlendShapeRenderers);
|
||||
}
|
||||
|
||||
public static IEnumerable<SkinnedMeshRenderer> EnumerateSkinnedMeshRenderer(Transform root, MeshEnumerateOption hasBlendShape)
|
||||
|
|
@ -160,5 +160,41 @@ namespace UniGLTF.MeshUtility
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void ReplaceMeshWithResults(GameObject copy, List<MeshIntegrationResult> results)
|
||||
{
|
||||
// destroy original meshes
|
||||
foreach (var skinnedMesh in copy.GetComponentsInChildren<SkinnedMeshRenderer>())
|
||||
{
|
||||
GameObject.DestroyImmediate(skinnedMesh);
|
||||
}
|
||||
foreach (var normalMesh in copy.GetComponentsInChildren<MeshFilter>())
|
||||
{
|
||||
if (normalMesh.gameObject.GetComponent<MeshRenderer>())
|
||||
{
|
||||
GameObject.DestroyImmediate(normalMesh.gameObject.GetComponent<MeshRenderer>());
|
||||
}
|
||||
GameObject.DestroyImmediate(normalMesh);
|
||||
}
|
||||
|
||||
// Add integrated
|
||||
foreach (var result in results)
|
||||
{
|
||||
result.IntegratedRenderer.transform.SetParent(copy.transform, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetMeshWritePath(Mesh mesh)
|
||||
{
|
||||
if (!string.IsNullOrEmpty((AssetDatabase.GetAssetPath(mesh))))
|
||||
{
|
||||
var directory = Path.GetDirectoryName(AssetDatabase.GetAssetPath(mesh)).Replace("\\", "/");
|
||||
return $"{directory}/{Path.GetFileNameWithoutExtension(mesh.name)}{ASSET_SUFFIX}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"Assets/{Path.GetFileNameWithoutExtension(mesh.name)}{ASSET_SUFFIX}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,122 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
|
||||
namespace UniGLTF.MeshUtility
|
||||
{
|
||||
public static class StaticMeshIntegrator
|
||||
{
|
||||
const string ASSET_SUFFIX = ".mesh.asset";
|
||||
|
||||
class Integrator
|
||||
{
|
||||
List<Vector3> m_positions = new List<Vector3>();
|
||||
List<Vector3> m_normals = new List<Vector3>();
|
||||
List<Vector2> m_uv = new List<Vector2>();
|
||||
/*
|
||||
List<Vector2> m_uv2 = new List<Vector2>(); // ToDo
|
||||
List<Vector2> m_uv3 = new List<Vector2>(); // ToDo
|
||||
List<Vector2> m_uv4 = new List<Vector2>(); // ToDo
|
||||
List<Color> m_colors = new List<Color>(); // ToDo
|
||||
*/
|
||||
|
||||
List<int[]> m_subMeshes = new List<int[]>();
|
||||
|
||||
List<Material> m_materials = new List<Material>();
|
||||
public List<Material> Materials
|
||||
{
|
||||
get { return m_materials; }
|
||||
}
|
||||
|
||||
public void Push(Matrix4x4 localToRoot, Mesh mesh, Material[] materials)
|
||||
{
|
||||
var offset = m_positions.Count;
|
||||
|
||||
var hasNormal = m_normals.Count == m_positions.Count;
|
||||
var hasUv = m_uv.Count == m_positions.Count;
|
||||
|
||||
// attributes
|
||||
m_positions.AddRange(mesh.vertices.Select(x => localToRoot.MultiplyPoint(x)));
|
||||
if(mesh.normals!=null && mesh.normals.Length == mesh.vertexCount)
|
||||
{
|
||||
if (!hasNormal) for (int i = m_normals.Count; i < m_positions.Count; ++i) m_normals.Add(Vector3.zero);
|
||||
m_normals.AddRange(mesh.normals.Select(x => localToRoot.MultiplyVector(x)));
|
||||
}
|
||||
if (mesh.uv != null && mesh.uv.Length == mesh.vertexCount)
|
||||
{
|
||||
if (!hasUv) for (int i = m_uv.Count; i < m_positions.Count; ++i) m_uv.Add(Vector2.zero);
|
||||
m_uv.AddRange(mesh.uv);
|
||||
}
|
||||
|
||||
// indices
|
||||
for (int i = 0; i < mesh.subMeshCount; ++i)
|
||||
{
|
||||
m_subMeshes.Add(mesh.GetIndices(i).Select(x => offset + x).ToArray());
|
||||
}
|
||||
|
||||
// materials
|
||||
m_materials.AddRange(materials);
|
||||
}
|
||||
|
||||
public Mesh ToMesh()
|
||||
{
|
||||
var mesh = new Mesh();
|
||||
mesh.name = MeshIntegratorUtility.INTEGRATED_MESH_NAME;
|
||||
|
||||
mesh.vertices = m_positions.ToArray();
|
||||
if (m_normals.Count > 0)
|
||||
{
|
||||
if (m_normals.Count < m_positions.Count) for (int i = m_normals.Count; i < m_positions.Count; ++i) m_normals.Add(Vector3.zero);
|
||||
mesh.normals = m_normals.ToArray();
|
||||
}
|
||||
if (m_uv.Count > 0)
|
||||
{
|
||||
if (m_uv.Count < m_positions.Count) for (int i = m_uv.Count; i < m_positions.Count; ++i) m_uv.Add(Vector2.zero);
|
||||
mesh.uv = m_uv.ToArray();
|
||||
}
|
||||
|
||||
mesh.subMeshCount = m_subMeshes.Count;
|
||||
for(int i=0; i<m_subMeshes.Count; ++i)
|
||||
{
|
||||
mesh.SetIndices(m_subMeshes[i], MeshTopology.Triangles, i);
|
||||
}
|
||||
|
||||
return mesh;
|
||||
}
|
||||
}
|
||||
|
||||
public struct MeshWithMaterials
|
||||
{
|
||||
public Mesh Mesh;
|
||||
public Material[] Materials;
|
||||
}
|
||||
|
||||
public static MeshWithMaterials Integrate(Transform root)
|
||||
{
|
||||
var integrator = new Integrator();
|
||||
|
||||
foreach (var t in root.Traverse())
|
||||
{
|
||||
var renderer = t.GetComponent<MeshRenderer>();
|
||||
var filter = t.GetComponent<MeshFilter>();
|
||||
if (renderer != null && filter != null && filter.sharedMesh != null
|
||||
&& renderer.sharedMaterials!=null && renderer.sharedMaterials.Length == filter.sharedMesh.subMeshCount)
|
||||
{
|
||||
integrator.Push(root.worldToLocalMatrix * t.localToWorldMatrix, filter.sharedMesh, renderer.sharedMaterials);
|
||||
}
|
||||
}
|
||||
|
||||
return new MeshWithMaterials
|
||||
{
|
||||
Mesh = integrator.ToMesh(),
|
||||
Materials = integrator.Materials.ToArray(),
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -343,8 +343,13 @@ namespace UniGLTF
|
|||
}
|
||||
|
||||
public static UnityPath FromAsset(UnityEngine.Object asset)
|
||||
{
|
||||
return new UnityPath(AssetDatabase.GetAssetPath(asset));
|
||||
{
|
||||
var assetPath = AssetDatabase.GetAssetPath(asset);
|
||||
if (string.IsNullOrEmpty(assetPath))
|
||||
{
|
||||
throw new System.ArgumentNullException();
|
||||
}
|
||||
return new UnityPath(assetPath);
|
||||
}
|
||||
|
||||
public void ImportAsset()
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using UnityEditorInternal;
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace VRM
|
||||
{
|
||||
|
|
@ -88,26 +89,7 @@ namespace VRM
|
|||
|
||||
protected virtual GameObject GetPrefab()
|
||||
{
|
||||
var assetPath = AssetDatabase.GetAssetPath(target);
|
||||
if (string.IsNullOrEmpty(assetPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
|
||||
// search prefab if nothing
|
||||
if (prefab == null && 0 < (target as BlendShapeAvatar).Clips.Count)
|
||||
{
|
||||
prefab = (target as BlendShapeAvatar).Clips[0].Prefab;
|
||||
}
|
||||
// once more, with string-based method
|
||||
if (prefab == null)
|
||||
{
|
||||
var parent = UniGLTF.UnityPath.FromUnityPath(assetPath).Parent;
|
||||
var prefabPath = parent.Parent.Child(parent.FileNameWithoutExtension + ".prefab");
|
||||
prefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath.Value);
|
||||
}
|
||||
return prefab;
|
||||
return BlendShapeClip.VrmPrefabSearch(target);
|
||||
}
|
||||
|
||||
protected virtual void OnEnable()
|
||||
|
|
|
|||
|
|
@ -231,6 +231,10 @@ namespace VRM
|
|||
// すべてのSkinnedMeshRendererを列挙する
|
||||
foreach (var renderer in m_items.Select(x => x.SkinnedMeshRenderer))
|
||||
{
|
||||
if (renderer == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var mesh = renderer.sharedMesh;
|
||||
if (mesh != null && mesh.blendShapeCount > 0)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -25,11 +25,11 @@ namespace VRM
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!r.Renderer.EnableForExport())
|
||||
{
|
||||
validation = Validation.Error($"{name}.Renderer is not active", ValidationContext.Create(extended));
|
||||
return false;
|
||||
}
|
||||
// if (!r.Renderer.EnableForExport())
|
||||
// {
|
||||
// validation = Validation.Error($"{name}.Renderer is not active", ValidationContext.Create(extended));
|
||||
// return false;
|
||||
// }
|
||||
|
||||
validation = default;
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UniGLTF;
|
||||
using UniGLTF.MeshUtility;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRM
|
||||
{
|
||||
/// <summary>
|
||||
/// Meshを統合し、統合後のMeshのBlendShapeの変化をVRMのBlendShapeClipに反映する
|
||||
/// </summary>
|
||||
public static class VRMMeshIntegratorUtility
|
||||
{
|
||||
public static List<BlendShapeClip> FollowBlendshapeRendererChange(List<MeshIntegrationResult> results, GameObject root, string assetFolder)
|
||||
{
|
||||
var clips = new List<BlendShapeClip>();
|
||||
var proxy = root.GetComponent<VRMBlendShapeProxy>();
|
||||
if (proxy == null || proxy.BlendShapeAvatar == null)
|
||||
{
|
||||
return clips;
|
||||
}
|
||||
var result = results.FirstOrDefault(x => x.IntegratedRenderer.sharedMesh.blendShapeCount > 0);
|
||||
if (result == null)
|
||||
{
|
||||
return clips;
|
||||
}
|
||||
|
||||
var rendererDict = new Dictionary<string, SkinnedMeshRenderer>();
|
||||
foreach (var x in result.SourceSkinnedMeshRenderers)
|
||||
{
|
||||
rendererDict.Add(x.transform.RelativePathFrom(root.transform), x);
|
||||
}
|
||||
var dstPath = result.IntegratedRenderer.transform.RelativePathFrom(root.transform);
|
||||
|
||||
// copy modify and write
|
||||
var clipAssetPathList = new List<string>();
|
||||
var sb = new StringBuilder();
|
||||
foreach (var src in proxy.BlendShapeAvatar.Clips)
|
||||
{
|
||||
if (src == null) continue;
|
||||
|
||||
// copy
|
||||
var copy = ScriptableObject.CreateInstance<BlendShapeClip>();
|
||||
copy.CopyFrom(src);
|
||||
copy.Prefab = null;
|
||||
|
||||
// modify
|
||||
for (var i = 0; i < copy.Values.Length; ++i)
|
||||
{
|
||||
var val = copy.Values[i];
|
||||
if (rendererDict.ContainsKey(val.RelativePath))
|
||||
{
|
||||
var srcRenderer = rendererDict[val.RelativePath];
|
||||
var name = srcRenderer.sharedMesh.GetBlendShapeName(val.Index);
|
||||
var newIndex = result.IntegratedRenderer.sharedMesh.GetBlendShapeIndex(name);
|
||||
if (newIndex == -1)
|
||||
{
|
||||
throw new KeyNotFoundException($"blendshape:{name} not found");
|
||||
}
|
||||
|
||||
val.RelativePath = dstPath;
|
||||
val.Index = newIndex;
|
||||
}
|
||||
copy.Values[i] = val;
|
||||
}
|
||||
|
||||
// write
|
||||
var assetPath = $"{assetFolder}/{copy.name}.asset";
|
||||
sb.AppendLine($"write: {assetPath}");
|
||||
AssetDatabase.CreateAsset(copy, assetPath);
|
||||
|
||||
clipAssetPathList.Add(assetPath);
|
||||
clips.Add(copy);
|
||||
}
|
||||
Debug.Log(sb.ToString());
|
||||
|
||||
{
|
||||
// create blendshape avatar & replace
|
||||
var copy = ScriptableObject.CreateInstance<BlendShapeAvatar>();
|
||||
copy.Clips.AddRange(clips);
|
||||
var assetPath = $"{assetFolder}/blendshape.asset";
|
||||
AssetDatabase.CreateAsset(copy, assetPath);
|
||||
|
||||
// assign
|
||||
proxy.BlendShapeAvatar = copy;
|
||||
}
|
||||
|
||||
return clips;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8eebeb093136b7f429c0e9e7295816b3
|
||||
guid: fb47e24fc1463584fa0b6b685d75f25e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
|
@ -26,6 +26,7 @@ namespace VRM
|
|||
None,
|
||||
NoTarget,
|
||||
HasParent,
|
||||
NotPrefab,
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
|
|
@ -100,10 +101,16 @@ namespace VRM
|
|||
private void OnEnable()
|
||||
{
|
||||
Clear(HelpMessage.Ready, ValidationError.None);
|
||||
m_root = Selection.activeGameObject;
|
||||
OnValidate();
|
||||
}
|
||||
|
||||
protected override bool DrawWizardGUI()
|
||||
{
|
||||
var t = m_root.GetGameObjectType();
|
||||
EditorGUILayout.HelpBox($"{t}", MessageType.Info);
|
||||
return base.DrawWizardGUI();
|
||||
}
|
||||
|
||||
static object GetPropertyValue(Shader shader, int i, Material m)
|
||||
{
|
||||
var propType = ShaderUtil.GetPropertyType(shader, i);
|
||||
|
|
@ -158,12 +165,19 @@ namespace VRM
|
|||
|
||||
void OnValidate()
|
||||
{
|
||||
isValid = false;
|
||||
if (m_root == null)
|
||||
{
|
||||
Clear(HelpMessage.SetTarget, ValidationError.NoTarget);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_root.GetGameObjectType() != GameObjectType.AssetPrefab)
|
||||
{
|
||||
Clear(HelpMessage.SetTarget, ValidationError.NotPrefab);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_root.transform.parent != null)
|
||||
{
|
||||
Clear(HelpMessage.InvalidTarget, ValidationError.HasParent);
|
||||
|
|
@ -214,92 +228,124 @@ namespace VRM
|
|||
|
||||
void OnWizardUpdate()
|
||||
{
|
||||
// helpString = "Set target gameobject `in scene`. Prefab not supported.";
|
||||
}
|
||||
|
||||
/// 2022.05 仕様変更
|
||||
///
|
||||
/// * prefab 専用
|
||||
/// * backup するのではなく 変更した copy を作成する。元は変えない
|
||||
/// * copy 先の統合前の renderer を disable で残さず destroy する
|
||||
/// * 実行すると mesh, blendshape, blendShape を新規に作成する
|
||||
/// * 新しいヒエラルキーを prefab に保存してから削除して終了する
|
||||
///
|
||||
void Integrate()
|
||||
{
|
||||
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))
|
||||
if (m_root.GetGameObjectType() != GameObjectType.AssetPrefab)
|
||||
{
|
||||
return;
|
||||
throw new Exception("for prefab only");
|
||||
}
|
||||
|
||||
var assetPath = UniGLTF.UnityPath.FromFullpath(path);
|
||||
if (!assetPath.IsUnderAssetsFolder)
|
||||
String folder = "Assets";
|
||||
var prefab = m_root.GetPrefab();
|
||||
if (prefab != null)
|
||||
{
|
||||
Debug.LogWarning($"{path} is not asset path");
|
||||
return;
|
||||
folder = AssetDatabase.GetAssetPath(prefab);
|
||||
Debug.Log(folder);
|
||||
}
|
||||
|
||||
// 新規で作成されるアセットはすべてこのフォルダの中に作る。上書きチェックはしない
|
||||
var assetFolder = EditorUtility.SaveFolderPanel("select asset save folder", Path.GetDirectoryName(folder), "VrmIntegrated");
|
||||
var unityPath = UniGLTF.UnityPath.FromFullpath(assetFolder);
|
||||
if (!unityPath.IsUnderAssetsFolder)
|
||||
{
|
||||
EditorUtility.DisplayDialog("asset folder", "Target folder must be in the `Assets` folder", "cancel");
|
||||
return;
|
||||
}
|
||||
assetFolder = unityPath.Value;
|
||||
|
||||
var copy = GameObject.Instantiate(m_root);
|
||||
|
||||
// 統合
|
||||
var excludes = m_excludes.Where(x => x.Exclude).Select(x => x.Mesh);
|
||||
var results = Integrate(copy, excludes, m_separateByBlendShape);
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
// write mesh asset
|
||||
foreach (var result in results)
|
||||
{
|
||||
if (result.IntegratedRenderer == null) continue;
|
||||
|
||||
var childAssetPath = assetPath.Parent.Child($"{result.IntegratedRenderer.gameObject.name}{ASSET_SUFFIX}");
|
||||
var childAssetPath = $"{assetFolder}/{result.IntegratedRenderer.gameObject.name}{ASSET_SUFFIX}";
|
||||
Debug.LogFormat("CreateAsset: {0}", childAssetPath);
|
||||
childAssetPath.CreateAsset(result.IntegratedRenderer.sharedMesh);
|
||||
Undo.RegisterCreatedObjectUndo(result.IntegratedRenderer.gameObject, "Integrate Renderers");
|
||||
AssetDatabase.CreateAsset(result.IntegratedRenderer.sharedMesh, childAssetPath);
|
||||
}
|
||||
|
||||
// Apply to Prefab
|
||||
if (UniGLTF.UnityPath.FromUnityPath(AssetDatabase.GetAssetPath(m_root)).Equals(assetPath))
|
||||
// 統合した結果をヒエラルキーに追加する
|
||||
foreach (var result in results)
|
||||
{
|
||||
VrmPrefabUtility.ApplyChangesToPrefab(instance);
|
||||
if (result.IntegratedRenderer != null)
|
||||
{
|
||||
result.IntegratedRenderer.transform.SetParent(copy.transform, false);
|
||||
}
|
||||
}
|
||||
|
||||
// 統合した結果を反映した BlendShapeClip を作成して置き換える
|
||||
var clips = VRMMeshIntegratorUtility.FollowBlendshapeRendererChange(results, copy, assetFolder);
|
||||
|
||||
// 用が済んだ 統合前 の renderer を削除する
|
||||
foreach (var result in results)
|
||||
{
|
||||
foreach (var renderer in result.SourceMeshRenderers)
|
||||
{
|
||||
GameObject.DestroyImmediate(renderer);
|
||||
}
|
||||
foreach (var renderer in result.SourceSkinnedMeshRenderers)
|
||||
{
|
||||
GameObject.DestroyImmediate(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
// reset firstperson
|
||||
var firstperson = copy.GetComponent<VRMFirstPerson>();
|
||||
if (firstperson != null)
|
||||
{
|
||||
firstperson.Reset();
|
||||
}
|
||||
|
||||
// prefab
|
||||
var prefabPath = $"{assetFolder}/VrmIntegrated.prefab";
|
||||
Debug.Log(prefabPath);
|
||||
PrefabUtility.SaveAsPrefabAsset(copy, prefabPath, out bool success);
|
||||
if (!success)
|
||||
{
|
||||
throw new System.Exception($"PrefabUtility.SaveAsPrefabAsset: {prefabPath}");
|
||||
}
|
||||
|
||||
// destroy scene
|
||||
UnityEngine.Object.DestroyImmediate(copy);
|
||||
|
||||
var prefabReference = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
var so = new SerializedObject(clip);
|
||||
so.Update();
|
||||
// clip.Prefab = copy;
|
||||
var prop = so.FindProperty("m_prefab");
|
||||
prop.objectReferenceValue = prefabReference;
|
||||
so.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
|
||||
static List<UniGLTF.MeshUtility.MeshIntegrationResult> Integrate(GameObject root, IEnumerable<Mesh> excludes, bool separateByBlendShape)
|
||||
{
|
||||
var results = new List<UniGLTF.MeshUtility.MeshIntegrationResult>();
|
||||
if (separateByBlendShape)
|
||||
{
|
||||
results.Add(MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithoutBlendShape, excludes: excludes));
|
||||
results.Add(MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithBlendShape, excludes: excludes));
|
||||
}
|
||||
else
|
||||
{
|
||||
PrefabUtility.SaveAsPrefabAsset(instance, assetPath.Value, out bool success);
|
||||
if (!success)
|
||||
{
|
||||
throw new System.Exception($"PrefabUtility.SaveAsPrefabAsset: {assetPath}");
|
||||
}
|
||||
results.Add(MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.All, excludes: excludes));
|
||||
}
|
||||
|
||||
// destroy source renderers
|
||||
UnityEngine.Object.DestroyImmediate(instance);
|
||||
return results;
|
||||
}
|
||||
|
||||
void OnWizardCreate()
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using System.Linq;
|
|||
using UniGLTF;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UniGLTF.MeshUtility;
|
||||
|
||||
namespace VRM
|
||||
{
|
||||
|
|
@ -31,7 +32,7 @@ namespace VRM
|
|||
|
||||
public static void ApplyChangesToPrefab(GameObject instance)
|
||||
{
|
||||
var prefab = GetPrefab(instance);
|
||||
var prefab = instance.GetPrefab();
|
||||
if (prefab == null)
|
||||
{
|
||||
return;
|
||||
|
|
@ -45,61 +46,5 @@ namespace VRM
|
|||
|
||||
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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
|
||||
namespace VRM
|
||||
|
|
@ -75,6 +80,45 @@ namespace VRM
|
|||
public class BlendShapeClip : ScriptableObject
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// Inspector preview 用の prefab をがんばってサーチする
|
||||
/// </summary>
|
||||
/// <param name="target"></param>
|
||||
/// <returns></returns>
|
||||
public static GameObject VrmPrefabSearch(UnityEngine.Object target)
|
||||
{
|
||||
var assetPath = AssetDatabase.GetAssetPath(target);
|
||||
if (string.IsNullOrEmpty(assetPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
|
||||
// once more, with string-based method
|
||||
if (prefab == null)
|
||||
{
|
||||
var parent = UniGLTF.UnityPath.FromUnityPath(assetPath).Parent;
|
||||
var prefabPath = parent.Parent.Child(parent.FileNameWithoutExtension + ".prefab");
|
||||
prefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath.Value);
|
||||
}
|
||||
// once more, with string-based method. search same folder *.prefab
|
||||
if (prefab == null)
|
||||
{
|
||||
var parent = UniGLTF.UnityPath.FromUnityPath(assetPath).Parent;
|
||||
foreach (var file in Directory.EnumerateFiles(parent.FullPath))
|
||||
{
|
||||
var ext = Path.GetExtension(file).ToLower();
|
||||
if (ext == ".prefab")
|
||||
{
|
||||
var prefabPath = UniGLTF.UnityPath.FromFullpath(file);
|
||||
prefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath.Value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return prefab;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Preview 用のObject参照
|
||||
/// </summary>
|
||||
|
|
@ -87,18 +131,7 @@ namespace VRM
|
|||
{
|
||||
if (m_prefab == null)
|
||||
{
|
||||
var assetPath = UnityEditor.AssetDatabase.GetAssetPath(this);
|
||||
if (!string.IsNullOrEmpty(assetPath))
|
||||
{
|
||||
// if asset is subasset of prefab
|
||||
m_prefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
|
||||
if (m_prefab == null)
|
||||
{
|
||||
var parent = UniGLTF.UnityPath.FromAsset(this).Parent;
|
||||
var prefabPath = parent.Parent.Child(parent.FileNameWithoutExtension + ".prefab");
|
||||
m_prefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath.Value);
|
||||
}
|
||||
}
|
||||
m_prefab = VrmPrefabSearch(this);
|
||||
}
|
||||
return m_prefab;
|
||||
}
|
||||
|
|
@ -142,5 +175,15 @@ namespace VRM
|
|||
/// </summary>
|
||||
[SerializeField]
|
||||
public bool IsBinary;
|
||||
|
||||
public void CopyFrom(BlendShapeClip src)
|
||||
{
|
||||
IsBinary = src.IsBinary;
|
||||
MaterialValues = src.MaterialValues.ToArray();
|
||||
Values = src.Values.ToArray();
|
||||
Preset = src.Preset;
|
||||
name = src.name;
|
||||
Prefab = src.Prefab;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,6 +91,12 @@ namespace VRM
|
|||
var rendererComponents = transform.GetComponentsInChildren<Renderer>();
|
||||
foreach (var renderer in rendererComponents)
|
||||
{
|
||||
// renderer が !enabled/!activeSelf なのがロード中なのか否か区別がつかないような気がするので
|
||||
// チェックしない。
|
||||
// if(!renderer.enabled)
|
||||
// {
|
||||
// continue;
|
||||
// }
|
||||
var flags = new RendererFirstPersonFlags
|
||||
{
|
||||
Renderer = renderer,
|
||||
|
|
|
|||
|
|
@ -1,76 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UniGLTF;
|
||||
using UniGLTF.MeshUtility;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRM
|
||||
{
|
||||
/// <summary>
|
||||
/// Meshを統合し、統合後のMeshのBlendShapeの変化をVRMのBlendShapeClipに反映する
|
||||
/// </summary>
|
||||
public static class VRMMeshIntegratorUtility
|
||||
{
|
||||
public static List<UniGLTF.MeshUtility.MeshIntegrationResult> Integrate(GameObject root, List<BlendShapeClip> blendshapeClips, IEnumerable<Mesh> excludes, bool separateByBlendShape)
|
||||
{
|
||||
var result = new List<UniGLTF.MeshUtility.MeshIntegrationResult>();
|
||||
|
||||
if (separateByBlendShape)
|
||||
{
|
||||
var withoutBlendShape = MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithoutBlendShape, excludes: excludes);
|
||||
if (withoutBlendShape.IntegratedRenderer != null)
|
||||
{
|
||||
result.Add(withoutBlendShape);
|
||||
}
|
||||
|
||||
var onlyBlendShape = MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithBlendShape, excludes: excludes);
|
||||
if (onlyBlendShape.IntegratedRenderer != null)
|
||||
{
|
||||
result.Add(onlyBlendShape);
|
||||
FollowBlendshapeRendererChange(blendshapeClips, onlyBlendShape, root);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var integrated = MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.All, excludes: excludes);
|
||||
if (integrated.IntegratedRenderer != null)
|
||||
{
|
||||
result.Add(integrated);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void FollowBlendshapeRendererChange(List<BlendShapeClip> clips, MeshIntegrationResult result, GameObject root)
|
||||
{
|
||||
if (clips == null || result == null || result.IntegratedRenderer == null || root == null) return;
|
||||
|
||||
var rendererDict = result.SourceSkinnedMeshRenderers
|
||||
.ToDictionary(x => x.transform.RelativePathFrom(root.transform), x => x);
|
||||
|
||||
var dstPath = result.IntegratedRenderer.transform.RelativePathFrom(root.transform);
|
||||
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
if (clip == null) continue;
|
||||
|
||||
for (var i = 0; i < clip.Values.Length; ++i)
|
||||
{
|
||||
var val = clip.Values[i];
|
||||
if (rendererDict.ContainsKey(val.RelativePath))
|
||||
{
|
||||
var srcRenderer = rendererDict[val.RelativePath];
|
||||
var name = srcRenderer.sharedMesh.GetBlendShapeName(val.Index);
|
||||
var newIndex = result.IntegratedRenderer.sharedMesh.GetBlendShapeIndex(name);
|
||||
|
||||
val.RelativePath = dstPath;
|
||||
val.Index = newIndex;
|
||||
}
|
||||
|
||||
clip.Values[i] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user