using System; using System.Collections.Generic; using System.Linq; using System.Text; using UniGLTF; using UniGLTF.Utils; using Unity.Collections; using UnityEngine; namespace VrmLib { /// /// 処理しやすいようにした中間形式 /// * index 参照は実参照 /// * accessor, bufferView は実バイト列(ArraySegment) /// * meshは、subMesh方式(indexが offset + length) /// 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 Materials = new List(); // gltf/skins public readonly List Skins = new List(); // gltf/meshes public readonly List MeshGroups = new List(); // 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 Nodes = new List(); public Dictionary 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(); } /// /// HumanoidBonesの構成チェック /// /// public bool CheckVrmHumanoid() { var vrmhumanoids = new HashSet(); // 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() .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; } /// /// Node Transform の Rotation, Scaling 成分を除去する /// 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 /// /// ボーンを含む Node Transform の Rotation, Scaling 成分を除去し、SkinnedMesh の Bind Matrix も再計算する。 /// 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(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(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(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(1); for (int i = 0; i < span.Length; ++i) { span[i] = span[i].ReverseZ(); } } else { throw new NotImplementedException(); } } struct Reverser { public Action ReverseBuffer; public Func ReverseVector3; public Func 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(), }; /// /// ignoreVrm: VRM-0.XX では無変換で入出力してた。VRM-1.0 では変換する。 /// 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(); } } /// /// UVのVを反転する。 => V = 1.0 - V /// 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(1); for (int i = 0; i < span.Length; ++i) { span[i] = span[i].UVVerticalFlip(); } } } } } /// /// * Position, Normal の Z座標に -1 を乗算する /// * Rotation => Axis Angle に分解 => Axis の Z座標に -1 を乗算。Angle に -1 を乗算 /// * Triangle の index を 0, 1, 2 から 2, 1, 0 に反転する /// void ReverseAxisAndFlipTriangle(Reverser reverser, bool ignoreVrm) { // 複数の gltf.accessor が別の要素間で共有されている場合に、2回処理されることを防ぐ // edgecase: InverseBindMatrices で遭遇 var unique = new HashSet>(); 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(1)); break; case AccessorValueType.UNSIGNED_INT: FlipTriangle(m.IndexBuffer.Bytes.Reinterpret(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 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 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 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; } } } }