Merge pull request #1647 from ousttrue/refactor_mesh_integrator

MeshIntegrator整理終わり
This commit is contained in:
ousttrue 2022-05-25 20:02:39 +09:00 committed by GitHub
commit 1173a6e05e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 610 additions and 924 deletions

View File

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

View File

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

View 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;
}
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 01c8541cb3fd27f4882e3d32c37a45aa
guid: 95cda911ba335b449901767f9bee8ca8
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

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

View File

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

View File

@ -1,6 +1,6 @@
namespace UniGLTF.MeshUtility
{
enum Tabs
public enum MeshProcessDialogTabs
{
MeshSeparator,
MeshIntegrator,

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 5d049a3daed1255499aaec8b6d1a2afe
guid: 829a678062065f04582189619babae79
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 8eebeb093136b7f429c0e9e7295816b3
guid: fb47e24fc1463584fa0b6b685d75f25e
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

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

View File

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

View File

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

View File

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

View File

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