mirror of
https://github.com/4sval/FModel.git
synced 2026-04-14 05:26:42 -05:00
play anim at the right pace + fixed outliner
This commit is contained in:
parent
f36a7b79cd
commit
a636c1ff84
|
|
@ -32,10 +32,9 @@ void main()
|
|||
finalNormal += boneMatrix * bindNormal * weight;
|
||||
}
|
||||
|
||||
vec3 pos = vec3(vInstanceMatrix * finalPos);
|
||||
vec3 nor = vec3(transpose(inverse(vInstanceMatrix)) * finalNormal);
|
||||
float scaleFactor = distance(vec3(finalPos), uViewPos) * 0.0025;
|
||||
vec4 nor = transpose(inverse(vInstanceMatrix)) * finalNormal * scaleFactor;
|
||||
finalPos.xyz += nor.xyz;
|
||||
|
||||
float scaleFactor = distance(pos, uViewPos) * 0.0025;
|
||||
vec3 scaleVertex = pos + nor * scaleFactor;
|
||||
gl_Position = uProjection * uView * vec4(scaleVertex, 1.0);
|
||||
gl_Position = uProjection * uView * vInstanceMatrix * finalPos;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,44 +1,53 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using CUE4Parse_Conversion.Animations;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using CUE4Parse.Utils;
|
||||
|
||||
namespace FModel.Views.Snooper.Models.Animations;
|
||||
|
||||
public class Animation : IDisposable
|
||||
{
|
||||
public int CurrentTime;
|
||||
public readonly int MaxTime;
|
||||
public int Frame;
|
||||
public float ElapsedTime;
|
||||
public readonly int MaxFrame;
|
||||
public readonly float FramesPerSecond;
|
||||
public readonly Dictionary<int, int> TrackIndexByBoneIndex;
|
||||
public readonly Transform[][] BoneTransforms;
|
||||
|
||||
public float TimePerFrame => 1.0f / FramesPerSecond;
|
||||
|
||||
public Animation(Skeleton skeleton, CAnimSet anim)
|
||||
{
|
||||
CurrentTime = 0;
|
||||
Frame = 0;
|
||||
ElapsedTime = 0;
|
||||
TrackIndexByBoneIndex = new Dictionary<int, int>();
|
||||
|
||||
var sequence = anim.Sequences[0];
|
||||
MaxTime = sequence.NumFrames - 1;
|
||||
MaxFrame = sequence.NumFrames;
|
||||
FramesPerSecond = sequence.Rate;
|
||||
|
||||
BoneTransforms = new Transform[skeleton.UnrealSkeleton.ReferenceSkeleton.FinalRefBoneInfo.Length][];
|
||||
for (var trackIndex = 0; trackIndex < BoneTransforms.Length; trackIndex++)
|
||||
{
|
||||
BoneTransforms[trackIndex] = new Transform[MaxFrame];
|
||||
|
||||
var bone = skeleton.UnrealSkeleton.ReferenceSkeleton.FinalRefBoneInfo[trackIndex];
|
||||
if (!skeleton.BonesIndexByLoweredName.TryGetValue(bone.Name.Text.ToLower(), out var boneIndex))
|
||||
{
|
||||
BoneTransforms[trackIndex] = new Transform[sequence.NumFrames];
|
||||
continue;
|
||||
}
|
||||
if (!skeleton.BonesTransformByIndex.TryGetValue(boneIndex, out var originalTransform))
|
||||
throw new ArgumentNullException($"no transform for bone '{boneIndex}'");
|
||||
|
||||
TrackIndexByBoneIndex[boneIndex] = trackIndex;
|
||||
var boneOrientation = originalTransform.Rotation;
|
||||
var bonePosition = originalTransform.Position;
|
||||
var boneScale = originalTransform.Scale;
|
||||
|
||||
BoneTransforms[trackIndex] = new Transform[sequence.NumFrames];
|
||||
for (var frame = 0; frame < BoneTransforms[trackIndex].Length; frame++)
|
||||
{
|
||||
sequence.Tracks[trackIndex].GetBonePosition(frame, sequence.NumFrames, false, ref bonePosition, ref boneOrientation);
|
||||
if (CurrentTime < sequence.Tracks[trackIndex].KeyScale.Length)
|
||||
boneScale = sequence.Tracks[trackIndex].KeyScale[CurrentTime];
|
||||
sequence.Tracks[trackIndex].GetBonePosition(frame, MaxFrame, false, ref bonePosition, ref boneOrientation);
|
||||
if (frame < sequence.Tracks[trackIndex].KeyScale.Length)
|
||||
boneScale = sequence.Tracks[trackIndex].KeyScale[frame];
|
||||
|
||||
// revert FixRotationKeys
|
||||
if (trackIndex > 0) boneOrientation.Conjugate();
|
||||
|
|
@ -56,8 +65,14 @@ public class Animation : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
public Matrix4x4 InterpolateBoneTransform(int trackIndex)
|
||||
{
|
||||
Frame = ElapsedTime.FloorToInt() % MaxFrame; // interpolate here
|
||||
return BoneTransforms[trackIndex][Frame].Matrix;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
TrackIndexByBoneIndex.Clear();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ public class Skeleton : IDisposable
|
|||
InvertedBonesMatrixByIndex = new Dictionary<int, Matrix4x4>();
|
||||
}
|
||||
|
||||
public Skeleton(FPackageIndex package, FReferenceSkeleton referenceSkeleton, Transform transform) : this()
|
||||
public Skeleton(FPackageIndex package, FReferenceSkeleton referenceSkeleton) : this()
|
||||
{
|
||||
UnrealSkeleton = package.Load<USkeleton>();
|
||||
IsLoaded = UnrealSkeleton != null;
|
||||
|
|
@ -36,17 +36,6 @@ public class Skeleton : IDisposable
|
|||
foreach ((var name, var boneIndex) in referenceSkeleton.FinalNameToIndexMap)
|
||||
BonesIndexByLoweredName[name.ToLower()] = boneIndex;
|
||||
|
||||
UpdateBoneMatrices(transform.Matrix);
|
||||
}
|
||||
|
||||
public void SetAnimation(CAnimSet anim)
|
||||
{
|
||||
Anim = new Animation(this, anim);
|
||||
}
|
||||
|
||||
public void UpdateBoneMatrices(Matrix4x4 matrix)
|
||||
{
|
||||
if (!IsLoaded) return;
|
||||
foreach (var boneIndex in BonesIndexByLoweredName.Values)
|
||||
{
|
||||
var bone = ReferenceSkeleton.FinalRefBonePose[boneIndex];
|
||||
|
|
@ -63,7 +52,7 @@ public class Skeleton : IDisposable
|
|||
}
|
||||
|
||||
if (!BonesTransformByIndex.TryGetValue(parentIndex, out var parentTransform))
|
||||
parentTransform = new Transform { Relation = matrix };
|
||||
parentTransform = new Transform { Relation = Matrix4x4.Identity };
|
||||
|
||||
boneTransform.Relation = parentTransform.Matrix;
|
||||
Matrix4x4.Invert(boneTransform.Matrix, out var inverted);
|
||||
|
|
@ -73,31 +62,40 @@ public class Skeleton : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
public void SetAnimation(CAnimSet anim)
|
||||
{
|
||||
Anim = new Animation(this, anim);
|
||||
}
|
||||
|
||||
public void SetPoseUniform(Shader shader)
|
||||
{
|
||||
if (!IsLoaded) return;
|
||||
foreach ((var boneIndex, var transform) in BonesTransformByIndex)
|
||||
foreach (var boneIndex in BonesTransformByIndex.Keys)
|
||||
{
|
||||
if (boneIndex >= Constants.MAX_BONE_UNIFORM)
|
||||
break;
|
||||
shader.SetUniform($"uFinalBonesMatrix[{boneIndex}]", InvertedBonesMatrixByIndex[boneIndex] * transform.Matrix);
|
||||
shader.SetUniform($"uFinalBonesMatrix[{boneIndex}]", Matrix4x4.Identity);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniform(Shader shader)
|
||||
public void SetUniform(float deltaSeconds, bool outline, Shader shader)
|
||||
{
|
||||
if (!IsLoaded) return;
|
||||
if (Anim == null) SetPoseUniform(shader);
|
||||
else foreach ((var boneName, var trackIndex) in UnrealSkeleton.ReferenceSkeleton.FinalNameToIndexMap)
|
||||
else
|
||||
{
|
||||
if (!BonesIndexByLoweredName.TryGetValue(boneName.ToLower(), out var boneIndex))
|
||||
continue;
|
||||
if (!InvertedBonesMatrixByIndex.TryGetValue(boneIndex, out var invertMatrix))
|
||||
throw new ArgumentNullException($"no inverse matrix for bone '{boneIndex}'");
|
||||
if (boneIndex >= Constants.MAX_BONE_UNIFORM)
|
||||
break;
|
||||
if (!outline) Anim.ElapsedTime += deltaSeconds / Anim.TimePerFrame;
|
||||
foreach ((var boneName, var trackIndex) in UnrealSkeleton.ReferenceSkeleton.FinalNameToIndexMap)
|
||||
{
|
||||
if (!BonesIndexByLoweredName.TryGetValue(boneName.ToLower(), out var boneIndex))
|
||||
continue;
|
||||
if (!InvertedBonesMatrixByIndex.TryGetValue(boneIndex, out var invertMatrix))
|
||||
throw new ArgumentNullException($"no inverse matrix for bone '{boneIndex}'");
|
||||
if (boneIndex >= Constants.MAX_BONE_UNIFORM)
|
||||
break;
|
||||
|
||||
shader.SetUniform($"uFinalBonesMatrix[{boneIndex}]", invertMatrix * Anim.BoneTransforms[trackIndex][Anim.CurrentTime].Matrix);
|
||||
shader.SetUniform($"uFinalBonesMatrix[{boneIndex}]", invertMatrix * Anim.InterpolateBoneTransform(trackIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -129,16 +129,15 @@ public class Model : IDisposable
|
|||
for (int i = 0; i < Sockets.Length; i++)
|
||||
{
|
||||
if (export.Sockets[i].Load<UStaticMeshSocket>() is not { } socket) continue;
|
||||
Sockets[i] = new Socket(socket, Transforms[0]);
|
||||
Sockets[i] = new Socket(socket);
|
||||
}
|
||||
}
|
||||
|
||||
public Model(USkeletalMesh export, CSkeletalMesh skeletalMesh) : this(export, skeletalMesh, Transform.Identity) {}
|
||||
private Model(USkeletalMesh export, CSkeletalMesh skeletalMesh, Transform transform) : this(export, export.Materials, skeletalMesh.LODs, transform)
|
||||
{
|
||||
var t = Transforms[0];
|
||||
Box = skeletalMesh.BoundingBox * Constants.SCALE_DOWN_RATIO;
|
||||
Skeleton = new Skeleton(export.Skeleton, export.ReferenceSkeleton, t);
|
||||
Skeleton = new Skeleton(export.Skeleton, export.ReferenceSkeleton);
|
||||
|
||||
var sockets = new List<FPackageIndex>();
|
||||
sockets.AddRange(export.Sockets);
|
||||
|
|
@ -148,12 +147,7 @@ public class Model : IDisposable
|
|||
for (int i = 0; i < Sockets.Length; i++)
|
||||
{
|
||||
if (sockets[i].Load<USkeletalMeshSocket>() is not { } socket) continue;
|
||||
|
||||
if (!Skeleton.BonesIndexByLoweredName.TryGetValue(socket.BoneName.Text, out var boneIndex) ||
|
||||
!Skeleton.BonesTransformByIndex.TryGetValue(boneIndex, out var boneTransform))
|
||||
boneTransform = t;
|
||||
|
||||
Sockets[i] = new Socket(socket, boneTransform);
|
||||
Sockets[i] = new Socket(socket);
|
||||
}
|
||||
|
||||
Morphs = new Morph[export.MorphTargets.Length];
|
||||
|
|
@ -254,40 +248,40 @@ public class Model : IDisposable
|
|||
|
||||
public void UpdateMatrices(Options options)
|
||||
{
|
||||
UpdateMatrices();
|
||||
var worldMatrix = UpdateMatrices();
|
||||
foreach (var socket in Sockets)
|
||||
{
|
||||
var boneMatrix = Matrix4x4.Identity;
|
||||
if (HasSkeleton && Skeleton.BonesIndexByLoweredName.TryGetValue(socket.BoneName.Text.ToLower(), out var boneIndex))
|
||||
{
|
||||
if (Skeleton.Anim?.TrackIndexByBoneIndex.TryGetValue(boneIndex, out var trackIndex) ?? false)
|
||||
boneMatrix = Skeleton.Anim.InterpolateBoneTransform(trackIndex);
|
||||
else if (Skeleton.BonesTransformByIndex.TryGetValue(boneIndex, out var boneTransform))
|
||||
boneMatrix = boneTransform.Matrix;
|
||||
}
|
||||
|
||||
var socketRelation = boneMatrix * worldMatrix;
|
||||
foreach (var attached in socket.AttachedModels)
|
||||
{
|
||||
if (!options.TryGetModel(attached, out var attachedModel))
|
||||
continue;
|
||||
|
||||
attachedModel.Transforms[attachedModel.SelectedInstance].Relation = socket.Transform.Matrix;
|
||||
attachedModel.UpdateMatrices();
|
||||
attachedModel.Transforms[attachedModel.SelectedInstance].Relation = socket.Transform.Matrix * socketRelation;
|
||||
attachedModel.UpdateMatrices(options);
|
||||
}
|
||||
}
|
||||
}
|
||||
private void UpdateMatrices()
|
||||
private Matrix4x4 UpdateMatrices()
|
||||
{
|
||||
var matrix = Transforms[SelectedInstance].Matrix;
|
||||
if (matrix == _previousMatrix) return;
|
||||
if (matrix == _previousMatrix) return matrix;
|
||||
|
||||
_matrixVbo.Bind();
|
||||
_matrixVbo.Update(SelectedInstance, matrix);
|
||||
_matrixVbo.Unbind();
|
||||
|
||||
if (HasSkeleton) Skeleton.UpdateBoneMatrices(matrix);
|
||||
foreach (var socket in Sockets)
|
||||
{
|
||||
if (!HasSkeleton ||
|
||||
!Skeleton.BonesIndexByLoweredName.TryGetValue(socket.BoneName.Text, out var boneIndex) ||
|
||||
!Skeleton.BonesTransformByIndex.TryGetValue(boneIndex, out var boneTransform))
|
||||
boneTransform = Transforms[SelectedInstance];
|
||||
|
||||
socket.UpdateSocketMatrix(boneTransform.Matrix);
|
||||
}
|
||||
|
||||
_previousMatrix = matrix;
|
||||
return matrix;
|
||||
}
|
||||
|
||||
public void UpdateMorph(int index)
|
||||
|
|
@ -376,7 +370,7 @@ public class Model : IDisposable
|
|||
IsSetup = true;
|
||||
}
|
||||
|
||||
public void Render(Shader shader, bool outline = false)
|
||||
public void Render(float deltaSeconds, Shader shader, bool outline = false)
|
||||
{
|
||||
if (outline) GL.Disable(EnableCap.DepthTest);
|
||||
if (TwoSided) GL.Disable(EnableCap.CullFace);
|
||||
|
|
@ -388,7 +382,7 @@ public class Model : IDisposable
|
|||
|
||||
_vao.Bind();
|
||||
shader.SetUniform("uMorphTime", MorphTime);
|
||||
if (HasSkeleton) Skeleton.SetUniform(shader);
|
||||
if (HasSkeleton) Skeleton.SetUniform(deltaSeconds, outline, shader);
|
||||
if (!outline)
|
||||
{
|
||||
shader.SetUniform("uUvCount", UvCount);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
|
||||
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
|
|
@ -22,30 +21,23 @@ public class Socket : IDisposable
|
|||
AttachedModels = new List<FGuid>();
|
||||
}
|
||||
|
||||
public Socket(UStaticMeshSocket socket, Transform transform) : this()
|
||||
public Socket(UStaticMeshSocket socket) : this()
|
||||
{
|
||||
Name = socket.SocketName.Text;
|
||||
Transform.Relation = transform.Matrix;
|
||||
Transform.Rotation = socket.RelativeRotation.Quaternion();
|
||||
Transform.Position = socket.RelativeLocation * Constants.SCALE_DOWN_RATIO;
|
||||
Transform.Scale = socket.RelativeScale;
|
||||
}
|
||||
|
||||
public Socket(USkeletalMeshSocket socket, Transform transform) : this()
|
||||
public Socket(USkeletalMeshSocket socket) : this()
|
||||
{
|
||||
Name = socket.SocketName.Text;
|
||||
BoneName = socket.BoneName;
|
||||
Transform.Relation = transform.Matrix;
|
||||
Transform.Rotation = socket.RelativeRotation.Quaternion();
|
||||
Transform.Position = socket.RelativeLocation * Constants.SCALE_DOWN_RATIO;
|
||||
Transform.Scale = socket.RelativeScale;
|
||||
}
|
||||
|
||||
public void UpdateSocketMatrix(Matrix4x4 matrix)
|
||||
{
|
||||
Transform.Relation = matrix;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
AttachedModels.Clear();
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ public class Renderer : IDisposable
|
|||
Options.SetupModelsAndLights();
|
||||
}
|
||||
|
||||
public void Render()
|
||||
public void Render(float deltaSeconds)
|
||||
{
|
||||
var viewMatrix = CameraOp.GetViewMatrix();
|
||||
var projMatrix = CameraOp.GetProjectionMatrix();
|
||||
|
|
@ -127,7 +127,7 @@ public class Renderer : IDisposable
|
|||
{
|
||||
model.UpdateMatrices(Options);
|
||||
if (!model.Show) continue;
|
||||
model.Render(_shader);
|
||||
model.Render(deltaSeconds, _shader);
|
||||
}
|
||||
|
||||
{ // light pass
|
||||
|
|
@ -147,7 +147,7 @@ public class Renderer : IDisposable
|
|||
if (Options.TryGetModel(out var selected) && selected.Show)
|
||||
{
|
||||
_outline.Render(viewMatrix, CameraOp.Position, projMatrix);
|
||||
selected.Render(_outline, true);
|
||||
selected.Render(deltaSeconds, _outline, true);
|
||||
}
|
||||
|
||||
// picking pass (dedicated FBO, binding to 0 afterward)
|
||||
|
|
|
|||
|
|
@ -446,12 +446,6 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
Layout("Guid");ImGui.Text($" : {s.Renderer.Options.SelectedModel.ToString(EGuidFormats.UniqueObjectGuid)}");
|
||||
if (model.HasSkeleton)
|
||||
{
|
||||
if (model.Skeleton.Anim != null)
|
||||
{
|
||||
ImGui.BeginDisabled(model.Skeleton.Anim.MaxTime == 0);
|
||||
ImGui.DragInt("Time", ref model.Skeleton.Anim.CurrentTime, 1, 0, model.Skeleton.Anim.MaxTime);
|
||||
ImGui.EndDisabled();
|
||||
}
|
||||
Layout("Skeleton");ImGui.Text($" : {model.Skeleton.UnrealSkeleton.Name}");
|
||||
Layout("Bones");ImGui.Text($" : x{model.Skeleton.UnrealSkeleton.BoneTree.Length}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,14 +116,16 @@ public class Snooper : GameWindow
|
|||
if (!IsVisible)
|
||||
return;
|
||||
|
||||
var delta = (float) args.Time;
|
||||
|
||||
ClearWhatHasBeenDrawn(); // clear window background
|
||||
_gui.Controller.Update(this, (float)args.Time);
|
||||
_gui.Controller.Update(this, delta);
|
||||
_gui.Render(this);
|
||||
|
||||
Framebuffer.Bind(); // switch to viewport background
|
||||
ClearWhatHasBeenDrawn(); // clear viewport background
|
||||
|
||||
Renderer.Render();
|
||||
Renderer.Render(delta);
|
||||
|
||||
Framebuffer.BindMsaa();
|
||||
Framebuffer.Bind(0); // switch to window background
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user