UniVRM/Assets/VRM/Editor/Format/VRMEditorExporter.cs

237 lines
8.8 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UniGLTF;
using UniGLTF.MeshUtility;
using UnityEngine;
using VRMShaders;
namespace VRM
{
public static class VRMEditorExporter
{
/// <summary>
/// Editor向けのエクスポート処理
/// </summary>
/// <param name="path">出力先</param>
/// <param name="settings">エクスポート設定</param>
public static byte[] Export(GameObject exportRoot, VRMMetaObject meta, VRMExportSettings settings)
{
List<GameObject> destroy = new List<GameObject>();
try
{
return Export(exportRoot, meta, settings, destroy);
}
finally
{
foreach (var x in destroy)
{
Debug.LogFormat("destroy: {0}", x.name);
GameObject.DestroyImmediate(x);
}
}
}
static bool IsPrefab(GameObject go)
{
return !go.scene.IsValid();
}
/// <summary>
/// DeepCopy
/// </summary>
/// <param name="src"></param>
/// <returns></returns>
static BlendShapeAvatar CopyBlendShapeAvatar(BlendShapeAvatar src, bool removeUnknown)
{
var avatar = GameObject.Instantiate(src);
avatar.Clips = new List<BlendShapeClip>();
foreach (var clip in src.Clips)
{
if (removeUnknown && clip.Preset == BlendShapePreset.Unknown)
{
continue;
}
avatar.Clips.Add(GameObject.Instantiate(clip));
}
return avatar;
}
/// <summary>
/// 使用されない BlendShape を間引いた Mesh を作成して置き換える
/// </summary>
/// <param name="mesh"></param>
/// <returns></returns>
static void ReplaceMesh(GameObject target, SkinnedMeshRenderer smr, BlendShapeAvatar copyBlendShapeAvatar)
{
Mesh mesh = smr.sharedMesh;
if (mesh == null) return;
if (mesh.blendShapeCount == 0) return;
// Mesh から BlendShapeClip からの参照がある blendShape の index を集める
var usedBlendshapeIndexArray = copyBlendShapeAvatar.Clips
.SelectMany(clip => clip.Values)
.Where(val => target.transform.Find(val.RelativePath) == smr.transform)
.Select(val => val.Index)
.Distinct()
.ToArray();
var copyMesh = mesh.Copy(copyBlendShape: false);
// 使われている BlendShape だけをコピーする
foreach (var i in usedBlendshapeIndexArray)
{
var name = mesh.GetBlendShapeName(i);
var vCount = mesh.vertexCount;
var vertices = new Vector3[vCount];
var normals = new Vector3[vCount];
var tangents = new Vector3[vCount];
mesh.GetBlendShapeFrameVertices(i, 0, vertices, normals, tangents);
copyMesh.AddBlendShapeFrame(name, 100f, vertices, normals, tangents);
}
// BlendShapeClip の BlendShapeIndex を更新する(前に詰める)
var indexMapper = usedBlendshapeIndexArray
.Select((x, i) => new { x, i })
.ToDictionary(pair => pair.x, pair => pair.i);
foreach (var clip in copyBlendShapeAvatar.Clips)
{
for (var i = 0; i < clip.Values.Length; ++i)
{
var value = clip.Values[i];
if (target.transform.Find(value.RelativePath) != smr.transform) continue;
value.Index = indexMapper[value.Index];
clip.Values[i] = value;
}
}
// mesh を置き換える
smr.sharedMesh = copyMesh;
}
static void ForceUniqueName(Transform transform, Dictionary<string, int> nameCount)
{
for (int i = 2; i < 5000; ++i)
{
var sb = new StringBuilder();
sb.Append(transform.name);
sb.Append('_');
sb.Append(i);
var newName = sb.ToString();
if (!nameCount.ContainsKey(newName))
{
Debug.LogWarningFormat("force rename {0} => {1}", transform.name, newName);
transform.name = newName;
nameCount.Add(newName, 1);
return;
}
}
throw new Exception("?");
}
/// <summary>
///
/// </summary>
/// <param name="path"></param>
/// <param name="settings"></param>
/// <param name="destroy">作業が終わったらDestoryするべき一時オブジェクト</param>
static byte[] Export(GameObject exportRoot, VRMMetaObject meta,
VRMExportSettings settings,
List<GameObject> destroy)
{
var target = exportRoot;
// 常にコピーする。シーンを変化させない
target = GameObject.Instantiate(target);
destroy.Add(target);
var metaBehaviour = target.GetComponent<VRMMeta>();
if (metaBehaviour == null)
{
metaBehaviour = target.AddComponent<VRMMeta>();
metaBehaviour.Meta = meta;
}
if (metaBehaviour.Meta == null)
{
// 来ないはず
throw new Exception("meta required");
}
{
// copy元
var animator = exportRoot.GetComponent<Animator>();
var beforeTransforms = exportRoot.GetComponentsInChildren<Transform>();
// copy先
var afterTransforms = target.GetComponentsInChildren<Transform>();
// copy先のhumanoidBoneのリストを得る
var bones = (HumanBodyBones[])Enum.GetValues(typeof(HumanBodyBones));
var humanTransforms = bones
.Where(x => x != HumanBodyBones.LastBone)
.Select(x => animator.GetBoneTransform(x))
.Where(x => x != null)
.Select(x => afterTransforms[Array.IndexOf(beforeTransforms, x)]) // copy 先を得る
.ToArray();
var nameCount = target.GetComponentsInChildren<Transform>()
.GroupBy(x => x.name)
.ToDictionary(x => x.Key, x => x.Count());
foreach (var t in target.GetComponentsInChildren<Transform>())
{
if (humanTransforms.Contains(t))
{
// keep original name
continue;
}
if (nameCount[t.name] > 1)
{
// 重複するボーン名をリネームする
ForceUniqueName(t, nameCount);
}
}
}
// 正規化
if (settings.PoseFreeze)
{
// BoneNormalizer.Execute は Copy を作って正規化する。UNDO無用
target = VRMBoneNormalizer.Execute(target, settings.ForceTPose);
destroy.Add(target);
}
var fp = target.GetComponent<VRMFirstPerson>();
// 元のBlendShapeClipに変更を加えないように複製
var proxy = target.GetComponent<VRMBlendShapeProxy>();
if (proxy != null)
{
var copyBlendShapeAvatar = CopyBlendShapeAvatar(proxy.BlendShapeAvatar, settings.ReduceBlendshapeClip);
proxy.BlendShapeAvatar = copyBlendShapeAvatar;
// BlendShape削減
if (settings.ReduceBlendshape)
{
foreach (SkinnedMeshRenderer smr in target.GetComponentsInChildren<SkinnedMeshRenderer>())
{
// 未使用のBlendShapeを間引く
ReplaceMesh(target, smr, copyBlendShapeAvatar);
}
}
}
// 出力
var sw = System.Diagnostics.Stopwatch.StartNew();
var data = new UniGLTF.ExportingGltfData();
using (var exporter = new VRMExporter(data, settings.MeshExportSettings))
{
exporter.Prepare(target);
exporter.Export(new EditorTextureSerializer());
}
var bytes = data.ToGlbBytes();
Debug.LogFormat("Export elapsed {0}", sw.Elapsed);
return bytes;
}
}
}