314 lines
10 KiB
C#
Executable File
314 lines
10 KiB
C#
Executable File
|
|
using System.Numerics;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text.Json;
|
|
using ReFuel.Gltf.Serialization;
|
|
|
|
namespace ReFuel.Gltf
|
|
{
|
|
public class GltfNode : GltfIdObject
|
|
{
|
|
public override GltfObjectKind Kind => GltfObjectKind.Node;
|
|
|
|
private GltfSmartId<GltfIdObject> camera;
|
|
private GltfSmartId<GltfIdObject> skin;
|
|
private GltfSmartId<GltfMesh> mesh;
|
|
private float[]? weights = null;
|
|
private TransformMode transformMode = TransformMode.Matrix;
|
|
private TransformUnion transform = new TransformUnion() { Matrix = Matrix4x4.Identity };
|
|
|
|
public string? Name { get; set; }
|
|
public object? Camera { get; set; }
|
|
public GltfRefList<GltfNode> Children { get; }
|
|
public object? Skin { get; set; }
|
|
|
|
public Matrix4x4 Matrix
|
|
{
|
|
get => transformMode == TransformMode.Matrix ? transform.Matrix : transform.ToMatrix().Matrix;
|
|
set
|
|
{
|
|
transformMode = TransformMode.Matrix;
|
|
transform.Matrix = value;
|
|
}
|
|
}
|
|
|
|
public Quaternion Rotation
|
|
{
|
|
get => transformMode == TransformMode.SRT ? transform.Rotation : transform.ToSRT().Rotation;
|
|
|
|
set
|
|
{
|
|
if (transformMode != TransformMode.SRT)
|
|
{
|
|
transformMode = TransformMode.SRT;
|
|
transform = transform.ToSRT();
|
|
}
|
|
|
|
transform.Rotation = value;
|
|
}
|
|
}
|
|
|
|
public Vector3 Scale
|
|
{
|
|
get => transformMode == TransformMode.SRT ?
|
|
transform.Scale :
|
|
transform.ToSRT().Scale;
|
|
set
|
|
{
|
|
if (transformMode != TransformMode.SRT)
|
|
{
|
|
transformMode = TransformMode.SRT;
|
|
transform = transform.ToSRT();
|
|
}
|
|
|
|
transform.Scale = value;
|
|
}
|
|
}
|
|
|
|
public Vector3 Translation
|
|
{
|
|
get => transformMode == TransformMode.SRT ?
|
|
transform.Translation :
|
|
transform.ToSRT().Translation;
|
|
set
|
|
{
|
|
if (transformMode != TransformMode.SRT)
|
|
{
|
|
transformMode = TransformMode.SRT;
|
|
transform = transform.ToSRT();
|
|
}
|
|
|
|
transform.Translation = value;
|
|
}
|
|
}
|
|
|
|
public GltfMesh? Mesh
|
|
{
|
|
get => mesh.IsSet ? mesh.Value : null;
|
|
set
|
|
{
|
|
if (value is null)
|
|
mesh.Reset();
|
|
else
|
|
mesh.Value = value;
|
|
}
|
|
}
|
|
public float[]? Weights => weights;
|
|
|
|
internal GltfNode(GltfDocument document) : base(document)
|
|
{
|
|
Children = new GltfRefList<GltfNode>(document.Nodes);
|
|
mesh = new GltfSmartId<GltfMesh>(document.Meshes);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Optimize this node for matrix mode transforms.
|
|
/// </summary>
|
|
/// <returns>Self</returns>
|
|
public GltfNode MatrixMode()
|
|
{
|
|
if (transformMode == TransformMode.Matrix)
|
|
return this;
|
|
|
|
transformMode = TransformMode.Matrix;
|
|
transform = transform.ToMatrix();
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Optimize this node for scale rotation translation transforms.
|
|
/// </summary>
|
|
/// <returns>Self</returns>
|
|
public GltfNode SrtMode()
|
|
{
|
|
if (transformMode == TransformMode.SRT)
|
|
return this;
|
|
|
|
transformMode = TransformMode.SRT;
|
|
transform = transform.ToSRT();
|
|
|
|
return this;
|
|
}
|
|
|
|
internal override void Deserialize(JsonElement element)
|
|
{
|
|
if (element.ValueKind != JsonValueKind.Object)
|
|
throw new Exception("Expected node to be an object.");
|
|
|
|
if (element.TryGetProperty("name", out JsonElement nameProperty))
|
|
Name = nameProperty.ToString();
|
|
|
|
if (element.TryGetProperty("camera", out JsonElement cameraProperty))
|
|
{
|
|
if (!cameraProperty.TryGetInt32(out int cameraId))
|
|
throw new Exception("Expected camera id to be a number.");
|
|
|
|
camera.Id = cameraId;
|
|
}
|
|
|
|
if (element.TryGetProperty("skin", out JsonElement skinElement))
|
|
{
|
|
if (!skinElement.TryGetInt32(out int skinId))
|
|
throw new Exception("Expected skin id to be a number.");
|
|
|
|
skin.Id = skinId;
|
|
}
|
|
|
|
if (element.TryGetProperty("mesh", out JsonElement meshElement))
|
|
{
|
|
if (!meshElement.TryGetInt32(out int meshId))
|
|
throw new Exception("Expected mesh id to be a number.");
|
|
|
|
mesh.Id = meshId;
|
|
}
|
|
|
|
if (element.TryGetProperty("children", out JsonElement childrenElement))
|
|
{
|
|
if (childrenElement.ValueKind != JsonValueKind.Array)
|
|
throw new Exception("Expected a JSON array for children.");
|
|
|
|
foreach (JsonElement child in childrenElement.EnumerateArray())
|
|
{
|
|
if (!child.TryGetInt32(out int id))
|
|
throw new Exception("Expected a number for child id.");
|
|
|
|
Children.Add(id);
|
|
}
|
|
}
|
|
|
|
if (element.TryGetProperty("matrix", out JsonElement matrixElement))
|
|
{
|
|
transformMode = TransformMode.Matrix;
|
|
transform.Matrix = matrixElement.ToSingleMatrix(4, 4, Matrix4x4.Identity);
|
|
}
|
|
else
|
|
{
|
|
transformMode = TransformMode.SRT;
|
|
transform = new TransformUnion()
|
|
{
|
|
Scale = Vector3.One,
|
|
Rotation = Quaternion.Identity,
|
|
Translation = Vector3.Zero
|
|
};
|
|
|
|
if (element.TryGetProperty("scale", out JsonElement scaleElement))
|
|
{
|
|
transform.Scale = scaleElement.ToSingleVector(3, Vector3.One);
|
|
}
|
|
|
|
if (element.TryGetProperty("rotation", out JsonElement rotationElement))
|
|
{
|
|
transform.Rotation = rotationElement.ToSingleVector(4, Quaternion.Identity);
|
|
}
|
|
|
|
if (element.TryGetProperty("translation", out JsonElement translationElement))
|
|
{
|
|
transform.Translation = translationElement.ToSingleVector(3, Vector3.One);
|
|
}
|
|
}
|
|
|
|
DeserializeCommon(element);
|
|
}
|
|
|
|
internal override void Serialize(Utf8JsonWriter writer)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Explicit)]
|
|
private struct TransformUnion
|
|
{
|
|
[FieldOffset(0 * sizeof(float))] public Matrix4x4 Matrix;
|
|
|
|
[FieldOffset(0 * sizeof(float))] public Vector3 Scale;
|
|
[FieldOffset(4 * sizeof(float))] public Quaternion Rotation;
|
|
[FieldOffset(8 * sizeof(float))] public Vector3 Translation;
|
|
|
|
public TransformUnion ToSRT()
|
|
{
|
|
Vector3 r0 = new Vector3(Matrix.M11, Matrix.M12, Matrix.M13);
|
|
Vector3 r1 = new Vector3(Matrix.M11, Matrix.M12, Matrix.M13);
|
|
Vector3 r2 = new Vector3(Matrix.M11, Matrix.M12, Matrix.M13);
|
|
|
|
float r0l = r0.Length();
|
|
float r1l = r1.Length();
|
|
float r2l = r2.Length();
|
|
|
|
Vector3 scale = new Vector3(r0l, r1l, r2l);
|
|
Vector3 translation = new Vector3(Matrix.M41, Matrix.M42, Matrix.M43);
|
|
|
|
// The following code segment has been adapted from OpenTK.Mathematics (MIT), which adapted from
|
|
// Blender (GPLv2 or more permissive).
|
|
// However, it is probably not copyrighted considering it is math.
|
|
|
|
r0 /= r0l;
|
|
r1 /= r1l;
|
|
r2 /= r2l;
|
|
|
|
Quaternion rotation = default;
|
|
float trace = 0.25f * (r0.X + r1.Y + r2.Z + 1f);
|
|
|
|
if (trace > 0)
|
|
{
|
|
float sq = MathF.Sqrt(trace);
|
|
|
|
rotation.W = sq;
|
|
sq = 1.0f / (4.0f * sq);
|
|
rotation.X = (r1.Z - r2.Y) * sq;
|
|
rotation.Y = (r2.X - r0.Z) * sq;
|
|
rotation.Z = (r0.Y - r1.X) * sq;
|
|
}
|
|
else if (r0.X > r1.Y && r0.X > r2.Z)
|
|
{
|
|
float sq = 2.0f * MathF.Sqrt(1.0f + r0.X - r1.Y - r2.Z);
|
|
|
|
rotation.X = 0.25f * sq;
|
|
sq = 1.0f / sq;
|
|
rotation.W = (r2.Y - r1.Z) * sq;
|
|
rotation.Y = (r1.X + r0.Y) * sq;
|
|
rotation.Z = (r2.X + r0.Z) * sq;
|
|
}
|
|
else if (r1.Y > r2.Z)
|
|
{
|
|
float sq = 2.0f * MathF.Sqrt(1.0f + r1.Y - r0.X - r2.Z);
|
|
|
|
rotation.Y = 0.25f * sq;
|
|
sq = 1.0f / sq;
|
|
rotation.W = (r2.X - r0.Z) * sq;
|
|
rotation.X = (r1.X + r0.Y) * sq;
|
|
rotation.Z = (r2.Y + r1.Z) * sq;
|
|
}
|
|
else
|
|
{
|
|
float sq = 2.0f * MathF.Sqrt(1.0f + r2.Z - r0.X - r1.Y);
|
|
|
|
rotation.Z = (float)(0.25 * sq);
|
|
sq = 1.0f / sq;
|
|
rotation.W = (float)((r1.X - r0.Y) * sq);
|
|
rotation.X = (float)((r2.X + r0.Z) * sq);
|
|
rotation.Y = (float)((r2.Y + r1.Z) * sq);
|
|
}
|
|
|
|
rotation = Quaternion.Normalize(rotation);
|
|
|
|
return new TransformUnion() { Scale = scale, Rotation = rotation, Translation = translation };
|
|
}
|
|
|
|
public TransformUnion ToMatrix()
|
|
{
|
|
Matrix4x4 matrix = Matrix4x4.CreateScale(Scale) *
|
|
Matrix4x4.CreateFromQuaternion(Rotation) *
|
|
Matrix4x4.CreateTranslation(Translation);
|
|
|
|
return new TransformUnion() { Matrix = matrix };
|
|
}
|
|
}
|
|
|
|
private enum TransformMode
|
|
{
|
|
Matrix,
|
|
SRT
|
|
}
|
|
}
|
|
}
|