2025-04-25 21:14:54 +09:00

549 lines
18 KiB
C#
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}
}
}
}