您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
548 行
18 KiB
548 行
18 KiB
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;
|
|
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;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|