diff --git a/GlitchyEngine/src/Content/ModelLoader.bf b/GlitchyEngine/src/Content/ModelLoader.bf index ed0da71..36f2a68 100644 --- a/GlitchyEngine/src/Content/ModelLoader.bf +++ b/GlitchyEngine/src/Content/ModelLoader.bf @@ -3,13 +3,19 @@ using System.Collections; using cgltf; using GlitchyEngine.Math; using GlitchyEngine.Renderer; +using GlitchyEngine.Renderer.Animation; namespace GlitchyEngine.Content { 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.Data* data; CGLTF.Result result = CGLTF.ParseFile(options, filename, out data); @@ -25,7 +31,9 @@ namespace GlitchyEngine.Content if(node.Mesh != null) { Matrix transform = ?; - CGLTF.NodeTransformWorld(&node, (float*)&transform); + CGLTF.NodeTransformLocal(&node, (float*)&transform); + + transform = RightToLeftHand * transform; for(var primitive in node.Mesh.Primitives) { @@ -34,6 +42,11 @@ namespace GlitchyEngine.Content output.Add((transform, binding)); } } + + if(node.Skin != null) + { + (skeleton, clip) = ExtractBonestuff(node.Skin, data); + } } /* TODO make awesome stuff */ @@ -96,6 +109,8 @@ namespace GlitchyEngine.Content VertexBuffer vertexBuffer = null; + binding.VertexCount = (uint32)attribute.Data.Count; + // Get vertex buffer { CGLTF.BufferView* bufferView = attribute.Data.BufferView; @@ -130,7 +145,14 @@ namespace GlitchyEngine.Content 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); } @@ -146,9 +168,178 @@ namespace GlitchyEngine.Content 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; } + 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(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(channel.Sampler.Input, i); + Vector3 sample = GetEntry(channel.Sampler.Output, i); + /* + float timeStamp = GetEntry(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(channel.Sampler.Input, i); + Quaternion sample = GetEntry(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(channel.Sampler.Input, i); + Vector3 sample = GetEntry(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(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. */ diff --git a/GlitchyEngine/src/Renderer/Animation/AnimationClip.bf b/GlitchyEngine/src/Renderer/Animation/AnimationClip.bf new file mode 100644 index 0000000..c8cda15 --- /dev/null +++ b/GlitchyEngine/src/Renderer/Animation/AnimationClip.bf @@ -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 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 TranslationChannel; + public JointAnimationChannel RotationChannel; + public JointAnimationChannel 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; + } + } +} diff --git a/GlitchyEngine/src/Renderer/Animation/Joint.bf b/GlitchyEngine/src/Renderer/Animation/Joint.bf new file mode 100644 index 0000000..7cbd85d --- /dev/null +++ b/GlitchyEngine/src/Renderer/Animation/Joint.bf @@ -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; + } +} diff --git a/GlitchyEngine/src/Renderer/Animation/Skeleton.bf b/GlitchyEngine/src/Renderer/Animation/Skeleton.bf new file mode 100644 index 0000000..d24a271 --- /dev/null +++ b/GlitchyEngine/src/Renderer/Animation/Skeleton.bf @@ -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]; + } + } +} diff --git a/GlitchyEngine/src/Renderer/BufferVariable.bf b/GlitchyEngine/src/Renderer/BufferVariable.bf index 5734099..6a822b4 100644 --- a/GlitchyEngine/src/Renderer/BufferVariable.bf +++ b/GlitchyEngine/src/Renderer/BufferVariable.bf @@ -139,6 +139,15 @@ namespace GlitchyEngine.Renderer *(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) { EnsureTypeMatch(3, 3, .Float); @@ -147,6 +156,18 @@ namespace GlitchyEngine.Renderer // 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) { diff --git a/GlitchyEngine/vendor/gltf b/GlitchyEngine/vendor/gltf index 0894de3..c37f778 160000 --- a/GlitchyEngine/vendor/gltf +++ b/GlitchyEngine/vendor/gltf @@ -1 +1 @@ -Subproject commit 0894de3b25ee1551cf1eb999e933ec4b3ead24cb +Subproject commit c37f778c9d99aa7e36b6b798985d6036eb1a09e3 diff --git a/Sandbox/content/Shaders/testShader.hlsl b/Sandbox/content/Shaders/testShader.hlsl index 29ccfb3..cf953ea 100644 --- a/Sandbox/content/Shaders/testShader.hlsl +++ b/Sandbox/content/Shaders/testShader.hlsl @@ -17,10 +17,18 @@ cbuffer Constants float3 LightDir; } +cbuffer SkinningMatrices +{ + matrix SkinningMatrices[255]; + float3x3 InvTransSkinningMatrices[255]; +} + struct VS_IN { - float3 Position : POSITION; - float3 Normal : NORMAL; + float3 Position : POSITION; + float3 Normal : NORMAL; + uint4 JointIndices : JOINTS_0; + float4 JointWeights : WEIGHTS_0; }; struct PS_IN @@ -33,10 +41,31 @@ PS_IN VS(VS_IN input) { 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.Normal = mul((float3x3)Transform, input.Normal); + output.Normal = mul((float3x3)Transform, normal); return output; } @@ -46,6 +75,7 @@ float4 PS(PS_IN input) : SV_TARGET input.Normal = normalize(input.Normal); float shading = dot(LightDir, input.Normal); + //float shading = dot(LightDir, LightDir) + 1; shading = clamp(shading, 0.0f, 1.0f); return BaseColor * shading; diff --git a/Sandbox/src/SandboxApp.bf b/Sandbox/src/SandboxApp.bf index b6a7965..cddbf32 100644 --- a/Sandbox/src/SandboxApp.bf +++ b/Sandbox/src/SandboxApp.bf @@ -10,6 +10,7 @@ using GlitchyEngine.Math; using GlitchyEngine.World; using System.Collections; using GlitchyEngine.Content; +using GlitchyEngine.Renderer.Animation; namespace Sandbox { @@ -70,8 +71,6 @@ namespace Sandbox BlendState _alphaBlendState ~ _?.ReleaseRef(); BlendState _opaqueBlendState ~ _?.ReleaseRef(); - EffectLibrary _effectLibrary ~ delete _; - EcsWorld _world = new EcsWorld() ~ delete _; // OrthographicCameraController _cameraController ~ delete _; @@ -87,13 +86,13 @@ namespace Sandbox { _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); @@ -209,6 +208,7 @@ namespace Sandbox _cameraController = new .(Application.Get().Window.Context.SwapChain.BackbufferViewport.Width / Application.Get().Window.Context.SwapChain.BackbufferViewport.Height); _cameraController.CameraPosition = .(0, 0, -5); + _cameraController.TranslationSpeed = 10; TestLoadModel(); } @@ -217,6 +217,10 @@ namespace Sandbox List<(Matrix Transform, GeometryBinding Model)> _modelTest = new .() ~ UnloadModelTest!(); + Skeleton Skeleton ~ delete _; + AnimationClip Clip ~ delete _; + AnimationPlayer AnimationPlayer ~ delete _; + mixin UnloadModelTest() { for(var entry in _modelTest) @@ -229,12 +233,20 @@ namespace Sandbox void TestLoadModel() { - var testEffect = _effectLibrary.Get("testShader"); + var testEffect = Application.Get().EffectLibrary.Get("testShader"); + + //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); - ModelLoader.LoadModel("content\\Models\\box.gltf", _context, testEffect, _modelTest); - testEffect.ReleaseRef(); + if(Skeleton != null && Clip != null) + AnimationPlayer = new AnimationPlayer(Skeleton, Clip); + /* CGLTF.Options options = .(); CGLTF.Data* data; @@ -277,9 +289,15 @@ namespace Sandbox } } + float f = 0.0f; + + bool playAnimation = false; + bool drawModel = false; + public override void Update(GameTime gameTime) { - + f += (float)gameTime.FrameTime.TotalSeconds; + _cameraController.Update(gameTime); RenderCommand.Clear(null, .(0.2f, 0.2f, 0.2f)); @@ -296,24 +314,104 @@ namespace Sandbox _opaqueBlendState.Bind(); - var basicEffect = _effectLibrary.Get("basicShader"); - + var basicEffect = Application.Get().EffectLibrary.Get("basicShader"); + // Model test { - _context.SetRasterizerState(_rasterizerStateClockWise); + //AnimationPlayer.CurrentClip.Samples[0].JointPose[1].Rotation = .(1, 0, 0, 1)..Normalize(); - var testEffect = _effectLibrary.Get("testShader"); - testEffect.Variables["BaseColor"].SetData(Color.White); - testEffect.Variables["LightDir"].SetData(Vector3(-1, 1, -0.5f).Normalized()); + Matrix scaling = .Scaling(1f, 1.0f, -1.0f); - 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(); } - + _context.SetRasterizerState(_rasterizerState); for(var entity in _world.Enumerate(typeof(TransformComponent))) @@ -342,13 +440,13 @@ namespace Sandbox _checkerMaterial.SetVariable("BaseColor", Color.White); - Renderer.Submit(_quadGeometryBinding, _checkerMaterial, .Scaling(1.5f)); + Renderer.Submit(_quadGeometryBinding, _checkerMaterial, Matrix.RotationZ(f) * .Scaling(1.5f)); _alphaBlendState.Bind(); _logoMaterial.SetVariable("BaseColor", Color.Pink); - Renderer.Submit(_quadGeometryBinding, _logoMaterial, .Scaling(2f)); + Renderer.Submit(_quadGeometryBinding, _logoMaterial, .Translation(0, 0, -1) * .Scaling(2f)); Renderer.EndScene(); @@ -370,6 +468,24 @@ namespace Sandbox 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.ColorEdit3("Square Color", ref _squareColor0);