363 lines
12 KiB
C#
363 lines
12 KiB
C#
#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
|
|
{
|
|
const string ASSET_SUFFIX = ".mesh.asset";
|
|
|
|
enum HelpMessage
|
|
{
|
|
Ready,
|
|
SetTarget,
|
|
InvalidTarget,
|
|
}
|
|
|
|
enum ValidationError
|
|
{
|
|
None,
|
|
NoTarget,
|
|
HasParent,
|
|
NotPrefab,
|
|
}
|
|
|
|
[SerializeField]
|
|
GameObject m_root;
|
|
|
|
[SerializeField]
|
|
bool m_separateByBlendShape = true;
|
|
|
|
[Header("Validation")]
|
|
[SerializeField]
|
|
Material[] m_uniqueMaterials;
|
|
|
|
[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;
|
|
}
|
|
}
|
|
[SerializeField]
|
|
MaterialList[] m_duplicateMaterials;
|
|
|
|
[Serializable]
|
|
public class ExcludeItem
|
|
{
|
|
public Mesh Mesh;
|
|
public bool Exclude;
|
|
}
|
|
|
|
[Header("Options")]
|
|
[SerializeField]
|
|
List<ExcludeItem> m_excludes = new List<ExcludeItem>();
|
|
|
|
[Header("Result")]
|
|
[SerializeField]
|
|
MeshMap[] integrationResults;
|
|
|
|
public static void CreateWizard()
|
|
{
|
|
ScriptableWizard.DisplayWizard<VrmMeshIntegratorWizard>("MeshIntegratorWizard", "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;
|
|
}
|
|
|
|
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()
|
|
;
|
|
|
|
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;
|
|
}
|
|
var item = new ExcludeItem { Mesh = mesh };
|
|
excludes.Add(item);
|
|
exclude_map[mesh] = item;
|
|
}
|
|
foreach (var x in m_excludes)
|
|
{
|
|
if (exclude_map.TryGetValue(x.Mesh, out ExcludeItem item))
|
|
{
|
|
// update
|
|
item.Exclude = x.Exclude;
|
|
}
|
|
}
|
|
m_excludes.Clear();
|
|
foreach (var kv in exclude_map)
|
|
{
|
|
m_excludes.Add(kv.Value);
|
|
}
|
|
}
|
|
|
|
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.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);
|
|
|
|
// write mesh asset
|
|
foreach (var result in results)
|
|
{
|
|
var childAssetPath = $"{assetFolder}/{result.IntegratedRenderer.gameObject.name}{ASSET_SUFFIX}";
|
|
Debug.LogFormat("CreateAsset: {0}", childAssetPath);
|
|
AssetDatabase.CreateAsset(result.IntegratedRenderer.sharedMesh, childAssetPath);
|
|
}
|
|
|
|
// 統合した結果をヒエラルキーに追加する
|
|
foreach (var result in results)
|
|
{
|
|
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
|
|
{
|
|
results.Add(MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.All, excludes: excludes));
|
|
}
|
|
return results;
|
|
}
|
|
|
|
void OnWizardCreate()
|
|
{
|
|
Integrate();
|
|
// close
|
|
}
|
|
|
|
void OnWizardOtherButton()
|
|
{
|
|
Integrate();
|
|
}
|
|
}
|
|
}
|