Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Linq;
using System.Numerics;
using System.Text;
using SharpGLTF.Schema2;
namespace SharpGLTF.Scenes
{
/// <summary>
/// Defines a node object within an armature.
/// </summary>
[System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
public class NodeBuilder
{
#region debug
private string _GetDebuggerDisplay()
{
var txt = $"NodeBuilder";
if (!string.IsNullOrWhiteSpace(this.Name)) txt += $" {this.Name}";
if (_Matrix.HasValue)
{
if (_Matrix.Value != Matrix4x4.Identity)
{
var xform = this.LocalTransform;
if (xform.Scale != Vector3.One) txt += $" 𝐒:{xform.Scale}";
if (xform.Rotation != Quaternion.Identity) txt += $" 𝐑:{xform.Rotation}";
if (xform.Translation != Vector3.Zero) txt += $" 𝚻:{xform.Translation}";
}
}
else
{
if (_Scale != null) txt += $" 𝐒:{_Scale.Value}";
if (_Rotation != null) txt += $" 𝐑:{_Rotation.Value}";
if (_Translation != null) txt += $" 𝚻:{_Translation.Value}";
}
if (this.VisualChildren.Any())
{
txt += $" | Children[{this.VisualChildren.Count}]";
}
return txt;
}
#endregion
#region lifecycle
public NodeBuilder() { }
public NodeBuilder(string name) { Name = name; }
public Dictionary<NodeBuilder, NodeBuilder> DeepClone()
{
var dict = new Dictionary<NodeBuilder, NodeBuilder>();
DeepClone(dict);
return dict;
}
private NodeBuilder DeepClone(IDictionary<NodeBuilder, NodeBuilder> nodeMap)
{
var clone = new NodeBuilder();
nodeMap[this] = clone;
clone.Name = this.Name;
clone._Matrix = this._Matrix;
clone._Scale = this._Scale?.Clone();
clone._Rotation = this._Rotation?.Clone();
clone._Translation = this._Translation?.Clone();
clone.Extras = this.Extras.DeepClone();
foreach (var c in _Children)
{
clone.AddNode(c.DeepClone(nodeMap));
}
return clone;
}
#endregion
#region data
private NodeBuilder _Parent;
private readonly List<NodeBuilder> _Children = new List<NodeBuilder>();
private Matrix4x4? _Matrix;
private Animations.AnimatableProperty<Vector3> _Scale;
private Animations.AnimatableProperty<Quaternion> _Rotation;
private Animations.AnimatableProperty<Vector3> _Translation;
#endregion
#region properties
public String Name { get; set; }
public IO.JsonContent Extras { get; set; }
#endregion
#region properties - hierarchy
public NodeBuilder Parent => _Parent;
public NodeBuilder Root => _Parent == null ? this : _Parent.Root;
public IReadOnlyList<NodeBuilder> VisualChildren => _Children;
#endregion
#region properties - transform
/// <summary>
/// Gets a value indicating whether this <see cref="NodeBuilder"/> has animations.
/// </summary>
public bool HasAnimations => (_Scale?.IsAnimated ?? false) || (_Rotation?.IsAnimated ?? false) || (_Translation?.IsAnimated ?? false);
public Animations.AnimatableProperty<Vector3> Scale => _Scale;
public Animations.AnimatableProperty<Quaternion> Rotation => _Rotation;
public Animations.AnimatableProperty<Vector3> Translation => _Translation;
/// <summary>
/// Gets or sets the local transform <see cref="Matrix4x4"/> of this <see cref="NodeBuilder"/>.
/// </summary>
public Matrix4x4 LocalMatrix
{
get => Transforms.Matrix4x4Factory.CreateFrom(_Matrix, Scale?.Value, Rotation?.Value, Translation?.Value);
set
{
if (HasAnimations) { _DecomposeMatrix(value); return; }
_Matrix = value != Matrix4x4.Identity ? value : (Matrix4x4?)null;
_Scale = null;
_Rotation = null;
_Translation = null;
}
}
internal Transforms.Matrix4x4Double LocalMatrixPrecise
{
get
{
if (_Matrix.HasValue) return new Transforms.Matrix4x4Double(_Matrix.Value);
var s = _Scale?.Value ?? Vector3.One;
var r = _Rotation?.Value ?? Quaternion.Identity;
var t = _Translation?.Value ?? Vector3.Zero;
return
Transforms.Matrix4x4Double.CreateScale(s.X, s.Y, s.Z)
*
Transforms.Matrix4x4Double.CreateFromQuaternion(r.Sanitized())
*
Transforms.Matrix4x4Double.CreateTranslation(t.X, t.Y, t.Z);
}
}
#pragma warning disable CA1721 // Property names should not match get methods
/// <summary>
/// Gets or sets the local Scale, Rotation and Translation of this <see cref="NodeBuilder"/>.
/// </summary>
public Transforms.AffineTransform LocalTransform
{
get => Transforms.AffineTransform.CreateFromAny(_Matrix, _Scale?.Value, _Rotation?.Value, Translation?.Value);
set
{
Guard.IsTrue(value.IsValid, nameof(value));
_Matrix = null;
if (value.Scale != Vector3.One) UseScale().Value = value.Scale;
if (value.Rotation != Quaternion.Identity) UseRotation().Value = value.Rotation;
if (value.Translation != Vector3.Zero) UseTranslation().Value = value.Translation;
}
}
/// <summary>
/// Gets or sets the world transform <see cref="Matrix4x4"/> of this <see cref="NodeBuilder"/>.
/// </summary>
public Matrix4x4 WorldMatrix
{
get
{
var vs = this.Parent;
return vs == null ? LocalMatrix : Transforms.Matrix4x4Factory.LocalToWorld(vs.WorldMatrix, LocalMatrix);
}
set
{
var vs = this.Parent;
LocalMatrix = vs == null ? value : Transforms.Matrix4x4Factory.WorldToLocal(vs.WorldMatrix, value);
}
}
internal Transforms.Matrix4x4Double WorldMatrixPrecise
{
get
{
var vs = this.Parent;
return vs == null ? LocalMatrixPrecise : LocalMatrixPrecise * vs.WorldMatrixPrecise;
}
}
#pragma warning restore CA1721 // Property names should not match get methods
#endregion
#region API - hierarchy
public NodeBuilder CreateNode(string name = null)
{
var c = new NodeBuilder();
c.Name = name;
AddNode(c);
return c;
}
public void AddNode(NodeBuilder node)
{
Guard.NotNull(node, nameof(node));
Guard.IsFalse(Object.ReferenceEquals(this, node), "cannot add to itself");
if (node._Parent == this) return; // already added to this node.
Guard.MustBeNull(node._Parent, nameof(node), "is child of another node.");
node._Parent = this;
_Children.Add(node);
}
/// <summary>
/// Checks if the collection of joints can be used for skinning a mesh.
/// </summary>
/// <param name="joints">A collection of joints.</param>
/// <returns>True if the joints can be used for skinning.</returns>
public static bool IsValidArmature(IEnumerable<NodeBuilder> joints)
{
if (joints == null) return false;
if (!joints.Any()) return false;
if (joints.Any(item => item == null)) return false;
var root = joints.First().Root;
// check if all joints share the same root
if (!joints.All(item => Object.ReferenceEquals(item.Root, root))) return false;
var nameGroups = Flatten(root)
.Where(item => item.Name != null)
.GroupBy(item => item.Name);
if (nameGroups.Any(group => group.Count() > 1)) return false;
return true;
}
public static IEnumerable<NodeBuilder> Flatten(NodeBuilder container)
{
if (container == null) yield break;
yield return container;
foreach (var c in container.VisualChildren)
{
var cc = Flatten(c);
foreach (var ccc in cc) yield return ccc;
}
}
/// <summary>
/// Rename all the <see cref="NodeBuilder"/> elements in <paramref name="collection"/>
/// so every node has a valid, unique name.
/// </summary>
/// <param name="collection">A collection of <see cref="NodeBuilder"/> elements.</param>
/// <param name="namePrefix">The name prefix.</param>
/// <remarks>
/// This was originally intended to help in solving the problem that many engines don't
/// support two nodes to have the same name. But ultimately, it's these engine's responsability
/// to deal with glTF specifications.
/// </remarks>
[Obsolete("It does not belong here.")]
public static void Rename(IEnumerable<NodeBuilder> collection, string namePrefix)
{
if (collection == null) return;
var names = new HashSet<string>();
var index = -1;
foreach (var item in collection)
{
++index;
// if the current name is already valid, keep it.
if (!string.IsNullOrWhiteSpace(item.Name))
{
if (item.RenameIfAvailable(item.Name, names)) continue;
}
// try with a default name
var newName = $"{namePrefix}{index}";
if (item.RenameIfAvailable(newName, names)) continue;
// retry with different names until finding a valid name.
for (int i = 0; i < int.MaxValue; ++i)
{
newName = $"{namePrefix}{index}-{i}";
if (item.RenameIfAvailable(newName, names)) break;
}
}
}
private bool RenameIfAvailable(string newName, ISet<string> usedNames)
{
if (usedNames.Contains(newName)) return false;
this.Name = newName;
usedNames.Add(newName);
return true;
}
#endregion
#region API - transform
private void _DecomposeMatrix()
{
if (!_Matrix.HasValue) return;
var m = _Matrix.Value;
_Matrix = null;
// we do the decomposition AFTER setting _Matrix to null to prevent an infinite recursive loop. Fixes #37
if (m != Matrix4x4.Identity) _DecomposeMatrix(m);
}
private void _DecomposeMatrix(Matrix4x4 matrix)
{
var affine = new Transforms.AffineTransform(matrix);
UseScale().Value = affine.Scale;
UseRotation().Value = affine.Rotation;
UseTranslation().Value = affine.Translation;
}
public Animations.AnimatableProperty<Vector3> UseScale()
{
_DecomposeMatrix();
if (_Scale == null)
{
_Scale = new Animations.AnimatableProperty<Vector3>();
_Scale.Value = Vector3.One;
}
return _Scale;
}
public Animations.CurveBuilder<Vector3> UseScale(string animationTrack)
{
return UseScale().UseTrackBuilder(animationTrack);
}
public Animations.AnimatableProperty<Quaternion> UseRotation()
{
_DecomposeMatrix();
if (_Rotation == null)
{
_Rotation = new Animations.AnimatableProperty<Quaternion>();
_Rotation.Value = Quaternion.Identity;
}
return _Rotation;
}
public Animations.CurveBuilder<Quaternion> UseRotation(string animationTrack)
{
return UseRotation().UseTrackBuilder(animationTrack);
}
public Animations.AnimatableProperty<Vector3> UseTranslation()
{
_DecomposeMatrix();
if (_Translation == null)
{
_Translation = new Animations.AnimatableProperty<Vector3>();
_Translation.Value = Vector3.Zero;
}
return _Translation;
}
public Animations.CurveBuilder<Vector3> UseTranslation(string animationTrack)
{
return UseTranslation().UseTrackBuilder(animationTrack);
}
public void SetScaleTrack(string track, Animations.ICurveSampler<Vector3> curve) { UseScale().SetTrack(track, curve); }
public void SetTranslationTrack(string track, Animations.ICurveSampler<Vector3> curve) { UseTranslation().SetTrack(track, curve); }
public void SetRotationTrack(string track, Animations.ICurveSampler<Quaternion> curve) { UseRotation().SetTrack(track, curve); }
public Transforms.AffineTransform GetLocalTransform(string animationTrack, float time)
{
if (animationTrack == null) return this.LocalTransform;
var scale = Scale?.GetValueAt(animationTrack, time);
var rotation = Rotation?.GetValueAt(animationTrack, time);
var translation = Translation?.GetValueAt(animationTrack, time);
return new Transforms.AffineTransform(scale, rotation, translation);
}
public Matrix4x4 GetWorldMatrix(string animationTrack, float time)
{
if (animationTrack == null) return this.WorldMatrix;
var vs = Parent;
var lm = GetLocalTransform(animationTrack, time).Matrix;
return vs == null ? lm : Transforms.Matrix4x4Factory.LocalToWorld(vs.GetWorldMatrix(animationTrack, time), lm);
}
public Matrix4x4 GetInverseBindMatrix(Matrix4x4? meshWorldMatrix = null)
{
Transforms.Matrix4x4Double mwx = meshWorldMatrix ?? Matrix4x4.Identity;
return (Matrix4x4)Transforms.SkinnedTransform.CalculateInverseBinding(mwx, this.WorldMatrixPrecise);
}
#endregion
#region With* API
public NodeBuilder WithLocalTranslation(Vector3 translation)
{
this.UseTranslation().Value = translation;
return this;
}
public NodeBuilder WithLocalScale(Vector3 scale)
{
this.UseScale().Value = scale;
return this;
}
public NodeBuilder WithLocalRotation(Quaternion rotation)
{
this.UseRotation().Value = rotation;
return this;
}
public NodeBuilder WithLocalTranslation(string animTrack, IReadOnlyDictionary<float, Vector3> keyframes)
{
Guard.NotNull(keyframes, nameof(keyframes));
var track = this.UseTranslation(animTrack);
foreach (var kf in keyframes)
{
track.SetPoint(kf.Key, kf.Value);
}
return this;
}
public NodeBuilder WithLocalRotation(string animTrack, IReadOnlyDictionary<float, Quaternion> keyframes)
{
Guard.NotNull(keyframes, nameof(keyframes));
var track = this.UseRotation(animTrack);
foreach (var kf in keyframes)
{
track.SetPoint(kf.Key, kf.Value);
}
return this;
}
public NodeBuilder WithLocalScale(string animTrack, IReadOnlyDictionary<float, Vector3> keyframes)
{
Guard.NotNull(keyframes, nameof(keyframes));
var track = this.UseScale(animTrack);
foreach (var kf in keyframes)
{
track.SetPoint(kf.Key, kf.Value);
}
return this;
}
public NodeBuilder WithExtras(IO.JsonContent content)
{
Extras = content;
return this;
}
#endregion
}
}
d49ffeb42This file is pinned to the selected release. Download the original to open it in your design tool.