mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 13:01:52 +00:00
Start of AnimationSystem
This commit is contained in:
@@ -3,13 +3,19 @@ using System.Collections;
|
|||||||
using cgltf;
|
using cgltf;
|
||||||
using GlitchyEngine.Math;
|
using GlitchyEngine.Math;
|
||||||
using GlitchyEngine.Renderer;
|
using GlitchyEngine.Renderer;
|
||||||
|
using GlitchyEngine.Renderer.Animation;
|
||||||
|
|
||||||
namespace GlitchyEngine.Content
|
namespace GlitchyEngine.Content
|
||||||
{
|
{
|
||||||
public static class ModelLoader
|
public static class ModelLoader
|
||||||
{
|
{
|
||||||
public static void LoadModel(String filename, GraphicsContext context, Effect validationEffect, List<(Matrix Transform, GeometryBinding Model)> output)
|
static readonly Matrix RightToLeftHand = .Scaling(1, 1, -1);
|
||||||
|
|
||||||
|
public static void LoadModel(String filename, GraphicsContext context, Effect validationEffect, List<(Matrix Transform, GeometryBinding Model)> output, out Skeleton skeleton, out AnimationClip clip)
|
||||||
{
|
{
|
||||||
|
skeleton = null;
|
||||||
|
clip = null;
|
||||||
|
|
||||||
CGLTF.Options options = .();
|
CGLTF.Options options = .();
|
||||||
CGLTF.Data* data;
|
CGLTF.Data* data;
|
||||||
CGLTF.Result result = CGLTF.ParseFile(options, filename, out data);
|
CGLTF.Result result = CGLTF.ParseFile(options, filename, out data);
|
||||||
@@ -25,7 +31,9 @@ namespace GlitchyEngine.Content
|
|||||||
if(node.Mesh != null)
|
if(node.Mesh != null)
|
||||||
{
|
{
|
||||||
Matrix transform = ?;
|
Matrix transform = ?;
|
||||||
CGLTF.NodeTransformWorld(&node, (float*)&transform);
|
CGLTF.NodeTransformLocal(&node, (float*)&transform);
|
||||||
|
|
||||||
|
transform = RightToLeftHand * transform;
|
||||||
|
|
||||||
for(var primitive in node.Mesh.Primitives)
|
for(var primitive in node.Mesh.Primitives)
|
||||||
{
|
{
|
||||||
@@ -34,6 +42,11 @@ namespace GlitchyEngine.Content
|
|||||||
output.Add((transform, binding));
|
output.Add((transform, binding));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(node.Skin != null)
|
||||||
|
{
|
||||||
|
(skeleton, clip) = ExtractBonestuff(node.Skin, data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO make awesome stuff */
|
/* TODO make awesome stuff */
|
||||||
@@ -96,6 +109,8 @@ namespace GlitchyEngine.Content
|
|||||||
|
|
||||||
VertexBuffer vertexBuffer = null;
|
VertexBuffer vertexBuffer = null;
|
||||||
|
|
||||||
|
binding.VertexCount = (uint32)attribute.Data.Count;
|
||||||
|
|
||||||
// Get vertex buffer
|
// Get vertex buffer
|
||||||
{
|
{
|
||||||
CGLTF.BufferView* bufferView = attribute.Data.BufferView;
|
CGLTF.BufferView* bufferView = attribute.Data.BufferView;
|
||||||
@@ -130,7 +145,14 @@ namespace GlitchyEngine.Content
|
|||||||
binding.SetVertexBufferSlot(bufferBinding, (.)bindingSlot);
|
binding.SetVertexBufferSlot(bufferBinding, (.)bindingSlot);
|
||||||
}
|
}
|
||||||
|
|
||||||
VertexElement element = .(format, new String(attribute.Name), true, (.)attribute.Index, (.)bindingSlot);
|
StringView strView = .(attribute.Name);
|
||||||
|
|
||||||
|
while((*(strView.EndPtr - 1)).IsDigit)
|
||||||
|
{
|
||||||
|
strView.Length--;
|
||||||
|
}
|
||||||
|
|
||||||
|
VertexElement element = .(format, new String(strView), true, (.)attribute.Index, (.)bindingSlot);
|
||||||
elements.Add(element);
|
elements.Add(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,9 +168,178 @@ namespace GlitchyEngine.Content
|
|||||||
binding.SetVertexLayout(layout..ReleaseRefNoDelete());
|
binding.SetVertexLayout(layout..ReleaseRefNoDelete());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: calculate normals if missing
|
||||||
|
// TODO: calculate tangents if missing (MikkTSpace algorithm)
|
||||||
|
// Note: Bitangent = cross(normal, tangent.xyz) * tangent.w
|
||||||
|
|
||||||
return binding;
|
return binding;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static (Skeleton, AnimationClip) ExtractBonestuff(CGLTF.Skin* skin, CGLTF.Data* data)
|
||||||
|
{
|
||||||
|
Skeleton skeleton = new Skeleton();
|
||||||
|
skeleton.Joints = new Joint[skin.Joints.Length];
|
||||||
|
|
||||||
|
AnimationClip testClip = new AnimationClip();
|
||||||
|
testClip.Skeleton = skeleton;
|
||||||
|
testClip.FramesPerSecond = 0;
|
||||||
|
testClip.JointAnimations = new JointAnimation[skeleton.Joints.Count];
|
||||||
|
//testClip.Samples = new AnimationSample[1];
|
||||||
|
//testClip.Samples[0].JointPose = new JointPose[skeleton.Joints.Count];
|
||||||
|
testClip.IsLooping = true;
|
||||||
|
|
||||||
|
for(int i < skin.Joints.Length)
|
||||||
|
{
|
||||||
|
ref Joint joint = ref skeleton.Joints[i];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
joint.InverseBindPose = GetEntry<Matrix>(skin.InverseBindMatrices, i);
|
||||||
|
|
||||||
|
joint.Name = new String(skin.Joints[i].Name);
|
||||||
|
|
||||||
|
int parentId = skin.Joints.IndexOf(skin.Joints[i].Parent);
|
||||||
|
|
||||||
|
Log.EngineLogger.AssertDebug(parentId < uint8.MaxValue, scope $"A skeleton must not have more than {uint8.MaxValue - 1} bones.");
|
||||||
|
|
||||||
|
if(parentId == -1)
|
||||||
|
joint.ParentID = uint8.MaxValue;
|
||||||
|
else
|
||||||
|
joint.ParentID = (uint8)parentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(var channel in data.Animations[0].Channels)
|
||||||
|
{
|
||||||
|
int nodeIndex = skin.Joints.IndexOf(channel.TargetNode);
|
||||||
|
|
||||||
|
Log.EngineLogger.AssertDebug(nodeIndex != -1);
|
||||||
|
|
||||||
|
ref JointAnimation jointAnimation = ref testClip.JointAnimations[nodeIndex];
|
||||||
|
|
||||||
|
Log.EngineLogger.AssertDebug(channel.Sampler.Input.Count == channel.Sampler.Output.Count);
|
||||||
|
|
||||||
|
int samples = (int)channel.Sampler.Input.Count;
|
||||||
|
|
||||||
|
Log.EngineLogger.AssertDebug(channel.Sampler.Input.ComponentType == .R_32f);
|
||||||
|
Log.EngineLogger.AssertDebug(channel.Sampler.Input.Type == .Scalar);
|
||||||
|
|
||||||
|
InterpolationMode interpolationMode;
|
||||||
|
|
||||||
|
switch(channel.Sampler.Interpolation)
|
||||||
|
{
|
||||||
|
case .Step:
|
||||||
|
interpolationMode = .Step;
|
||||||
|
case .Linear:
|
||||||
|
interpolationMode = .Linear;
|
||||||
|
case .CubicSpline:
|
||||||
|
interpolationMode = .CubicSpline;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(channel.TargetPath)
|
||||||
|
{
|
||||||
|
case .Translation:
|
||||||
|
jointAnimation.TranslationChannel = new .(samples, interpolationMode);
|
||||||
|
|
||||||
|
Log.EngineLogger.AssertDebug(channel.Sampler.Output.ComponentType == .R_32f);
|
||||||
|
Log.EngineLogger.AssertDebug(channel.Sampler.Output.Type == .Vec3);
|
||||||
|
|
||||||
|
for(int i < samples)
|
||||||
|
{
|
||||||
|
float timeStamp = GetEntry<float>(channel.Sampler.Input, i);
|
||||||
|
Vector3 sample = GetEntry<Vector3>(channel.Sampler.Output, i);
|
||||||
|
/*
|
||||||
|
float timeStamp = GetEntry<float>(channel.Sampler.Input, i);
|
||||||
|
float timeStamp2 = ?;
|
||||||
|
CGLTF.AccessorReadFloat(channel.Sampler.Input, (uint)i, (float*)&timeStamp2, 1);
|
||||||
|
|
||||||
|
Log.EngineLogger.Assert(timeStamp == timeStamp2);
|
||||||
|
|
||||||
|
Vector3 sample2 = ?;
|
||||||
|
CGLTF.AccessorReadFloat(channel.Sampler.Output, (uint)i, (float*)&sample2, 3);
|
||||||
|
|
||||||
|
Log.EngineLogger.Assert(sample == sample2);
|
||||||
|
*/
|
||||||
|
jointAnimation.TranslationChannel.TimeStamps[i] = timeStamp;
|
||||||
|
jointAnimation.TranslationChannel.Values[i] = sample;
|
||||||
|
}
|
||||||
|
case .Rotation:
|
||||||
|
jointAnimation.RotationChannel = new .(samples, interpolationMode);
|
||||||
|
|
||||||
|
Log.EngineLogger.AssertDebug(channel.Sampler.Output.ComponentType == .R_32f);
|
||||||
|
Log.EngineLogger.AssertDebug(channel.Sampler.Output.Type == .Vec4);
|
||||||
|
|
||||||
|
for(int i < samples)
|
||||||
|
{
|
||||||
|
float timeStamp = GetEntry<float>(channel.Sampler.Input, i);
|
||||||
|
Quaternion sample = GetEntry<Quaternion>(channel.Sampler.Output, i);
|
||||||
|
|
||||||
|
jointAnimation.RotationChannel.TimeStamps[i] = timeStamp;
|
||||||
|
jointAnimation.RotationChannel.Values[i] = sample;
|
||||||
|
}
|
||||||
|
case .Scale:
|
||||||
|
jointAnimation.ScaleChannel = new .(samples, interpolationMode);
|
||||||
|
|
||||||
|
Log.EngineLogger.AssertDebug(channel.Sampler.Output.ComponentType == .R_32f);
|
||||||
|
Log.EngineLogger.AssertDebug(channel.Sampler.Output.Type == .Vec3);
|
||||||
|
|
||||||
|
for(int i < samples)
|
||||||
|
{
|
||||||
|
float timeStamp = GetEntry<float>(channel.Sampler.Input, i);
|
||||||
|
Vector3 sample = GetEntry<Vector3>(channel.Sampler.Output, i);
|
||||||
|
|
||||||
|
jointAnimation.ScaleChannel.TimeStamps[i] = timeStamp;
|
||||||
|
jointAnimation.ScaleChannel.Values[i] = sample;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
Log.EngineLogger.Error($"Unknown channel target path \"{channel.TargetPath}\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
//channel.
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
//testClip.JointAnimations[i].
|
||||||
|
testClip.Duration = Math.Max(testClip.Duration, jointAnimation.Duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (skeleton, testClip);
|
||||||
|
}
|
||||||
|
|
||||||
|
static T GetEntry<T>(CGLTF.Accessor* accessor, int index)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.AssertDebug((uint)index < accessor.Count);
|
||||||
|
|
||||||
|
T result = ?;
|
||||||
|
|
||||||
|
switch(typeof(T))
|
||||||
|
{
|
||||||
|
case typeof(float):
|
||||||
|
CGLTF.AccessorReadFloat(accessor, (uint)index, (float*)&result, 1);
|
||||||
|
case typeof(Vector2):
|
||||||
|
CGLTF.AccessorReadFloat(accessor, (uint)index, (float*)&result, 2);
|
||||||
|
case typeof(Vector3):
|
||||||
|
CGLTF.AccessorReadFloat(accessor, (uint)index, (float*)&result, 3);
|
||||||
|
case typeof(Vector4), typeof(Quaternion):
|
||||||
|
CGLTF.AccessorReadFloat(accessor, (uint)index, (float*)&result, 4);
|
||||||
|
case typeof(Matrix):
|
||||||
|
CGLTF.AccessorReadFloat(accessor, (uint)index, (float*)&result, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
/*
|
||||||
|
uint8* data = (uint8*)accessor.BufferView.Buffer.Data;
|
||||||
|
|
||||||
|
data += accessor.BufferView.Offset;
|
||||||
|
|
||||||
|
data += accessor.Offset;
|
||||||
|
|
||||||
|
data += accessor.Stride * (uint)index;
|
||||||
|
|
||||||
|
return *(T*)data;
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the vector and component type to the corresponding Format.
|
* Converts the vector and component type to the corresponding Format.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,258 @@
|
|||||||
|
using GlitchyEngine.Math;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace GlitchyEngine.Renderer.Animation
|
||||||
|
{
|
||||||
|
class AnimationPlayer
|
||||||
|
{
|
||||||
|
public Skeleton Skeleton;
|
||||||
|
public AnimationClip CurrentClip;
|
||||||
|
public float TimeStamp;
|
||||||
|
|
||||||
|
public SkeletonPose Pose ~ delete _;
|
||||||
|
|
||||||
|
public Matrix[] SkinningMatricies ~ delete _;
|
||||||
|
public Matrix3x3[] InvTransSkinningMatricies ~ delete _;
|
||||||
|
|
||||||
|
public this(Skeleton skeleton, AnimationClip clip)
|
||||||
|
{
|
||||||
|
Skeleton = skeleton;
|
||||||
|
CurrentClip = clip;
|
||||||
|
|
||||||
|
Pose = new SkeletonPose(Skeleton);
|
||||||
|
|
||||||
|
SkinningMatricies = new Matrix[Skeleton.Joints.Count];
|
||||||
|
InvTransSkinningMatricies = new Matrix3x3[Skeleton.Joints.Count];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(GameTime gameTime)
|
||||||
|
{
|
||||||
|
TimeStamp += (float)gameTime.FrameTime.TotalSeconds;
|
||||||
|
|
||||||
|
if(CurrentClip.IsLooping && CurrentClip.Duration != 0)
|
||||||
|
{
|
||||||
|
TimeStamp %= CurrentClip.Duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i < Skeleton.Joints.Count)
|
||||||
|
{
|
||||||
|
ref JointPose localPose = ref Pose.LocalPose[i];
|
||||||
|
|
||||||
|
localPose = CurrentClip.JointAnimations[i].GetCurrentPose(TimeStamp);
|
||||||
|
|
||||||
|
Matrix jointToParent =
|
||||||
|
Matrix.Translation(localPose.Translation) *
|
||||||
|
Matrix.RotationQuaternion(localPose.Rotation) *
|
||||||
|
Matrix.Scaling(localPose.Scale);
|
||||||
|
|
||||||
|
uint8 parentIndex = Skeleton.Joints[i].ParentID;
|
||||||
|
|
||||||
|
ref Matrix globalPose = ref Pose.GlobalPose[i];
|
||||||
|
|
||||||
|
if(parentIndex == uint8.MaxValue)
|
||||||
|
{
|
||||||
|
globalPose = jointToParent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
globalPose = Pose.GlobalPose[parentIndex] * jointToParent;
|
||||||
|
}
|
||||||
|
|
||||||
|
SkinningMatricies[i] = globalPose * Skeleton.Joints[i].InverseBindPose;
|
||||||
|
InvTransSkinningMatricies[i] = ((Matrix3x3)SkinningMatricies[i]).Inverse().Transpose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AnimationClip
|
||||||
|
{
|
||||||
|
public Skeleton Skeleton;
|
||||||
|
public float FramesPerSecond;
|
||||||
|
public JointAnimation[] JointAnimations;
|
||||||
|
public bool IsLooping;
|
||||||
|
public float Duration;
|
||||||
|
|
||||||
|
// TODO: check
|
||||||
|
|
||||||
|
public ~this()
|
||||||
|
{
|
||||||
|
for(var jointAnimation in JointAnimations)
|
||||||
|
{
|
||||||
|
jointAnimation.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
delete JointAnimations;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum InterpolationMode
|
||||||
|
{
|
||||||
|
Step,
|
||||||
|
Linear,
|
||||||
|
CubicSpline
|
||||||
|
}
|
||||||
|
|
||||||
|
class JointAnimationChannel<T> where T : struct // : IDisposable
|
||||||
|
{
|
||||||
|
public float[] TimeStamps;
|
||||||
|
public T[] Values;
|
||||||
|
public InterpolationMode InterpolationMode = .Step;
|
||||||
|
|
||||||
|
public float Duration => TimeStamps[TimeStamps.Count - 1];
|
||||||
|
|
||||||
|
public this(int samples, InterpolationMode interpolationMode)
|
||||||
|
{
|
||||||
|
TimeStamps = new float[samples];
|
||||||
|
Values = new T[samples];
|
||||||
|
InterpolationMode = interpolationMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ~this()
|
||||||
|
{
|
||||||
|
delete TimeStamps;
|
||||||
|
delete Values;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public (T, T, float) GetSample(float currentTime)
|
||||||
|
{
|
||||||
|
let (previousIndex, previousTime) = FindPreviousTimeStamp(currentTime);
|
||||||
|
|
||||||
|
T previousSample = Values[previousIndex];
|
||||||
|
|
||||||
|
if(InterpolationMode == .Step)
|
||||||
|
{
|
||||||
|
return (previousSample, default(T), 0f);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int nextIndex = previousIndex + 1;
|
||||||
|
if(nextIndex >= Values.Count)
|
||||||
|
nextIndex = Values.Count - 1;
|
||||||
|
|
||||||
|
float nextTime = TimeStamps[nextIndex];
|
||||||
|
|
||||||
|
T nextSample = Values[nextIndex];
|
||||||
|
|
||||||
|
float interpolationValue = (currentTime - previousTime) / (nextTime - previousTime);
|
||||||
|
|
||||||
|
return (previousSample, nextSample, interpolationValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public (int Index, float timeStamp) FindPreviousTimeStamp(float currentTime)
|
||||||
|
{
|
||||||
|
// TODO use binary search to speed things up?
|
||||||
|
|
||||||
|
int index = TimeStamps.Count - 1;
|
||||||
|
|
||||||
|
for(int i < TimeStamps.Count)
|
||||||
|
{
|
||||||
|
if(TimeStamps[i] > currentTime)
|
||||||
|
{
|
||||||
|
index = i - 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(index == -1)
|
||||||
|
index = 0;
|
||||||
|
|
||||||
|
// Don't return -1 but return first frame
|
||||||
|
return (index, TimeStamps[index]);
|
||||||
|
|
||||||
|
//return array.Count - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct JointAnimation : IDisposable
|
||||||
|
{
|
||||||
|
public JointAnimationChannel<Vector3> TranslationChannel;
|
||||||
|
public JointAnimationChannel<Quaternion> RotationChannel;
|
||||||
|
public JointAnimationChannel<Vector3> ScaleChannel;
|
||||||
|
|
||||||
|
public float Duration
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Math.Max(Math.Max(
|
||||||
|
TranslationChannel?.Duration ?? 0, RotationChannel?.Duration ?? 0), ScaleChannel?.Duration ?? 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
delete TranslationChannel;
|
||||||
|
delete RotationChannel;
|
||||||
|
delete ScaleChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JointPose GetCurrentPose(float timeStamp)
|
||||||
|
{
|
||||||
|
JointPose result;
|
||||||
|
|
||||||
|
if(TranslationChannel != null)
|
||||||
|
{
|
||||||
|
let (previous, next, interpolationValue) = TranslationChannel.GetSample(timeStamp);
|
||||||
|
|
||||||
|
switch(TranslationChannel.InterpolationMode)
|
||||||
|
{
|
||||||
|
case .Step:
|
||||||
|
result.Translation = previous;
|
||||||
|
case .Linear:
|
||||||
|
result.Translation = Vector3.Lerp(previous, next, interpolationValue);
|
||||||
|
default:
|
||||||
|
result.Rotation = ?;
|
||||||
|
Runtime.NotImplemented();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.Translation = .Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(RotationChannel != null)
|
||||||
|
{
|
||||||
|
let (previous, next, interpolationValue) = RotationChannel.GetSample(timeStamp);
|
||||||
|
|
||||||
|
switch(RotationChannel.InterpolationMode)
|
||||||
|
{
|
||||||
|
case .Step:
|
||||||
|
result.Rotation = previous;
|
||||||
|
case .Linear:
|
||||||
|
result.Rotation = Quaternion.Slerp(previous, next, interpolationValue);
|
||||||
|
default:
|
||||||
|
result.Rotation = ?;
|
||||||
|
Runtime.NotImplemented();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.Rotation = .Identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ScaleChannel != null)
|
||||||
|
{
|
||||||
|
let (previous, next, interpolationValue) = ScaleChannel.GetSample(timeStamp);
|
||||||
|
|
||||||
|
//switch(ScaleChannel.InterpolationMode)
|
||||||
|
switch(InterpolationMode.Step)
|
||||||
|
{
|
||||||
|
case .Step:
|
||||||
|
result.Scale = previous;
|
||||||
|
case .Linear:
|
||||||
|
result.Scale = Vector3.Lerp(previous, next, interpolationValue);
|
||||||
|
default:
|
||||||
|
result.Rotation = ?;
|
||||||
|
Runtime.NotImplemented();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.Scale = .One;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using GlitchyEngine.Math;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace GlitchyEngine.Renderer.Animation
|
||||||
|
{
|
||||||
|
public struct Joint
|
||||||
|
{
|
||||||
|
/// Converts vertices from model space to joint space
|
||||||
|
public Matrix InverseBindPose;
|
||||||
|
public String Name;
|
||||||
|
public uint8 ParentID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct JointPose
|
||||||
|
{
|
||||||
|
public Quaternion Rotation;
|
||||||
|
public Vector3 Translation;
|
||||||
|
public Vector3 Scale;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
using GlitchyEngine.Math;
|
||||||
|
|
||||||
|
namespace GlitchyEngine.Renderer.Animation
|
||||||
|
{
|
||||||
|
public class Skeleton
|
||||||
|
{
|
||||||
|
public Joint[] Joints ~ delete _;
|
||||||
|
|
||||||
|
public Joint? GetParent(Joint joint)
|
||||||
|
{
|
||||||
|
if(joint.ParentID == uint8.MaxValue)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return Joints[joint.ParentID];
|
||||||
|
}
|
||||||
|
|
||||||
|
public ~this()
|
||||||
|
{
|
||||||
|
for(var joint in Joints)
|
||||||
|
{
|
||||||
|
delete joint.Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SkeletonPose
|
||||||
|
{
|
||||||
|
public Skeleton Skeleton;
|
||||||
|
public JointPose[] LocalPose ~ delete _;
|
||||||
|
public Matrix[] GlobalPose ~ delete _;
|
||||||
|
|
||||||
|
public this(Skeleton skeleton)
|
||||||
|
{
|
||||||
|
Skeleton = skeleton;
|
||||||
|
|
||||||
|
int jointCount = Skeleton.Joints.Count;
|
||||||
|
|
||||||
|
LocalPose = new JointPose[jointCount];
|
||||||
|
GlobalPose = new Matrix[jointCount];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -139,6 +139,15 @@ namespace GlitchyEngine.Renderer
|
|||||||
*(Matrix4x3*)firstByte = value;
|
*(Matrix4x3*)firstByte = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetData(Matrix4x3[] value)
|
||||||
|
{
|
||||||
|
EnsureTypeMatch(4, 3, .Float);
|
||||||
|
|
||||||
|
// TODO: assert length
|
||||||
|
|
||||||
|
Internal.MemCpy(firstByte, value.Ptr, sizeof(Matrix4x3) * Math.Min(value.Count, _elements));
|
||||||
|
}
|
||||||
|
|
||||||
public void SetData(Matrix3x3 value)
|
public void SetData(Matrix3x3 value)
|
||||||
{
|
{
|
||||||
EnsureTypeMatch(3, 3, .Float);
|
EnsureTypeMatch(3, 3, .Float);
|
||||||
@@ -148,6 +157,18 @@ namespace GlitchyEngine.Renderer
|
|||||||
// Todo: maybe manual copy
|
// Todo: maybe manual copy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetData(Matrix3x3[] value)
|
||||||
|
{
|
||||||
|
EnsureTypeMatch(3, 3, .Float);
|
||||||
|
|
||||||
|
// TODO: assert length
|
||||||
|
|
||||||
|
for(int i < Math.Min(value.Count, _elements))
|
||||||
|
{
|
||||||
|
((Matrix4x3*)firstByte)[i] = Matrix4x3(value[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void SetData(Matrix value)
|
public void SetData(Matrix value)
|
||||||
{
|
{
|
||||||
EnsureTypeMatch(4, 4, .Float);
|
EnsureTypeMatch(4, 4, .Float);
|
||||||
|
|||||||
Vendored
+1
-1
Submodule GlitchyEngine/vendor/gltf updated: 0894de3b25...c37f778c9d
@@ -17,10 +17,18 @@ cbuffer Constants
|
|||||||
float3 LightDir;
|
float3 LightDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cbuffer SkinningMatrices
|
||||||
|
{
|
||||||
|
matrix SkinningMatrices[255];
|
||||||
|
float3x3 InvTransSkinningMatrices[255];
|
||||||
|
}
|
||||||
|
|
||||||
struct VS_IN
|
struct VS_IN
|
||||||
{
|
{
|
||||||
float3 Position : POSITION;
|
float3 Position : POSITION;
|
||||||
float3 Normal : NORMAL;
|
float3 Normal : NORMAL;
|
||||||
|
uint4 JointIndices : JOINTS_0;
|
||||||
|
float4 JointWeights : WEIGHTS_0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PS_IN
|
struct PS_IN
|
||||||
@@ -33,10 +41,31 @@ PS_IN VS(VS_IN input)
|
|||||||
{
|
{
|
||||||
PS_IN output;
|
PS_IN output;
|
||||||
|
|
||||||
float4 worldPosition = mul(Transform, float4(input.Position, 1));
|
// Unanimated position in modelspace
|
||||||
|
float4 positionRaw = float4(input.Position, 1);
|
||||||
|
|
||||||
|
// Calculate animated position in modelspace
|
||||||
|
|
||||||
|
float4 position =
|
||||||
|
mul(SkinningMatrices[input.JointIndices.x], positionRaw) * input.JointWeights.x +
|
||||||
|
mul(SkinningMatrices[input.JointIndices.y], positionRaw) * input.JointWeights.y +
|
||||||
|
mul(SkinningMatrices[input.JointIndices.z], positionRaw) * input.JointWeights.z +
|
||||||
|
mul(SkinningMatrices[input.JointIndices.w], positionRaw) * input.JointWeights.w;
|
||||||
|
|
||||||
|
float3 normal =
|
||||||
|
mul(InvTransSkinningMatrices[input.JointIndices.x], input.Normal) * input.JointWeights.x +
|
||||||
|
mul(InvTransSkinningMatrices[input.JointIndices.y], input.Normal) * input.JointWeights.y +
|
||||||
|
mul(InvTransSkinningMatrices[input.JointIndices.z], input.Normal) * input.JointWeights.z +
|
||||||
|
mul(InvTransSkinningMatrices[input.JointIndices.w], input.Normal) * input.JointWeights.w;
|
||||||
|
|
||||||
|
//position = saturate(position - 1000) + positionRaw;
|
||||||
|
|
||||||
|
//float4 position = mul(SkinningMatrices[input.JointIndices.x], positionRaw);
|
||||||
|
|
||||||
|
float4 worldPosition = mul(Transform, position);
|
||||||
|
|
||||||
output.Position = mul(ViewProjection, worldPosition);
|
output.Position = mul(ViewProjection, worldPosition);
|
||||||
output.Normal = mul((float3x3)Transform, input.Normal);
|
output.Normal = mul((float3x3)Transform, normal);
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
@@ -46,6 +75,7 @@ float4 PS(PS_IN input) : SV_TARGET
|
|||||||
input.Normal = normalize(input.Normal);
|
input.Normal = normalize(input.Normal);
|
||||||
|
|
||||||
float shading = dot(LightDir, input.Normal);
|
float shading = dot(LightDir, input.Normal);
|
||||||
|
//float shading = dot(LightDir, LightDir) + 1;
|
||||||
shading = clamp(shading, 0.0f, 1.0f);
|
shading = clamp(shading, 0.0f, 1.0f);
|
||||||
|
|
||||||
return BaseColor * shading;
|
return BaseColor * shading;
|
||||||
|
|||||||
+133
-17
@@ -10,6 +10,7 @@ using GlitchyEngine.Math;
|
|||||||
using GlitchyEngine.World;
|
using GlitchyEngine.World;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using GlitchyEngine.Content;
|
using GlitchyEngine.Content;
|
||||||
|
using GlitchyEngine.Renderer.Animation;
|
||||||
|
|
||||||
namespace Sandbox
|
namespace Sandbox
|
||||||
{
|
{
|
||||||
@@ -70,8 +71,6 @@ namespace Sandbox
|
|||||||
BlendState _alphaBlendState ~ _?.ReleaseRef();
|
BlendState _alphaBlendState ~ _?.ReleaseRef();
|
||||||
BlendState _opaqueBlendState ~ _?.ReleaseRef();
|
BlendState _opaqueBlendState ~ _?.ReleaseRef();
|
||||||
|
|
||||||
EffectLibrary _effectLibrary ~ delete _;
|
|
||||||
|
|
||||||
EcsWorld _world = new EcsWorld() ~ delete _;
|
EcsWorld _world = new EcsWorld() ~ delete _;
|
||||||
|
|
||||||
// OrthographicCameraController _cameraController ~ delete _;
|
// OrthographicCameraController _cameraController ~ delete _;
|
||||||
@@ -87,13 +86,13 @@ namespace Sandbox
|
|||||||
{
|
{
|
||||||
_context = Application.Get().Window.Context..AddRef();
|
_context = Application.Get().Window.Context..AddRef();
|
||||||
|
|
||||||
_effectLibrary = new EffectLibrary(_context);
|
var effectLibrary = Application.Get().EffectLibrary;
|
||||||
|
|
||||||
_effectLibrary.LoadNoRefInc("content\\Shaders\\basicShader.hlsl");
|
effectLibrary.LoadNoRefInc("content\\Shaders\\basicShader.hlsl");
|
||||||
|
|
||||||
_effectLibrary.LoadNoRefInc("content\\Shaders\\testShader.hlsl");
|
effectLibrary.LoadNoRefInc("content\\Shaders\\testShader.hlsl");
|
||||||
|
|
||||||
var textureEffect = _effectLibrary.Load("content\\Shaders\\textureShader.hlsl");
|
var textureEffect = effectLibrary.Load("content\\Shaders\\textureShader.hlsl");
|
||||||
|
|
||||||
_depthTarget = new DepthStencilTarget(_context, _context.SwapChain.Width, _context.SwapChain.Height);
|
_depthTarget = new DepthStencilTarget(_context, _context.SwapChain.Width, _context.SwapChain.Height);
|
||||||
|
|
||||||
@@ -209,6 +208,7 @@ namespace Sandbox
|
|||||||
_cameraController = new .(Application.Get().Window.Context.SwapChain.BackbufferViewport.Width /
|
_cameraController = new .(Application.Get().Window.Context.SwapChain.BackbufferViewport.Width /
|
||||||
Application.Get().Window.Context.SwapChain.BackbufferViewport.Height);
|
Application.Get().Window.Context.SwapChain.BackbufferViewport.Height);
|
||||||
_cameraController.CameraPosition = .(0, 0, -5);
|
_cameraController.CameraPosition = .(0, 0, -5);
|
||||||
|
_cameraController.TranslationSpeed = 10;
|
||||||
|
|
||||||
TestLoadModel();
|
TestLoadModel();
|
||||||
}
|
}
|
||||||
@@ -217,6 +217,10 @@ namespace Sandbox
|
|||||||
|
|
||||||
List<(Matrix Transform, GeometryBinding Model)> _modelTest = new .() ~ UnloadModelTest!();
|
List<(Matrix Transform, GeometryBinding Model)> _modelTest = new .() ~ UnloadModelTest!();
|
||||||
|
|
||||||
|
Skeleton Skeleton ~ delete _;
|
||||||
|
AnimationClip Clip ~ delete _;
|
||||||
|
AnimationPlayer AnimationPlayer ~ delete _;
|
||||||
|
|
||||||
mixin UnloadModelTest()
|
mixin UnloadModelTest()
|
||||||
{
|
{
|
||||||
for(var entry in _modelTest)
|
for(var entry in _modelTest)
|
||||||
@@ -229,12 +233,20 @@ namespace Sandbox
|
|||||||
|
|
||||||
void TestLoadModel()
|
void TestLoadModel()
|
||||||
{
|
{
|
||||||
var testEffect = _effectLibrary.Get("testShader");
|
var testEffect = Application.Get().EffectLibrary.Get("testShader");
|
||||||
|
|
||||||
ModelLoader.LoadModel("content\\Models\\box.gltf", _context, testEffect, _modelTest);
|
//ModelLoader.LoadModel("content\\Models\\Test\\axisTest.glb", _context, testEffect, _modelTest, out Skeleton, out Clip);
|
||||||
|
//ModelLoader.LoadModel("content\\Models\\RiggedSimple\\RiggedSimple.glb", _context, testEffect, _modelTest, out Skeleton, out Clip);
|
||||||
|
//ModelLoader.LoadModel("content\\Models\\Fox\\Fox_2.glb", _context, testEffect, _modelTest, out Skeleton, out Clip);
|
||||||
|
//ModelLoader.LoadModel("content\\Models\\DancingCylinder\\DancingCylinder.glb", _context, testEffect, _modelTest, out Skeleton, out Clip);
|
||||||
|
//ModelLoader.LoadModel("content\\Models\\Figure\\Figure.gltf", _context, testEffect, _modelTest, out Skeleton, out Clip);
|
||||||
|
ModelLoader.LoadModel("content\\Models\\RiggedFigure\\RiggedFigure.glb", _context, testEffect, _modelTest, out Skeleton, out Clip);
|
||||||
|
|
||||||
testEffect.ReleaseRef();
|
testEffect.ReleaseRef();
|
||||||
|
|
||||||
|
if(Skeleton != null && Clip != null)
|
||||||
|
AnimationPlayer = new AnimationPlayer(Skeleton, Clip);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
CGLTF.Options options = .();
|
CGLTF.Options options = .();
|
||||||
CGLTF.Data* data;
|
CGLTF.Data* data;
|
||||||
@@ -277,8 +289,14 @@ namespace Sandbox
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float f = 0.0f;
|
||||||
|
|
||||||
|
bool playAnimation = false;
|
||||||
|
bool drawModel = false;
|
||||||
|
|
||||||
public override void Update(GameTime gameTime)
|
public override void Update(GameTime gameTime)
|
||||||
{
|
{
|
||||||
|
f += (float)gameTime.FrameTime.TotalSeconds;
|
||||||
|
|
||||||
_cameraController.Update(gameTime);
|
_cameraController.Update(gameTime);
|
||||||
|
|
||||||
@@ -296,21 +314,101 @@ namespace Sandbox
|
|||||||
|
|
||||||
_opaqueBlendState.Bind();
|
_opaqueBlendState.Bind();
|
||||||
|
|
||||||
var basicEffect = _effectLibrary.Get("basicShader");
|
var basicEffect = Application.Get().EffectLibrary.Get("basicShader");
|
||||||
|
|
||||||
// Model test
|
// Model test
|
||||||
{
|
{
|
||||||
_context.SetRasterizerState(_rasterizerStateClockWise);
|
//AnimationPlayer.CurrentClip.Samples[0].JointPose[1].Rotation = .(1, 0, 0, 1)..Normalize();
|
||||||
|
|
||||||
var testEffect = _effectLibrary.Get("testShader");
|
Matrix scaling = .Scaling(1f, 1.0f, -1.0f);
|
||||||
testEffect.Variables["BaseColor"].SetData(Color.White);
|
|
||||||
testEffect.Variables["LightDir"].SetData(Vector3(-1, 1, -0.5f).Normalized());
|
|
||||||
|
|
||||||
for(var entry in _modelTest)
|
var testEffect = Application.Get().EffectLibrary.Get("testShader");
|
||||||
|
|
||||||
|
if(AnimationPlayer != null)
|
||||||
{
|
{
|
||||||
Renderer.Submit(entry.Model, testEffect, entry.Transform);
|
if(playAnimation)
|
||||||
|
AnimationPlayer.Update(gameTime);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GameTime gt = new GameTime();
|
||||||
|
AnimationPlayer.Update(gt);
|
||||||
|
delete gt;
|
||||||
|
}
|
||||||
|
|
||||||
|
//_context.SetRasterizerState(_rasterizerStateClockWise);
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for(var globalPose in AnimationPlayer.Pose.GlobalPose)
|
||||||
|
{
|
||||||
|
Joint currentJoint = AnimationPlayer.Skeleton.Joints[i];
|
||||||
|
|
||||||
|
Matrix bindPose = currentJoint.InverseBindPose.Invert();
|
||||||
|
|
||||||
|
// Draw skeleton
|
||||||
|
Matrix mat = AnimationPlayer.SkinningMatricies[i] * bindPose;
|
||||||
|
|
||||||
|
uint8 parentId = currentJoint.ParentID;
|
||||||
|
|
||||||
|
if(parentId != uint8.MaxValue)
|
||||||
|
{
|
||||||
|
Vector3 start = mat.Translation;
|
||||||
|
|
||||||
|
Joint parent = AnimationPlayer.Skeleton.Joints[parentId];
|
||||||
|
|
||||||
|
Matrix parentBindPose = parent.InverseBindPose.Invert();
|
||||||
|
|
||||||
|
Matrix parentMatrix = AnimationPlayer.SkinningMatricies[parentId] * parentBindPose;
|
||||||
|
|
||||||
|
Vector3 end = parentMatrix.Translation;
|
||||||
|
|
||||||
|
Renderer.DrawLine(start, end, .Black, scaling);
|
||||||
|
}
|
||||||
|
|
||||||
|
Renderer.DrawLine(.Zero, Vector3(0.1f, 0, 0), .Red, scaling * mat);
|
||||||
|
Renderer.DrawLine(.Zero, Vector3(0, 0.1f, 0), .Lime, scaling * mat);
|
||||||
|
Renderer.DrawLine(.Zero, Vector3(0, 0, 0.1f), .Blue, scaling * mat);
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
testEffect.Variables["SkinningMatrices"].SetData(AnimationPlayer.SkinningMatricies);
|
||||||
|
testEffect.Variables["InvTransSkinningMatrices"].SetData(AnimationPlayer.InvTransSkinningMatricies);
|
||||||
|
|
||||||
|
testEffect.Variables["BaseColor"].SetData(Color.White);
|
||||||
|
testEffect.Variables["LightDir"].SetData(Vector3(1, 1, -0.5f).Normalized());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(drawModel)
|
||||||
|
{
|
||||||
|
for(var entry in _modelTest)
|
||||||
|
{
|
||||||
|
Renderer.Submit(entry.Model, testEffect, .Identity * scaling);//entry.Transform
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Draw bind pose
|
||||||
|
for(Joint joint in Skeleton.Joints)
|
||||||
|
{
|
||||||
|
Matrix bindPose = joint.InverseBindPose.Invert();
|
||||||
|
|
||||||
|
if(joint.ParentID != uint8.MaxValue)
|
||||||
|
{
|
||||||
|
Vector3 start = bindPose.Translation;
|
||||||
|
|
||||||
|
Matrix parentBindPose = Skeleton.Joints[joint.ParentID].InverseBindPose.Invert();
|
||||||
|
|
||||||
|
Vector3 end = parentBindPose.Translation;
|
||||||
|
|
||||||
|
Renderer.DrawLine(start, end, .White);
|
||||||
|
}
|
||||||
|
|
||||||
|
Renderer.DrawLine(.Zero, Vector3(0.1f, 0, 0), .Red, bindPose);
|
||||||
|
Renderer.DrawLine(.Zero, Vector3(0, 0.1f, 0), .Lime, bindPose);
|
||||||
|
Renderer.DrawLine(.Zero, Vector3(0, 0, 0.1f), .Blue, bindPose);
|
||||||
|
}
|
||||||
|
*/
|
||||||
testEffect.ReleaseRef();
|
testEffect.ReleaseRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,13 +440,13 @@ namespace Sandbox
|
|||||||
|
|
||||||
_checkerMaterial.SetVariable("BaseColor", Color.White);
|
_checkerMaterial.SetVariable("BaseColor", Color.White);
|
||||||
|
|
||||||
Renderer.Submit(_quadGeometryBinding, _checkerMaterial, .Scaling(1.5f));
|
Renderer.Submit(_quadGeometryBinding, _checkerMaterial, Matrix.RotationZ(f) * .Scaling(1.5f));
|
||||||
|
|
||||||
_alphaBlendState.Bind();
|
_alphaBlendState.Bind();
|
||||||
|
|
||||||
_logoMaterial.SetVariable("BaseColor", Color.Pink);
|
_logoMaterial.SetVariable("BaseColor", Color.Pink);
|
||||||
|
|
||||||
Renderer.Submit(_quadGeometryBinding, _logoMaterial, .Scaling(2f));
|
Renderer.Submit(_quadGeometryBinding, _logoMaterial, .Translation(0, 0, -1) * .Scaling(2f));
|
||||||
|
|
||||||
Renderer.EndScene();
|
Renderer.EndScene();
|
||||||
|
|
||||||
@@ -370,6 +468,24 @@ namespace Sandbox
|
|||||||
|
|
||||||
private bool OnImGuiRender(ImGuiRenderEvent e)
|
private bool OnImGuiRender(ImGuiRenderEvent e)
|
||||||
{
|
{
|
||||||
|
ImGui.Begin("Animation");
|
||||||
|
|
||||||
|
if(AnimationPlayer != null)
|
||||||
|
{
|
||||||
|
ImGui.DragFloat("Timestamp", &AnimationPlayer.TimeStamp, 0.01f, -Clip.Duration, Clip.Duration);
|
||||||
|
|
||||||
|
while(AnimationPlayer.TimeStamp < 0)
|
||||||
|
{
|
||||||
|
AnimationPlayer.TimeStamp += Clip.Duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Checkbox("Play", &playAnimation);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Checkbox("Draw Model", &drawModel);
|
||||||
|
|
||||||
|
ImGui.End();
|
||||||
|
|
||||||
ImGui.Begin("Test");
|
ImGui.Begin("Test");
|
||||||
|
|
||||||
ImGui.ColorEdit3("Square Color", ref _squareColor0);
|
ImGui.ColorEdit3("Square Color", ref _squareColor0);
|
||||||
|
|||||||
Reference in New Issue
Block a user