mirror of
https://github.com/vrm-c/UniVRM.git
synced 2026-03-30 14:25:13 -05:00
237 lines
8.8 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|