TargetIsPrefab and DialogMessage

This commit is contained in:
ousttrue
2023-11-21 18:19:18 +09:00
parent 0394b2a5fe
commit 6dd4320656
9 changed files with 534 additions and 440 deletions

View File

@@ -0,0 +1,82 @@
// namespace UniGLTF.MeshUtility
// {
// [Serializable]
// struct MaterialKey
// {
// public string Shader;
// public KeyValuePair<string, object>[] Properties;
// public override bool Equals(object obj)
// {
// if (!(obj is MaterialKey))
// {
// return base.Equals(obj);
// }
// var key = (MaterialKey)obj;
// return Shader == key.Shader
// && Properties.SequenceEqual(key.Properties)
// ;
// }
// public override int GetHashCode()
// {
// return base.GetHashCode();
// }
// }
// [Serializable]
// struct MaterialList
// {
// public Material[] Materials;
// public MaterialList(Material[] list)
// {
// Materials = list;
// }
// }
// static object GetPropertyValue(Shader shader, int i, Material m)
// {
// var propType = ShaderUtil.GetPropertyType(shader, i);
// switch (propType)
// {
// case ShaderUtil.ShaderPropertyType.Color:
// return m.GetColor(ShaderUtil.GetPropertyName(shader, i));
// case ShaderUtil.ShaderPropertyType.Range:
// case ShaderUtil.ShaderPropertyType.Float:
// return m.GetFloat(ShaderUtil.GetPropertyName(shader, i));
// case ShaderUtil.ShaderPropertyType.Vector:
// return m.GetVector(ShaderUtil.GetPropertyName(shader, i));
// case ShaderUtil.ShaderPropertyType.TexEnv:
// return m.GetTexture(ShaderUtil.GetPropertyName(shader, i));
// default:
// throw new NotImplementedException(propType.ToString());
// }
// }
// static MaterialKey GetMaterialKey(Material m)
// {
// var key = new MaterialKey
// {
// Shader = m.shader.name,
// };
// key.Properties = Enumerable.Range(0, ShaderUtil.GetPropertyCount(m.shader))
// .Select(x => new KeyValuePair<string, object>(
// ShaderUtil.GetPropertyName(m.shader, x),
// GetPropertyValue(m.shader, x, m))
// )
// .OrderBy(x => x.Key)
// .ToArray()
// ;
// return key;
// }
// }

View File

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

View File

@@ -3,12 +3,15 @@ using UnityEditor;
using UniGLTF.M17N;
using System.Collections.Generic;
using System.Linq;
using System.IO;
namespace UniGLTF.MeshUtility
{
public class MeshUtilityDialog : EditorWindow
{
public const string MENU_NAME = "glTF MeshUtility";
protected const string ASSET_SUFFIX = ".mesh.asset";
public static void OpenWindow()
{
var window =
@@ -21,7 +24,6 @@ namespace UniGLTF.MeshUtility
{
Freeze,
IntegrateSplit,
BoneMeshEraser,
}
protected Tabs _tab;
protected GameObject _exportTarget;
@@ -62,10 +64,109 @@ namespace UniGLTF.MeshUtility
}
}
bool IsValid => !_validations.Any(v => !v.CanExport);
MeshInfo[] integrationResults;
Vector2 _scrollPos;
void OnEnable()
{
// Clear(HelpMessage.Ready, ValidationError.None);
// OnValidate();
}
// void Clear(HelpMessage help, ValidationError error)
// {
// helpString = help.Msg();
// errorString = error != ValidationError.None ? error.Msg() : null;
// m_uniqueMaterials = new Material[] { };
// m_duplicateMaterials = new MaterialList[] { };
// m_excludes.Clear();
// isValid = false;
// }
// void OnValidate()
// {
// 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);
// return;
// }
// var backup = m_excludes.ToArray();
// Clear(HelpMessage.Ready, ValidationError.None);
// isValid = true;
// m_uniqueMaterials = MeshIntegratorUtility.EnumerateSkinnedMeshRenderer(m_root.transform, MeshEnumerateOption.OnlyWithoutBlendShape)
// .SelectMany(x => x.sharedMaterials)
// .Distinct()
// .ToArray();
// m_duplicateMaterials = m_uniqueMaterials
// .GroupBy(x => GetMaterialKey(x), x => x)
// .Select(x => new MaterialList(x.ToArray()))
// .Where(x => x.Materials.Length > 1)
// .ToArray()
// ;
// UpdateExcludes(backup);
// }
// void UpdateExcludes(ExcludeItem[] backup)
// {
// var exclude_map = new Dictionary<Mesh, ExcludeItem>();
// var excludes = new List<ExcludeItem>();
// foreach (var x in m_root.GetComponentsInChildren<Renderer>())
// {
// var mesh = x.GetMesh();
// if (mesh == null)
// {
// continue;
// }
// if (exclude_map.ContainsKey(mesh))
// {
// continue;
// }
// var item = new ExcludeItem
// {
// Mesh = mesh,
// };
// var found = backup.FirstOrDefault(y => y.Mesh == mesh);
// if (found != null)
// {
// item.Exclude = found.Exclude;
// }
// excludes.Add(item);
// exclude_map[mesh] = item;
// }
// m_excludes.AddRange(excludes);
// }
/// <summary>
/// Scene と Prefab で挙動をスイッチする。
///
/// - Scene: ヒエラルキーを操作する。Asset の 書き出しはしない。UNDO はする。TODO: 明示的な Asset の書き出し。
/// - Prefab: 対象をコピーして処理する。Undo は実装しない。結果を Asset として書き出し、処理後にコピーは削除する。
///
/// </summary>
bool TargetIsPrefab => _exportTarget != null && _exportTarget.scene.name == null;
protected virtual void DialogMessage()
{
EditorGUILayout.HelpBox(MeshUtilityMessages.MESH_UTILITY.Msg(), MessageType.Info);
}
private void OnGUI()
@@ -74,6 +175,9 @@ namespace UniGLTF.MeshUtility
_scrollPos = EditorGUILayout.BeginScrollView(_scrollPos);
EditorGUIUtility.labelWidth = 200;
LanguageGetter.OnGuiSelectLang();
DialogMessage();
var exportTarget = (GameObject)EditorGUILayout.ObjectField(
MeshUtilityMessages.TARGET_OBJECT.Msg(),
_exportTarget, typeof(GameObject), true);
@@ -83,6 +187,14 @@ namespace UniGLTF.MeshUtility
MeshIntegration.UpdateMeshIntegrationList(_exportTarget);
modified = true;
}
// GameObject or Prefab ?
if (TargetIsPrefab)
{
EditorGUILayout.HelpBox(MeshUtilityMessages.PREFAB_TARGET.Msg(), MessageType.Warning);
}
// tab bar
_tab = TabBar.OnGUI(_tab, "LargeButton", GUI.ToolbarButtonSize.Fixed);
foreach (var validation in _validations)
@@ -90,9 +202,6 @@ namespace UniGLTF.MeshUtility
validation.DrawGUI();
}
EditorGUILayout.HelpBox(MeshUtilityMessages.MESH_SEPARATOR.Msg(), MessageType.Info);
EditorGUILayout.HelpBox(MeshUtilityMessages.MESH_INTEGRATOR.Msg(), MessageType.Info);
switch (_tab)
{
case Tabs.Freeze:
@@ -113,24 +222,26 @@ namespace UniGLTF.MeshUtility
break;
}
case Tabs.BoneMeshEraser:
{
// TODO: FirstPerson 処理と統合する
EditorGUILayout.HelpBox(MeshUtilityMessages.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;
}
// TODO:
// Mesh統合のオプション
// case Tabs.BoneMeshEraser:
// {
// // TODO: FirstPerson 処理と統合する
// EditorGUILayout.HelpBox(MeshUtilityMessages.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;
// }
}
EditorGUILayout.EndScrollView();
@@ -144,18 +255,121 @@ namespace UniGLTF.MeshUtility
GUI.enabled = true;
if (pressed)
{
Undo.RegisterFullObjectHierarchyUndo(exportTarget, "MeshUtility");
foreach (var go in MeshUtility.Process(exportTarget))
if (TargetIsPrefab)
{
Undo.RegisterCreatedObjectUndo(go, "MeshUtility");
/// [prefab]
///
/// * backup するのではなく 変更した copy を作成する。元は変えない
/// * copy 先の統合前の renderer を disable で残さず destroy する
/// * 実行すると mesh, blendshape, blendShape を新規に作成する
/// * 新しいヒエラルキーを prefab に保存してから削除して終了する
// 出力フォルダを決める
var folder = "Assets";
var prefab = _exportTarget.GetPrefab();
if (prefab != null)
{
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.IsUnderWritableFolder)
{
EditorUtility.DisplayDialog("asset folder", "Target folder must be in the Assets or writable Packages folder", "cancel");
return;
}
assetFolder = unityPath.Value;
var copy = GameObject.Instantiate(_exportTarget);
var (results, created) = MeshUtility.Process(copy);
WriteAssets(copy, assetFolder, results);
// destroy scene
UnityEngine.Object.DestroyImmediate(copy);
}
else
{
Undo.RegisterFullObjectHierarchyUndo(_exportTarget, "MeshUtility");
var (results, created) = MeshUtility.Process(_exportTarget);
foreach (var go in created)
{
Undo.RegisterCreatedObjectUndo(go, "MeshUtility");
}
}
// TODO: Show Result ?
_exportTarget = null;
// Show Result ?
// Close();
// GUIUtility.ExitGUI();
}
}
void WriteAssets(GameObject copy, string assetFolder, List<MeshIntegrationResult> results)
{
//
// write mesh asset
foreach (var result in results)
{
var childAssetPath = $"{assetFolder}/{result.Integrated.IntegratedRenderer.gameObject.name}{ASSET_SUFFIX}";
Debug.LogFormat("CreateAsset: {0}", childAssetPath);
AssetDatabase.CreateAsset(result.Integrated.IntegratedRenderer.sharedMesh, childAssetPath);
}
// 統合した結果をヒエラルキーに追加する
foreach (var result in results)
{
if (result.Integrated.IntegratedRenderer != null)
{
result.Integrated.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}");
}
// 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();
// }
}
protected bool ToggleIsModified(string label, ref bool value)
{
var newValue = EditorGUILayout.Toggle(label, value);

View File

@@ -8,61 +8,80 @@ namespace UniGLTF.MeshUtility
[LangMsg(Languages.en, "TargetObject")]
TARGET_OBJECT,
[LangMsg(Languages.ja, @"ターゲットオブジェクト下の SkinnedMeshRenderer にアタッチされたメッシュを、 BlendShape の有無で分割します。
[LangMsg(Languages.ja, @"凍結 > 統合 > 分割 という一連の処理を実行します。
* Asset: 新しい Mesh Asset が元と同じフォルダに作成されます。例: Original -> Original_WithBlendShape.mesh & Original_WithoutBlendShape.mesh
* Scene: コピーされたヒエラルキーでは、分割された Mesh は BlendShape のある Mesh で置き換えられて、BlendShape の無い Mesh を使った SkinnedMeshRenderer が追加されます。
[凍結]
- Mesh
- BlendShape Mesh
[統合]
- MeshRenderer SkinnedMeshRenderer SkinnedMeshRenderer
[分割]
- BlendShape
[Scene と Prefab]
Scene Prefab
(Scene/Runtime)
- UNDO可能
- Asset Unityを再起動すると Mesh Asset
(Prefab/Editor)
- prefab Asset
- Asset
- Undo
")]
[LangMsg(Languages.en, @"Separate the mesh attached to the SkinnedMeshRenderer under the target object with or without BlendShape.
[LangMsg(Languages.en, @"
Separate the mesh attached to the SkinnedMeshRenderer under the target object with or without BlendShape.
* Asset: A new Mesh Asset will be created in the same folder as the original. Example: Original-> Original_WithBlendShape.mesh & Original_WithoutBlendShape.mesh
* Scene: In the copied hierarchy, the split Mesh is replaced with a Mesh that holds the BlendShape, and a SkinnedMeshRenderer with a Mesh without BlendShape is added.
")]
MESH_SEPARATOR,
MESH_UTILITY,
[LangMsg(Languages.ja, "ブレンドシェイプの有無で分割する")]
[LangMsg(Languages.en, "Divide by the presence or absence of `blendshape`")]
MESH_SEPARATOR_BY_BLENDSHAPE,
[LangMsg(Languages.ja, @"ターゲットオブジェクト下の SkinnedMeshRenderer または MeshFilter にアタッチされたメッシュを統合します。
// [LangMsg(Languages.ja, @"ターゲットオブジェクト下の SkinnedMeshRenderer または MeshFilter にアタッチされたメッシュを統合します。
* Asset: Assets/MeshIntegrated.mesh が作成されます(上書きされるので注意してください)。
* Scene: コピーされたヒエラルキーでは、統合された Mesh は除去されます。新しく MeshIntegrator ノードが追加されます。
* VRMではBlendShapeClipの統合など追加の処理が必要です。`VRM0-MeshIntegratorWizard` を使ってください。
")]
[LangMsg(Languages.en, @"Integrates the attached mesh into the SkinnedMeshRenderer or MeshFilter under the target object.
// * Asset: Assets/MeshIntegrated.mesh が作成されます(上書きされるので注意してください)。
// * Scene: コピーされたヒエラルキーでは、統合された Mesh は除去されます。新しく MeshIntegrator ノードが追加されます。
// * VRMではBlendShapeClipの統合など追加の処理が必要です。`VRM0-MeshIntegratorWizard` を使ってください。
// ")]
// [LangMsg(Languages.en, @"Integrates the attached mesh into the SkinnedMeshRenderer or MeshFilter under the target object.
* Asset: Assets/MeshIntegrated.mesh is created (note that it will be overwritten).
* Scene: In the copied hierarchy, the integrated mesh is removed. A new MeshIntegrator node is added.
* VRM requires additional processing such as BlendShapeClip integration. Use the `VRM0-MeshIntegratorWizard` integration feature.
")]
MESH_INTEGRATOR,
// * Asset: Assets/MeshIntegrated.mesh is created (note that it will be overwritten).
// * Scene: In the copied hierarchy, the integrated mesh is removed. A new MeshIntegrator node is added.
// * VRM requires additional processing such as BlendShapeClip integration. Use the `VRM0-MeshIntegratorWizard` integration feature.
// ")]
// MESH_INTEGRATOR,
// [LangMsg(Languages.ja, "静的メッシュを一つに統合します")]
// [LangMsg(Languages.en, "Integrate static meshes into one")]
// STATIC_MESH_INTEGRATOR,
// // [LangMsg(Languages.ja, "静的メッシュを一つに統合します")]
// // [LangMsg(Languages.en, "Integrate static meshes into one")]
// // STATIC_MESH_INTEGRATOR,
[LangMsg(Languages.ja, @"指定された SkinnedMeshRenderer から、指定されたボーンに対する Weight を保持する三角形を除去します。
// [LangMsg(Languages.ja, @"指定された SkinnedMeshRenderer から、指定されたボーンに対する Weight を保持する三角形を除去します。
* Asset: 元の Mesh と同じフォルダに、三角形を除去した Mesh を保存します。
* Scene: コピーされたヒエラルキーでは、三角形が除去された Mesh に差し替えられます。
")]
[LangMsg(Languages.en, @"Removes the triangle that holds the weight for the specified bone from the specified SkinnedMeshRenderer.
// * Asset: 元の Mesh と同じフォルダに、三角形を除去した Mesh を保存します。
// * Scene: コピーされたヒエラルキーでは、三角形が除去された Mesh に差し替えられます。
// ")]
// [LangMsg(Languages.en, @"Removes the triangle that holds the weight for the specified bone from the specified SkinnedMeshRenderer.
* Assets: Save the mesh with the triangles removed in the same folder as the original mesh.
* Scene: In the copied hierarchy, it will be replaced with a Mesh with the triangles removed.
")]
// * Assets: Save the mesh with the triangles removed in the same folder as the original mesh.
// * Scene: In the copied hierarchy, it will be replaced with a Mesh with the triangles removed.
// ")]
BONE_MESH_ERASER,
// BONE_MESH_ERASER,
[LangMsg(Languages.ja, "Skinned Meshを選んでください")]
[LangMsg(Languages.en, "Select a skinned mesh")]
SELECT_SKINNED_MESH,
[LangMsg(Languages.ja, "Erase Rootを選んでください")]
[LangMsg(Languages.en, "Select a erase root")]
SELECT_ERASE_ROOT,
// [LangMsg(Languages.ja, "Erase Rootを選んでください")]
// [LangMsg(Languages.en, "Select a erase root")]
// SELECT_ERASE_ROOT,
[LangMsg(Languages.ja, "GameObjectを選んでください")]
[LangMsg(Languages.en, "Select a GameObject first")]
@@ -72,9 +91,9 @@ namespace UniGLTF.MeshUtility
[LangMsg(Languages.en, "No skinned mesh is contained")]
NO_SKINNED_MESH,
[LangMsg(Languages.ja, "GameObjectに静的メッシュが含まれていません")]
[LangMsg(Languages.en, "No static mesh is contained")]
NO_STATIC_MESH,
// [LangMsg(Languages.ja, "GameObjectに静的メッシュが含まれていません")]
// [LangMsg(Languages.en, "No static mesh is contained")]
// NO_STATIC_MESH,
[LangMsg(Languages.ja, "GameObjectにスキンメッシュ・静的メッシュが含まれていません")]
[LangMsg(Languages.en, "Skinned/Static mesh is not contained")]
@@ -83,5 +102,9 @@ namespace UniGLTF.MeshUtility
[LangMsg(Languages.ja, "BlendShapeClipが不整合を起こすので、`VRM0-> MeshIntegrator`を使ってください")]
[LangMsg(Languages.en, "Because BlendShapeClip causes inconsistency , use `VRM0 -> MeshIntegrator` instead")]
VRM_DETECTED,
[LangMsg(Languages.ja, "対象は, Prefab です。実行時に書き出しファイルの指定があります。")]
[LangMsg(Languages.en, "The target is prefab. A temporary file is specified during execution.")]
PREFAB_TARGET,
}
}

View File

@@ -155,7 +155,7 @@ namespace UniGLTF.MeshUtility
return MeshIntegrationGroups.ToList();
}
public virtual List<GameObject> Process(GameObject go)
public virtual (List<MeshIntegrationResult>, List<GameObject>) Process(GameObject go)
{
// TODO unpack prefab
@@ -181,48 +181,45 @@ namespace UniGLTF.MeshUtility
var copy = CopyMeshIntegrationGroups();
var newGo = new List<GameObject>();
var newList = new List<GameObject>();
var empty = GetOrCreateEmpty(go, "mesh");
var results = new List<MeshIntegrationResult>();
foreach (var group in copy)
{
var empty = GetOrCreateEmpty(go, "mesh");
var (result, newGo) = Integrate(empty, group);
results.Add(result);
newList.AddRange(newGo);
}
var results = new List<MeshIntegrationResult>();
foreach (var group in copy)
foreach (var result in results)
{
foreach (var r in result.SourceMeshRenderers)
{
Integrate(newGo, empty, results, group);
RemoveComponent(r);
}
foreach (var result in results)
foreach (var r in result.SourceSkinnedMeshRenderers)
{
foreach (var r in result.SourceMeshRenderers)
{
RemoveComponent(r);
}
foreach (var r in result.SourceSkinnedMeshRenderers)
{
RemoveComponent(r);
}
RemoveComponent(r);
}
}
MeshIntegrationGroups.Clear();
return newGo;
return (results, newList);
}
protected virtual MeshIntegrationResult Integrate(List<GameObject> newGo,
GameObject empty, List<MeshIntegrationResult> results, MeshIntegrationGroup group)
protected virtual (MeshIntegrationResult, GameObject[]) Integrate(GameObject empty,
MeshIntegrationGroup group)
{
var result = MeshIntegrator.Integrate(group, SplitByBlendShape
? MeshIntegrator.BlendShapeOperation.Split
: MeshIntegrator.BlendShapeOperation.Use);
results.Add(result);
foreach (var created in result.AddIntegratedRendererTo(empty))
{
newGo.Add(created);
}
var newGo = result.AddIntegratedRendererTo(empty).ToArray();
return result;
return (result, newGo);
}
}
}

View File

@@ -1,370 +1,88 @@
#pragma warning disable 0414, 0649
using UnityEditor;
using UnityEngine;
using System.Linq;
using System;
using System.Collections.Generic;
using UniGLTF.MeshUtility;
using System.IO;
using UniGLTF.M17N;
namespace VRM
{
public class VrmMeshIntegratorWizard : ScriptableWizard
public class VrmMeshIntegratorWizard : UniGLTF.MeshUtility.MeshUtilityDialog
{
public const string MENU_NAME = "VRM 0.x MeshUtility";
const string ASSET_SUFFIX = ".mesh.asset";
public new const string MENU_NAME = "VRM 0.x MeshUtility";
enum HelpMessage
public new static void OpenWindow()
{
Ready,
SetTarget,
InvalidTarget,
var window =
(VrmMeshIntegratorWizard)EditorWindow.GetWindow(typeof(VrmMeshIntegratorWizard));
window.titleContent = new GUIContent(MENU_NAME);
window.Show();
}
enum ValidationError
// void Integrate()
// {
// // 統合
// var excludes = m_excludes.Where(x => x.Exclude).Select(x => x.Mesh);
// var results = Integrate(copy, excludes, m_separateByBlendShape);
// }
// 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
// {
// results.Add(MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.All, excludes: excludes));
// }
// return results;
// }
protected override void DialogMessage()
{
None,
NoTarget,
HasParent,
NotPrefab,
EditorGUILayout.HelpBox(Message.MESH_UTILITY.Msg(), MessageType.Info);
}
[SerializeField]
GameObject m_root;
[SerializeField]
bool m_separateByBlendShape = true;
[Header("Validation")]
[SerializeField]
Material[] m_uniqueMaterials;
[Serializable]
struct MaterialKey
enum Message
{
public string Shader;
public KeyValuePair<string, object>[] Properties;
[LangMsg(Languages.ja, @"(VRM-0.x専用) 凍結 > 統合 > 分割 という一連の処理を実行します。
public override bool Equals(object obj)
{
if (!(obj is MaterialKey))
{
return base.Equals(obj);
}
[凍結]
- Mesh
- BlendShape Mesh
var key = (MaterialKey)obj;
- VRM-0.x
- HumanoidAvatar
- BlendShapeClip, SpringBone, Constraint
return Shader == key.Shader
&& Properties.SequenceEqual(key.Properties)
;
}
[統合]
- MeshRenderer SkinnedMeshRenderer SkinnedMeshRenderer
public override int GetHashCode()
{
return base.GetHashCode();
}
}
- VRM FirstPerson (BOTH, FirstPerson, ThirdPerson)
- FirstPerson=AUTO
- Mesh ThirdPerson FirstPersonOnly
[Serializable]
struct MaterialList
{
public Material[] Materials;
[分割]
- BlendShape
- BOTH, FirstPerson, ThirdPerson x 2 6Mesh 3Mesh
public MaterialList(Material[] list)
{
Materials = list;
}
}
[SerializeField]
MaterialList[] m_duplicateMaterials;
[Scene と Prefab]
Scene Prefab
[Serializable]
public class ExcludeItem
{
public Mesh Mesh;
public bool Exclude;
}
(Scene/Runtime)
- UNDO可能
- Asset Unityを再起動すると Mesh Asset
[Header("Options")]
[SerializeField]
List<ExcludeItem> m_excludes = new List<ExcludeItem>();
[Header("Result")]
[SerializeField]
MeshInfo[] integrationResults;
public static void CreateWizard()
{
ScriptableWizard.DisplayWizard<VrmMeshIntegratorWizard>(MENU_NAME, "Integrate and close window", "Integrate");
}
private void OnEnable()
{
Clear(HelpMessage.Ready, ValidationError.None);
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);
switch (propType)
{
case ShaderUtil.ShaderPropertyType.Color:
return m.GetColor(ShaderUtil.GetPropertyName(shader, i));
case ShaderUtil.ShaderPropertyType.Range:
case ShaderUtil.ShaderPropertyType.Float:
return m.GetFloat(ShaderUtil.GetPropertyName(shader, i));
case ShaderUtil.ShaderPropertyType.Vector:
return m.GetVector(ShaderUtil.GetPropertyName(shader, i));
case ShaderUtil.ShaderPropertyType.TexEnv:
return m.GetTexture(ShaderUtil.GetPropertyName(shader, i));
default:
throw new NotImplementedException(propType.ToString());
}
}
static MaterialKey GetMaterialKey(Material m)
{
var key = new MaterialKey
{
Shader = m.shader.name,
};
key.Properties = Enumerable.Range(0, ShaderUtil.GetPropertyCount(m.shader))
.Select(x => new KeyValuePair<string, object>(
ShaderUtil.GetPropertyName(m.shader, x),
GetPropertyValue(m.shader, x, m))
)
.OrderBy(x => x.Key)
.ToArray()
;
return key;
}
void Clear(HelpMessage help, ValidationError error)
{
helpString = help.Msg();
errorString = error != ValidationError.None ? error.Msg() : null;
m_uniqueMaterials = new Material[] { };
m_duplicateMaterials = new MaterialList[] { };
m_excludes.Clear();
isValid = false;
}
void OnValidate()
{
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);
return;
}
var backup = m_excludes.ToArray();
Clear(HelpMessage.Ready, ValidationError.None);
isValid = true;
m_uniqueMaterials = MeshIntegratorUtility.EnumerateSkinnedMeshRenderer(m_root.transform, MeshEnumerateOption.OnlyWithoutBlendShape)
.SelectMany(x => x.sharedMaterials)
.Distinct()
.ToArray();
m_duplicateMaterials = m_uniqueMaterials
.GroupBy(x => GetMaterialKey(x), x => x)
.Select(x => new MaterialList(x.ToArray()))
.Where(x => x.Materials.Length > 1)
.ToArray()
;
UpdateExcludes(backup);
}
void UpdateExcludes(ExcludeItem[] backup)
{
var exclude_map = new Dictionary<Mesh, ExcludeItem>();
var excludes = new List<ExcludeItem>();
foreach (var x in m_root.GetComponentsInChildren<Renderer>())
{
var mesh = x.GetMesh();
if (mesh == null)
{
continue;
}
if (exclude_map.ContainsKey(mesh))
{
continue;
}
var item = new ExcludeItem
{
Mesh = mesh,
};
var found = backup.FirstOrDefault(y => y.Mesh == mesh);
if (found != null)
{
item.Exclude = found.Exclude;
}
excludes.Add(item);
exclude_map[mesh] = item;
}
m_excludes.AddRange(excludes);
}
void OnWizardUpdate()
{
}
/// 2022.05 仕様変更
///
/// * prefab 専用
/// * backup するのではなく 変更した copy を作成する。元は変えない
/// * copy 先の統合前の renderer を disable で残さず destroy する
/// * 実行すると mesh, blendshape, blendShape を新規に作成する
/// * 新しいヒエラルキーを prefab に保存してから削除して終了する
///
void Integrate()
{
if (m_root.GetGameObjectType() != GameObjectType.AssetPrefab)
{
throw new Exception("for prefab only");
}
String folder = "Assets";
var prefab = m_root.GetPrefab();
if (prefab != null)
{
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.IsUnderWritableFolder)
{
EditorUtility.DisplayDialog("asset folder", "Target folder must be in the Assets or writable Packages 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);
// write mesh asset
foreach (var result in results)
{
var childAssetPath = $"{assetFolder}/{result.Integrated.IntegratedRenderer.gameObject.name}{ASSET_SUFFIX}";
Debug.LogFormat("CreateAsset: {0}", childAssetPath);
AssetDatabase.CreateAsset(result.Integrated.IntegratedRenderer.sharedMesh, childAssetPath);
}
// 統合した結果をヒエラルキーに追加する
foreach (var result in results)
{
if (result.Integrated.IntegratedRenderer != null)
{
result.Integrated.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
{
results.Add(MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.All, excludes: excludes));
}
return results;
}
void OnWizardCreate()
{
Integrate();
// close
}
void OnWizardOtherButton()
{
Integrate();
(Prefab/Editor)
- prefab Asset
- Asset
- Undo
")]
[LangMsg(Languages.en, @"TODO
")]
MESH_UTILITY,
}
}
}

View File

@@ -25,7 +25,7 @@ namespace VRM
[MenuItem(UserMenuPrefix + "/" + VrmMeshIntegratorWizard.MENU_NAME, false, 3)]
private static void OpenMeshIntegratorWizard() => VrmMeshIntegratorWizard.CreateWizard();
private static void OpenMeshIntegratorWizard() => VrmMeshIntegratorWizard.OpenWindow();
[MenuItem(UserMenuPrefix + "/" + VRMHumanoidNormalizerMenu.MENU_NAME, true, 51)]

View File

@@ -1,6 +1,8 @@
using UnityEngine;
using UnityEditor;
using UniGLTF;
using UniGLTF.M17N;
namespace UniVRM10
{
@@ -57,5 +59,50 @@ namespace UniVRM10
var mod = base.MeshIntegrateGui();
return firstPerson || mod;
}
protected override void DialogMessage()
{
EditorGUILayout.HelpBox(Message.MESH_UTILITY.Msg(), MessageType.Info);
}
enum Message
{
[LangMsg(Languages.ja, @"(VRM-1.0専用) 凍結 > 統合 > 分割 という一連の処理を実行します。
[凍結]
- Mesh
- BlendShape Mesh
- VRM-1.0
- VRM-1.0
- HumanoidAvatar
- Expression, SpringBone, Constraint
[統合]
- MeshRenderer SkinnedMeshRenderer SkinnedMeshRenderer
- VRM FirstPerson (BOTH, FirstPerson, ThirdPerson)
- FirstPerson=AUTO
- Mesh ThirdPerson FirstPersonOnly
[分割]
- BlendShape
- BOTH, FirstPerson, ThirdPerson x 2 6Mesh 3Mesh
[Scene と Prefab]
Scene Prefab
(Scene/Runtime)
- UNDO可能
- Asset Unityを再起動すると Mesh Asset
(Prefab/Editor)
- prefab Asset
- Asset
- Undo
")]
[LangMsg(Languages.en, @"TODO
")]
MESH_UTILITY,
}
}
}

View File

@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UniGLTF.MeshUtility;
using UniHumanoid;
using UnityEngine;
using UnityEngine.Rendering;
namespace UniVRM10
{
@@ -37,12 +39,12 @@ namespace UniVRM10
return copy;
}
protected override UniGLTF.MeshUtility.MeshIntegrationResult Integrate(List<GameObject> newGo,
protected override
(UniGLTF.MeshUtility.MeshIntegrationResult, GameObject[]) Integrate(
GameObject empty,
List<UniGLTF.MeshUtility.MeshIntegrationResult> results,
UniGLTF.MeshUtility.MeshIntegrationGroup group)
{
var result = base.Integrate(newGo, empty, results, group);
var (result, newList) = base.Integrate(empty, group);
if (_generateFirstPerson && group.Name == "auto")
{
@@ -57,14 +59,14 @@ namespace UniVRM10
}
}
return result;
return (result, newList);
}
Vrm10Instance _vrmInstance = null;
/// <summary>
/// glTF に比べて Humanoid や FirstPerson の処理が追加される
/// </summary>
public override List<GameObject> Process(GameObject go)
public override (List<MeshIntegrationResult>, List<GameObject>) Process(GameObject go)
{
_vrmInstance = go.GetComponent<Vrm10Instance>();
if (_vrmInstance == null)
@@ -85,7 +87,7 @@ namespace UniVRM10
// TODO: update: spring
// TODO: update: constraint
// TODO: update: firstPerson offset
var list = base.Process(go);
var (list, newList) = base.Process(go);
if (FreezeBlendShape || FreezeRotation || FreezeScaling)
{
@@ -94,7 +96,7 @@ namespace UniVRM10
animator.avatar = newAvatar;
}
return list;
return (list, newList);
}
void _ProcessFirstPerson(Vrm10Instance vrmInstance, UniGLTF.MeshUtility.MeshInfo info)