您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
364 行
9.4 KiB
364 行
9.4 KiB
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEngine;
|
|
using UniGLTF;
|
|
|
|
namespace VrmLib
|
|
{
|
|
public enum ChildMatrixMode
|
|
{
|
|
KeepLocal,
|
|
KeepWorld,
|
|
}
|
|
|
|
public class Node : GltfId, IEnumerable<Node>
|
|
{
|
|
static int s_nextUniqueId = 1;
|
|
|
|
public readonly int UniqueID;
|
|
|
|
public string Name
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public Node(string name)
|
|
{
|
|
UniqueID = s_nextUniqueId++;
|
|
Name = name;
|
|
}
|
|
|
|
#region Transform
|
|
//
|
|
// Localで値を保持する
|
|
//
|
|
public Vector3 LocalTranslationWithoutUpdate = Vector3.zero;
|
|
public Vector3 LocalTranslation
|
|
{
|
|
get => LocalTranslationWithoutUpdate;
|
|
set
|
|
{
|
|
if (LocalTranslationWithoutUpdate == value) return;
|
|
LocalTranslationWithoutUpdate = value;
|
|
CalcWorldMatrix(Parent != null ? Parent.Matrix : Matrix4x4.identity);
|
|
}
|
|
}
|
|
|
|
public Quaternion LocalRotationWithoutUpdate = Quaternion.identity;
|
|
public Quaternion LocalRotation
|
|
{
|
|
get => LocalRotationWithoutUpdate;
|
|
set
|
|
{
|
|
if (LocalRotationWithoutUpdate == value) return;
|
|
LocalRotationWithoutUpdate = value;
|
|
CalcWorldMatrix(Parent != null ? Parent.Matrix : Matrix4x4.identity);
|
|
}
|
|
}
|
|
|
|
public Vector3 LocalScalingWithoutUpdate = Vector3.one;
|
|
public Vector3 LocalScaling
|
|
{
|
|
get => LocalScalingWithoutUpdate;
|
|
set
|
|
{
|
|
if (LocalScalingWithoutUpdate == value) return;
|
|
LocalScalingWithoutUpdate = value;
|
|
CalcWorldMatrix(Parent != null ? Parent.Matrix : Matrix4x4.identity);
|
|
}
|
|
}
|
|
|
|
public Matrix4x4 LocalMatrix
|
|
{
|
|
get => Matrix4x4.Translate(LocalTranslation)
|
|
* Matrix4x4.Rotate(LocalRotation)
|
|
* Matrix4x4.Scale(LocalScaling)
|
|
;
|
|
}
|
|
|
|
public void SetLocalMatrix(Matrix4x4 value, bool calcWorldMatrix)
|
|
{
|
|
(LocalTranslationWithoutUpdate, LocalRotationWithoutUpdate, LocalScalingWithoutUpdate) = value.Decompose();
|
|
CalcWorldMatrix(Parent != null ? Parent.Matrix : Matrix4x4.identity, calcWorldMatrix);
|
|
}
|
|
|
|
Matrix4x4 m_matrix = Matrix4x4.identity;
|
|
public Matrix4x4 Matrix
|
|
{
|
|
get => m_matrix;
|
|
}
|
|
|
|
public Quaternion Rotation
|
|
{
|
|
get
|
|
{
|
|
return Matrix.rotation;
|
|
}
|
|
set
|
|
{
|
|
if (Parent == null)
|
|
{
|
|
LocalRotation = value;
|
|
}
|
|
else
|
|
{
|
|
LocalRotation = Quaternion.Inverse(Parent.Rotation) * value;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void SetMatrix(Matrix4x4 m, bool calcWorldMatrix)
|
|
{
|
|
if (Parent != null)
|
|
{
|
|
SetLocalMatrix(Parent.InverseMatrix * m, calcWorldMatrix);
|
|
}
|
|
else
|
|
{
|
|
SetLocalMatrix(m, calcWorldMatrix);
|
|
}
|
|
}
|
|
|
|
public Matrix4x4 InverseMatrix
|
|
{
|
|
get
|
|
{
|
|
return Matrix.inverse;
|
|
}
|
|
}
|
|
|
|
public void CalcWorldMatrix(bool calcChildren = true)
|
|
{
|
|
if (Parent == null)
|
|
{
|
|
CalcWorldMatrix(Matrix4x4.identity, calcChildren);
|
|
}
|
|
else
|
|
{
|
|
CalcWorldMatrix(Parent.Matrix, calcChildren);
|
|
}
|
|
}
|
|
|
|
public void CalcWorldMatrix(Matrix4x4 parent, bool calcChildren = true)
|
|
{
|
|
var value = parent * LocalMatrix;
|
|
m_matrix = value;
|
|
|
|
RaiseMatrixUpdated();
|
|
|
|
if (calcChildren)
|
|
{
|
|
foreach (var child in Children)
|
|
{
|
|
child.CalcWorldMatrix(m_matrix, calcChildren);
|
|
}
|
|
}
|
|
}
|
|
|
|
public event Action<Matrix4x4> MatrixUpdated;
|
|
void RaiseMatrixUpdated()
|
|
{
|
|
var handle = MatrixUpdated;
|
|
if (handle != null)
|
|
{
|
|
handle(Matrix);
|
|
}
|
|
}
|
|
|
|
public Vector3 Translation
|
|
{
|
|
get => Matrix.GetColumn(3);
|
|
set
|
|
{
|
|
if (Parent == null)
|
|
{
|
|
LocalTranslation = value;
|
|
}
|
|
else
|
|
{
|
|
LocalTranslation = Parent.InverseMatrix.MultiplyPoint(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
public Vector3 SkeletonLocalPosition
|
|
{
|
|
get => Translation;
|
|
set
|
|
{
|
|
Translation = value;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Hierarchy
|
|
|
|
public Node Parent { get; private set; }
|
|
|
|
public IEnumerable<Node> Ancestors()
|
|
{
|
|
if (Parent == null)
|
|
{
|
|
yield break;
|
|
}
|
|
yield return Parent;
|
|
foreach (var x in Parent.Ancestors())
|
|
{
|
|
yield return x;
|
|
}
|
|
}
|
|
|
|
readonly List<Node> m_children = new List<Node>();
|
|
|
|
public void Add(Node child, ChildMatrixMode mode = ChildMatrixMode.KeepLocal)
|
|
{
|
|
if (child.Parent != null)
|
|
{
|
|
child.Parent.m_children.Remove(child);
|
|
}
|
|
m_children.Add(child);
|
|
child.Parent = this;
|
|
|
|
switch (mode)
|
|
{
|
|
case ChildMatrixMode.KeepLocal:
|
|
child.CalcWorldMatrix(Matrix);
|
|
break;
|
|
|
|
case ChildMatrixMode.KeepWorld:
|
|
child.SetMatrix(child.Matrix, true);
|
|
break;
|
|
}
|
|
}
|
|
|
|
public void Remove(Node child)
|
|
{
|
|
child.Parent = null;
|
|
m_children.Remove(child);
|
|
}
|
|
|
|
public IEnumerable<Node> Traverse()
|
|
{
|
|
yield return this;
|
|
|
|
foreach (var child in Children)
|
|
{
|
|
foreach (var x in child.Traverse())
|
|
{
|
|
yield return x;
|
|
}
|
|
}
|
|
}
|
|
|
|
public IEnumerator<Node> GetEnumerator()
|
|
{
|
|
return ((IEnumerable<Node>)Children).GetEnumerator();
|
|
}
|
|
|
|
IEnumerator IEnumerable.GetEnumerator()
|
|
{
|
|
return ((IEnumerable<Node>)Children).GetEnumerator();
|
|
}
|
|
#endregion
|
|
|
|
|
|
public MeshGroup MeshGroup;
|
|
|
|
// VRMでは、Meshes.Count==1
|
|
public Mesh Mesh => MeshGroup?.Meshes?[0];
|
|
|
|
HumanoidBones? m_bone;
|
|
public HumanoidBones? HumanoidBone
|
|
{
|
|
get => m_bone;
|
|
set
|
|
{
|
|
if (m_bone == value)
|
|
{
|
|
return;
|
|
}
|
|
if (value == HumanoidBones.unknown)
|
|
{
|
|
return;
|
|
}
|
|
m_bone = value;
|
|
}
|
|
}
|
|
|
|
public IReadOnlyList<Node> Children => m_children;
|
|
|
|
public Node FindBone(HumanoidBones bone)
|
|
{
|
|
return Traverse().FirstOrDefault(x => x.HumanoidBone == bone);
|
|
}
|
|
|
|
public void RotateFromTo(Vector3 worldSrc, Vector3 worldDst)
|
|
{
|
|
// world to local
|
|
var src = Quaternion.Inverse(Rotation) * worldSrc.normalized;
|
|
var dst = Quaternion.Inverse(Rotation) * worldDst.normalized;
|
|
|
|
var dot = Vector3.Dot(src, dst);
|
|
Quaternion rot;
|
|
if (Math.Abs(1.0f - dot) < float.Epsilon)
|
|
{
|
|
// 0degree
|
|
rot = Quaternion.identity;
|
|
}
|
|
else if (Math.Abs(-1.0f - dot) < float.Epsilon)
|
|
{
|
|
// 180degree
|
|
rot = Quaternion.Euler(0, MathFWrap.PI, 0);
|
|
}
|
|
else
|
|
{
|
|
var axis = Vector3.Normalize(Vector3.Cross(src, dst));
|
|
rot = Quaternion.AngleAxis((float)Math.Acos(dot), axis);
|
|
}
|
|
|
|
LocalRotation = rot;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
if (HumanoidBone.HasValue)
|
|
{
|
|
return $"{Name}[{HumanoidBone.Value}]: {LocalTranslation.x:0.00}, {LocalTranslation.y:0.00}, {LocalTranslation.z:0.00}";
|
|
}
|
|
else
|
|
{
|
|
return $"{Name}: {LocalTranslation.x:0.00}, {LocalTranslation.y:0.00}, {LocalTranslation.z:0.00}";
|
|
}
|
|
}
|
|
}
|
|
|
|
public static class NodeExtensions
|
|
{
|
|
public static IEnumerable<Node> Traverse(this Node self)
|
|
{
|
|
yield return self;
|
|
foreach (var child in self.Children)
|
|
{
|
|
foreach (var x in child.Traverse())
|
|
{
|
|
yield return x;
|
|
}
|
|
}
|
|
}
|
|
|
|
public static Vector3 CenterOfDescendant(this Node self)
|
|
{
|
|
var sum = Vector3.zero;
|
|
int i = 0;
|
|
foreach (var x in self.Traverse())
|
|
{
|
|
sum += x.SkeletonLocalPosition;
|
|
++i;
|
|
}
|
|
return sum / i;
|
|
}
|
|
}
|
|
}
|