549 lines
18 KiB
C#
549 lines
18 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Text;
|
||
using UniGLTF;
|
||
using UniGLTF.Utils;
|
||
using Unity.Collections;
|
||
using UnityEngine;
|
||
|
||
namespace VrmLib
|
||
{
|
||
/// <summary>
|
||
/// 処理しやすいようにした中間形式
|
||
/// * index 参照は実参照
|
||
/// * accessor, bufferView は実バイト列(ArraySegment<byte>)
|
||
/// * meshは、subMesh方式(indexが offset + length)
|
||
/// </summary>
|
||
public class Model
|
||
{
|
||
public Model(Coordinates coordinates)
|
||
{
|
||
Coordinates = coordinates;
|
||
}
|
||
|
||
public Coordinates Coordinates;
|
||
|
||
public string AssetVersion = "2.0";
|
||
public string AssetGenerator = $"UniVRM-{PackageVersion.VERSION}";
|
||
public string AssetCopyright;
|
||
public string AssetMinVersion;
|
||
|
||
// gltf/materials
|
||
public readonly List<object> Materials = new List<object>();
|
||
|
||
// gltf/skins
|
||
public readonly List<Skin> Skins = new List<Skin>();
|
||
|
||
// gltf/meshes
|
||
public readonly List<MeshGroup> MeshGroups = new List<MeshGroup>();
|
||
|
||
// gltf の nodes に含まれない。sceneに相当
|
||
Node m_root = new Node("__root__");
|
||
|
||
public Node Root
|
||
{
|
||
get => m_root;
|
||
}
|
||
public void SetRoot(Node root)
|
||
{
|
||
m_root = root;
|
||
|
||
Nodes.Clear();
|
||
Nodes.AddRange(root.Traverse().Skip(1));
|
||
}
|
||
|
||
// gltf/nodes
|
||
public List<Node> Nodes = new List<Node>();
|
||
|
||
|
||
public Dictionary<HumanoidBones, Node> GetBoneMap()
|
||
{
|
||
return Root.Traverse()
|
||
.Where(x => x.HumanoidBone.HasValue)
|
||
.ToDictionary(x => x.HumanoidBone.Value, x => x);
|
||
}
|
||
|
||
public override string ToString()
|
||
{
|
||
var sb = new StringBuilder();
|
||
sb.Append($"[GLTF] generator: {AssetGenerator}\n");
|
||
|
||
for (int i = 0; i < Materials.Count; ++i)
|
||
{
|
||
var m = Materials[i];
|
||
sb.Append($"[Material#{i:00}] {m}\n");
|
||
}
|
||
for (int i = 0; i < MeshGroups.Count; ++i)
|
||
{
|
||
var m = MeshGroups[i];
|
||
sb.Append($"[Mesh#{i:00}] {m}\n");
|
||
}
|
||
sb.Append($"[Node] {Nodes.Count} nodes\n");
|
||
|
||
foreach (var skin in Skins)
|
||
{
|
||
sb.Append($"[Skin] {skin}\n");
|
||
}
|
||
|
||
return sb.ToString();
|
||
}
|
||
|
||
/// <summary>
|
||
/// HumanoidBonesの構成チェック
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
public bool CheckVrmHumanoid()
|
||
{
|
||
var vrmhumanoids = new HashSet<HumanoidBones>();
|
||
|
||
// HumanoidBonesの重複チェック
|
||
foreach (var node in Nodes)
|
||
{
|
||
if (node.HumanoidBone.HasValue)
|
||
{
|
||
if (vrmhumanoids.Contains(node.HumanoidBone.Value))
|
||
{
|
||
return false;
|
||
}
|
||
else
|
||
{
|
||
vrmhumanoids.Add(node.HumanoidBone.Value);
|
||
}
|
||
}
|
||
}
|
||
|
||
// HumanoidBonesでBoneRequiredAttributeが定義されているものすべてが使われているかどうかを判断
|
||
|
||
var boneattributes
|
||
= CachedEnum.GetValues<HumanoidBones>()
|
||
.Select(bone => bone.GetType().GetField(bone.ToString()))
|
||
.Select(info => info.GetCustomAttributes(typeof(BoneRequiredAttribute), false) as BoneRequiredAttribute[])
|
||
.Where(attributes => attributes.Length > 0);
|
||
|
||
var nodeHumanoids
|
||
= vrmhumanoids
|
||
.Select(humanoid => humanoid.GetType().GetField(humanoid.ToString()))
|
||
.Select(info => info.GetCustomAttributes(typeof(BoneRequiredAttribute), false) as BoneRequiredAttribute[])
|
||
.Where(attributes => attributes.Length > 0);
|
||
|
||
if (nodeHumanoids.Count() != boneattributes.Count()) return false;
|
||
|
||
return true;
|
||
}
|
||
|
||
public static Node GetNode(Node root, string path)
|
||
{
|
||
var splitted = path.Split('/');
|
||
var it = splitted.Select(x => x).GetEnumerator();
|
||
|
||
var current = root;
|
||
while (it.MoveNext())
|
||
{
|
||
current = current.Children.First(x => x.Name == it.Current);
|
||
}
|
||
|
||
return current;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Node Transform の Rotation, Scaling 成分を除去する
|
||
/// </summary>
|
||
public void ApplyRotationAndScale()
|
||
{
|
||
// worldPositionを記録する
|
||
var m_positionMap = Nodes.ToDictionary(x => x, x => x.Translation);
|
||
|
||
// 回転・拡縮を除去する
|
||
// 木構造の根元から実行する
|
||
// Rootは編集対象外
|
||
foreach (var node in Root.Traverse().Skip(1))
|
||
{
|
||
// 回転・拡縮を除去
|
||
if (m_positionMap.TryGetValue(node, out Vector3 pos))
|
||
{
|
||
var t = Matrix4x4.Translate(pos);
|
||
node.SetMatrix(t, false);
|
||
}
|
||
}
|
||
}
|
||
|
||
#region Node
|
||
public void NodeAdd(Node node, Node parent = null)
|
||
{
|
||
if (parent is null)
|
||
{
|
||
parent = this.Root;
|
||
}
|
||
parent.Add(node);
|
||
if (this.Nodes.Contains(node))
|
||
{
|
||
throw new ArgumentException($"Nodes contain {node}");
|
||
}
|
||
this.Nodes.Add(node);
|
||
}
|
||
|
||
public void NodeRemove(Node remove)
|
||
{
|
||
foreach (var node in this.Nodes)
|
||
{
|
||
if (node.Parent == remove)
|
||
{
|
||
remove.Remove(node);
|
||
}
|
||
if (remove.Parent == node)
|
||
{
|
||
node.Remove(remove);
|
||
}
|
||
}
|
||
if (this.Root.Children.Contains(remove))
|
||
{
|
||
this.Root.Remove(remove);
|
||
}
|
||
|
||
this.Nodes.Remove(remove);
|
||
}
|
||
#endregion
|
||
|
||
/// <summary>
|
||
/// ボーンを含む Node Transform の Rotation, Scaling 成分を除去し、SkinnedMesh の Bind Matrix も再計算する。
|
||
/// </summary>
|
||
public string SkinningBake(INativeArrayManager arrayManager)
|
||
{
|
||
foreach (var node in this.Nodes)
|
||
{
|
||
var meshGroup = node.MeshGroup;
|
||
if (meshGroup == null)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
if (meshGroup.Skin != null)
|
||
{
|
||
// 正規化されていれば1つしかない
|
||
// されていないと Primitive の数だけある
|
||
foreach (var mesh in meshGroup.Meshes)
|
||
{
|
||
{
|
||
// Skinningの出力先を自身にすることでBakeする
|
||
meshGroup.Skin.Skinning(arrayManager, mesh.VertexBuffer);
|
||
}
|
||
|
||
// morphのPositionは相対値が入っているはずなので、手を加えない(正規化されていない場合、二重に補正が掛かる)
|
||
/*
|
||
foreach (var morph in mesh.MorphTargets)
|
||
{
|
||
if (morph.VertexBuffer.Positions != null)
|
||
{
|
||
meshGroup.Skin.Skinning(morph.VertexBuffer);
|
||
}
|
||
}
|
||
*/
|
||
}
|
||
|
||
meshGroup.Skin.Root = null;
|
||
meshGroup.Skin.InverseMatrices = null;
|
||
}
|
||
else
|
||
{
|
||
foreach (var mesh in meshGroup.Meshes)
|
||
{
|
||
// nodeに対して疑似的にSkinningする
|
||
// 回転と拡縮を適用し位置は適用しない
|
||
mesh.ApplyRotationAndScaling(node.Matrix);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 回転・拡縮を除去する
|
||
this.ApplyRotationAndScale();
|
||
|
||
// inverse matrix の再計算
|
||
foreach (var node in this.Nodes)
|
||
{
|
||
var meshGroup = node.MeshGroup;
|
||
if (meshGroup == null)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
foreach (var mesh in meshGroup.Meshes)
|
||
{
|
||
if (meshGroup.Skin != null)
|
||
{
|
||
meshGroup.Skin.CalcInverseMatrices(arrayManager);
|
||
}
|
||
}
|
||
}
|
||
|
||
return "SkinningBake";
|
||
}
|
||
|
||
static void ReverseX(BufferAccessor ba)
|
||
{
|
||
if (ba.ComponentType != AccessorValueType.FLOAT)
|
||
{
|
||
throw new Exception();
|
||
}
|
||
if (ba.AccessorType == AccessorVectorType.VEC3)
|
||
{
|
||
var span = ba.Bytes.Reinterpret<Vector3>(1);
|
||
for (int i = 0; i < span.Length; ++i)
|
||
{
|
||
span[i] = span[i].ReverseX();
|
||
}
|
||
}
|
||
else if (ba.AccessorType == AccessorVectorType.MAT4)
|
||
{
|
||
var span = ba.Bytes.Reinterpret<Matrix4x4>(1);
|
||
for (int i = 0; i < span.Length; ++i)
|
||
{
|
||
span[i] = span[i].ReverseX();
|
||
}
|
||
}
|
||
else
|
||
{
|
||
throw new NotImplementedException();
|
||
}
|
||
}
|
||
|
||
static void ReverseZ(BufferAccessor ba)
|
||
{
|
||
if (ba.ComponentType != AccessorValueType.FLOAT)
|
||
{
|
||
throw new Exception();
|
||
}
|
||
if (ba.AccessorType == AccessorVectorType.VEC3)
|
||
{
|
||
var span = ba.Bytes.Reinterpret<Vector3>(1);
|
||
for (int i = 0; i < span.Length; ++i)
|
||
{
|
||
span[i] = span[i].ReverseZ();
|
||
}
|
||
}
|
||
else if (ba.AccessorType == AccessorVectorType.MAT4)
|
||
{
|
||
var span = ba.Bytes.Reinterpret<Matrix4x4>(1);
|
||
for (int i = 0; i < span.Length; ++i)
|
||
{
|
||
span[i] = span[i].ReverseZ();
|
||
}
|
||
}
|
||
else
|
||
{
|
||
throw new NotImplementedException();
|
||
}
|
||
}
|
||
|
||
struct Reverser
|
||
{
|
||
public Action<BufferAccessor> ReverseBuffer;
|
||
public Func<Vector3, Vector3> ReverseVector3;
|
||
public Func<Matrix4x4, Matrix4x4> ReverseMatrix;
|
||
}
|
||
|
||
static Reverser ZReverser => new Reverser
|
||
{
|
||
ReverseBuffer = ReverseZ,
|
||
ReverseVector3 = v => v.ReverseZ(),
|
||
ReverseMatrix = m => m.ReverseZ(),
|
||
};
|
||
|
||
static Reverser XReverser => new Reverser
|
||
{
|
||
ReverseBuffer = ReverseX,
|
||
ReverseVector3 = v => v.ReverseX(),
|
||
ReverseMatrix = m => m.ReverseX(),
|
||
};
|
||
|
||
/// <summary>
|
||
/// ignoreVrm: VRM-0.XX では無変換で入出力してた。VRM-1.0 では変換する。
|
||
/// </summary>
|
||
public void ConvertCoordinate(Coordinates coordinates, bool ignoreVrm = false)
|
||
{
|
||
if (Coordinates.Equals(coordinates))
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (Coordinates.IsVrm0 && coordinates.IsUnity)
|
||
{
|
||
ReverseAxisAndFlipTriangle(ZReverser, ignoreVrm);
|
||
UVVerticalFlip();
|
||
Coordinates = coordinates;
|
||
}
|
||
else if (Coordinates.IsUnity && coordinates.IsVrm0)
|
||
{
|
||
ReverseAxisAndFlipTriangle(ZReverser, ignoreVrm);
|
||
UVVerticalFlip();
|
||
Coordinates = coordinates;
|
||
}
|
||
else if (Coordinates.IsVrm1 && coordinates.IsUnity)
|
||
{
|
||
ReverseAxisAndFlipTriangle(XReverser, ignoreVrm);
|
||
UVVerticalFlip();
|
||
Coordinates = coordinates;
|
||
}
|
||
else if (Coordinates.IsUnity && coordinates.IsVrm1)
|
||
{
|
||
ReverseAxisAndFlipTriangle(XReverser, ignoreVrm);
|
||
UVVerticalFlip();
|
||
Coordinates = coordinates;
|
||
}
|
||
else
|
||
{
|
||
throw new NotImplementedException();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// UVのVを反転する。 => V = 1.0 - V
|
||
/// </summary>
|
||
void UVVerticalFlip()
|
||
{
|
||
foreach (var g in MeshGroups)
|
||
{
|
||
foreach (var m in g.Meshes)
|
||
{
|
||
var uv = m.VertexBuffer.TexCoords;
|
||
if (uv != null)
|
||
{
|
||
var span = uv.Bytes.Reinterpret<Vector2>(1);
|
||
for (int i = 0; i < span.Length; ++i)
|
||
{
|
||
span[i] = span[i].UVVerticalFlip();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// * Position, Normal の Z座標に -1 を乗算する
|
||
/// * Rotation => Axis Angle に分解 => Axis の Z座標に -1 を乗算。Angle に -1 を乗算
|
||
/// * Triangle の index を 0, 1, 2 から 2, 1, 0 に反転する
|
||
/// </summary>
|
||
void ReverseAxisAndFlipTriangle(Reverser reverser, bool ignoreVrm)
|
||
{
|
||
// 複数の gltf.accessor が別の要素間で共有されている場合に、2回処理されることを防ぐ
|
||
// edgecase: InverseBindMatrices で遭遇
|
||
var unique = new HashSet<NativeArray<byte>>();
|
||
|
||
foreach (var g in MeshGroups)
|
||
{
|
||
foreach (var m in g.Meshes)
|
||
{
|
||
foreach (var kv in m.VertexBuffer)
|
||
{
|
||
var k = kv.Key;
|
||
var v = kv.Value;
|
||
if (k == VertexBuffer.PositionKey || k == VertexBuffer.NormalKey)
|
||
{
|
||
if (unique.Add(v.Bytes))
|
||
{
|
||
reverser.ReverseBuffer(v);
|
||
}
|
||
}
|
||
else if (k == VertexBuffer.TangentKey)
|
||
{
|
||
// I don't know
|
||
}
|
||
}
|
||
|
||
if (unique.Add(m.IndexBuffer.Bytes))
|
||
{
|
||
switch (m.IndexBuffer.ComponentType)
|
||
{
|
||
case AccessorValueType.UNSIGNED_BYTE:
|
||
FlipTriangle(m.IndexBuffer.Bytes);
|
||
break;
|
||
case AccessorValueType.UNSIGNED_SHORT:
|
||
FlipTriangle(m.IndexBuffer.Bytes.Reinterpret<UInt16>(1));
|
||
break;
|
||
case AccessorValueType.UNSIGNED_INT:
|
||
FlipTriangle(m.IndexBuffer.Bytes.Reinterpret<UInt32>(1));
|
||
break;
|
||
default:
|
||
throw new NotImplementedException();
|
||
}
|
||
}
|
||
|
||
foreach (var mt in m.MorphTargets)
|
||
{
|
||
foreach (var kv in mt.VertexBuffer)
|
||
{
|
||
var k = kv.Key;
|
||
var v = kv.Value;
|
||
if (k == VertexBuffer.PositionKey || k == VertexBuffer.NormalKey)
|
||
{
|
||
if (unique.Add(v.Bytes))
|
||
{
|
||
reverser.ReverseBuffer(v);
|
||
}
|
||
}
|
||
if (k == VertexBuffer.TangentKey)
|
||
{
|
||
// I don't know
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 親から順に処理する
|
||
// Rootは原点決め打ちのノード(GLTFに含まれない)
|
||
foreach (var n in Root.Traverse().Skip(1))
|
||
{
|
||
n.SetMatrix(reverser.ReverseMatrix(n.Matrix), false);
|
||
}
|
||
// 親から順に処理したので不要
|
||
// Root.CalcWorldMatrix();
|
||
|
||
foreach (var s in Skins)
|
||
{
|
||
if (s.InverseMatrices != null)
|
||
{
|
||
if (unique.Add(s.InverseMatrices.Bytes))
|
||
{
|
||
reverser.ReverseBuffer(s.InverseMatrices);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
static void FlipTriangle(NativeArray<byte> indices)
|
||
{
|
||
for (int i = 0; i < indices.Length; i += 3)
|
||
{
|
||
// 0, 1, 2 to 2, 1, 0
|
||
var tmp = indices[i + 2];
|
||
indices[i + 2] = indices[i];
|
||
indices[i] = tmp;
|
||
}
|
||
}
|
||
|
||
static void FlipTriangle(NativeArray<ushort> indices)
|
||
{
|
||
for (int i = 0; i < indices.Length; i += 3)
|
||
{
|
||
// 0, 1, 2 to 2, 1, 0
|
||
var tmp = indices[i + 2];
|
||
indices[i + 2] = indices[i];
|
||
indices[i] = tmp;
|
||
}
|
||
}
|
||
|
||
static void FlipTriangle(NativeArray<uint> indices)
|
||
{
|
||
for (int i = 0; i < indices.Length; i += 3)
|
||
{
|
||
// 0, 1, 2 to 2, 1, 0
|
||
var tmp = indices[i + 2];
|
||
indices[i + 2] = indices[i];
|
||
indices[i] = tmp;
|
||
}
|
||
}
|
||
|
||
}
|
||
}
|