mirror of
https://github.com/4sval/FModel.git
synced 2026-04-19 08:07:43 -05:00
timeline
This commit is contained in:
parent
c7d532fff9
commit
9b4c83931b
|
|
@ -1 +1 @@
|
|||
Subproject commit f91288c261ecf9cd5a276683a9812fe920265eb7
|
||||
Subproject commit c7fed92ddb2dc2aaec428504396945deefb1ff22
|
||||
|
|
@ -33,9 +33,9 @@ public class ImGuiController : IDisposable
|
|||
private int _windowHeight;
|
||||
// private string _iniPath;
|
||||
|
||||
private ImFontPtr _normal;
|
||||
private ImFontPtr _bold;
|
||||
private ImFontPtr _semiBold;
|
||||
public ImFontPtr FontNormal;
|
||||
public ImFontPtr FontBold;
|
||||
public ImFontPtr FontSemiBold;
|
||||
|
||||
private readonly Vector2 _scaleFactor = Vector2.One;
|
||||
|
||||
|
|
@ -57,9 +57,9 @@ public class ImGuiController : IDisposable
|
|||
// ImGui.LoadIniSettingsFromDisk(_iniPath);
|
||||
|
||||
var io = ImGui.GetIO();
|
||||
_normal = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\segoeui.ttf", 16);
|
||||
_bold = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\segoeuib.ttf", 16);
|
||||
_semiBold = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\seguisb.ttf", 16);
|
||||
FontNormal = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\segoeui.ttf", 16);
|
||||
FontBold = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\segoeuib.ttf", 16);
|
||||
FontSemiBold = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\seguisb.ttf", 16);
|
||||
|
||||
io.BackendFlags |= ImGuiBackendFlags.RendererHasVtxOffset;
|
||||
io.ConfigFlags |= ImGuiConfigFlags.NavEnableKeyboard;
|
||||
|
|
@ -75,13 +75,13 @@ public class ImGuiController : IDisposable
|
|||
_frameBegun = true;
|
||||
}
|
||||
|
||||
public void Bold() => PushFont(_bold);
|
||||
public void SemiBold() => PushFont(_semiBold);
|
||||
public void Bold() => PushFont(FontBold);
|
||||
public void SemiBold() => PushFont(FontSemiBold);
|
||||
|
||||
public void PopFont()
|
||||
{
|
||||
ImGui.PopFont();
|
||||
PushFont(_normal);
|
||||
PushFont(FontNormal);
|
||||
}
|
||||
|
||||
private void PushFont(ImFontPtr ptr) => ImGui.PushFont(ptr);
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ public partial class MainWindow
|
|||
"MoonMan/Content/DeliverUsTheMoon/Characters/Astronaut/SK_Astronaut.uasset"));
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
_applicationView.CUE4Parse.Extract(cancellationToken,
|
||||
"MoonMan/Content/DeliverUsTheMoon/Characters/Astronaut/AM_ControllingASE.uasset"));
|
||||
"MoonMan/Content/DeliverUsTheMoon/Characters/Astronaut/cinematic/A_Astro_Space_Breach_Grab2_Success.uasset"));
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
_applicationView.CUE4Parse.Extract(cancellationToken,
|
||||
"MoonMan/Content/DeliverUsTheMoon/Characters/Astronaut/AM_OxygenHub_Enter.uasset"));
|
||||
|
|
|
|||
|
|
@ -1,18 +1,35 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using CUE4Parse_Conversion.Animations;
|
||||
using CUE4Parse.Utils;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace FModel.Views.Snooper.Models.Animations;
|
||||
|
||||
public enum AnimSeparatorType
|
||||
{
|
||||
InBetween,
|
||||
End
|
||||
}
|
||||
|
||||
public class Animation : IDisposable
|
||||
{
|
||||
public float ElapsedTime;
|
||||
public bool IsPaused;
|
||||
public readonly int TotalFrames;
|
||||
public readonly float TotalDuration;
|
||||
|
||||
public int CurrentSequence;
|
||||
public readonly Sequence[] Sequences;
|
||||
public int SequencesCount => Sequences.Length;
|
||||
|
||||
public Animation()
|
||||
{
|
||||
Reset();
|
||||
|
||||
IsPaused = false;
|
||||
TotalFrames = 0;
|
||||
TotalDuration = 0.0f;
|
||||
Sequences = Array.Empty<Sequence>();
|
||||
}
|
||||
|
||||
|
|
@ -22,52 +39,124 @@ public class Animation : IDisposable
|
|||
for (int i = 0; i < Sequences.Length; i++)
|
||||
{
|
||||
Sequences[i] = new Sequence(anim.Sequences[i], skeleton, rotationOnly);
|
||||
|
||||
TotalFrames += Sequences[i].MaxFrame;
|
||||
TotalDuration += Sequences[i].Duration;
|
||||
}
|
||||
|
||||
if (Sequences.Length > 0)
|
||||
ElapsedTime = Sequences[0].StartTime;
|
||||
}
|
||||
|
||||
public void Update(float deltaSeconds)
|
||||
{
|
||||
Sequences[CurrentSequence].Update(deltaSeconds);
|
||||
if (IsPaused) return;
|
||||
if (Sequences[CurrentSequence].IsComplete)
|
||||
{
|
||||
Sequences[CurrentSequence].Reset();
|
||||
CurrentSequence++;
|
||||
}
|
||||
if (CurrentSequence >= SequencesCount)
|
||||
Reset();
|
||||
|
||||
ElapsedTime += Sequences[CurrentSequence].Update(deltaSeconds);
|
||||
}
|
||||
|
||||
|
||||
public Matrix4x4 InterpolateBoneTransform(int boneIndex)
|
||||
{
|
||||
// interpolate here
|
||||
return Sequences[CurrentSequence].BonesTransform[boneIndex][Sequences[CurrentSequence].Frame].Matrix;
|
||||
}
|
||||
|
||||
public void CheckForNextSequence()
|
||||
private void Reset()
|
||||
{
|
||||
if (Sequences[CurrentSequence].ElapsedTime > Sequences[CurrentSequence].EndPos)
|
||||
{
|
||||
Sequences[CurrentSequence].ElapsedTime = 0;
|
||||
Sequences[CurrentSequence].Frame = 0;
|
||||
CurrentSequence++;
|
||||
}
|
||||
|
||||
if (CurrentSequence >= SequencesCount)
|
||||
{
|
||||
CurrentSequence = 0;
|
||||
}
|
||||
ElapsedTime = 0.0f;
|
||||
CurrentSequence = 0;
|
||||
}
|
||||
|
||||
public void ImGuiTimeline()
|
||||
private float _timeHeight = 10.0f;
|
||||
private float _timeBarHeight => _timeHeight * 2.0f;
|
||||
public void ImGuiTimeline(ImFontPtr fontPtr)
|
||||
{
|
||||
var io = ImGui.GetIO();
|
||||
var canvasP0 = ImGui.GetCursorScreenPos();
|
||||
var canvasSize = ImGui.GetContentRegionAvail();
|
||||
var canvasP1 = new Vector2(canvasP0.X + canvasSize.X, canvasP0.Y + canvasSize.Y);
|
||||
var ratio = canvasSize / Sequences[CurrentSequence].MaxFrame;
|
||||
var ratio = canvasSize / TotalFrames;
|
||||
|
||||
var drawList = ImGui.GetWindowDrawList();
|
||||
drawList.AddRectFilled(canvasP0, canvasP1, 0xFF242424);
|
||||
|
||||
var l1 = new Vector2(canvasP0.X + Sequences[CurrentSequence].Frame * ratio.X, canvasP0.Y);
|
||||
var l2 = new Vector2(l1.X, canvasP1.Y);
|
||||
drawList.AddLine(l1, l2, 0xFF0000FF, 2f);
|
||||
ImGui.InvisibleButton("timeline_canvas", canvasP1 with { Y = _timeBarHeight }, ImGuiButtonFlags.MouseButtonLeft);
|
||||
IsPaused = ImGui.IsItemActive();
|
||||
if (IsPaused && ImGui.IsMouseDragging(ImGuiMouseButton.Left))
|
||||
{
|
||||
var mousePosCanvas = io.MousePos - canvasP0;
|
||||
ElapsedTime = Math.Clamp(mousePosCanvas.X / canvasSize.X * TotalDuration, 0, TotalDuration);
|
||||
}
|
||||
|
||||
ImGui.Text($"{Sequences[CurrentSequence].Name} > {(CurrentSequence < SequencesCount - 1 ? Sequences[CurrentSequence + 1].Name : Sequences[0].Name)}");
|
||||
ImGui.Text($"Frame: {Sequences[CurrentSequence].Frame}/{Sequences[CurrentSequence].MaxFrame}");
|
||||
ImGui.Text($"FPS: {Sequences[CurrentSequence].FramesPerSecond}");
|
||||
drawList.AddRectFilled(canvasP0, canvasP1 with { Y = canvasP0.Y + _timeBarHeight }, 0xFF181818);
|
||||
drawList.PushClipRect(canvasP0, canvasP1 with { Y = canvasP0.Y + _timeBarHeight }, true);
|
||||
{
|
||||
for (float x = 0; x < canvasSize.X; x += ratio.X * 10f)
|
||||
{
|
||||
drawList.AddLine(new Vector2(canvasP0.X + x, canvasP0.Y + _timeHeight + 2.5f), canvasP1 with { X = canvasP0.X + x }, 0xA0FFFFFF);
|
||||
drawList.AddText(fontPtr, 14, new Vector2(canvasP0.X + x + 4, canvasP0.Y + 7.5f), 0x50FFFFFF, (x / ratio.X).FloorToInt().ToString());
|
||||
}
|
||||
}
|
||||
drawList.PopClipRect();
|
||||
|
||||
for (int i = 0; i < Sequences.Length; i++)
|
||||
{
|
||||
Sequences[i].DrawSequence(drawList, canvasP0, ratio, i);
|
||||
}
|
||||
|
||||
DrawSeparator(drawList, canvasP0, canvasP1, ElapsedTime * ratio.X, AnimSeparatorType.InBetween);
|
||||
DrawSeparator(drawList, canvasP0, canvasP1, TotalDuration * ratio.X, AnimSeparatorType.End);
|
||||
|
||||
// ImGui.Text($"{Sequences[CurrentSequence].Name} > {(CurrentSequence < SequencesCount - 1 ? Sequences[CurrentSequence + 1].Name : Sequences[0].Name)}");
|
||||
// ImGui.Text($"Frame: {Sequences[CurrentSequence].Frame}/{Sequences[CurrentSequence].MaxFrame}");
|
||||
// ImGui.Text($"Frame: {Frame}/{TotalFrames}");
|
||||
// ImGui.Text($"FPS: {Sequences[CurrentSequence].FramesPerSecond}");
|
||||
}
|
||||
|
||||
private void DrawSeparator(ImDrawListPtr drawList, Vector2 origin, Vector2 destination, float time, AnimSeparatorType separatorType)
|
||||
{
|
||||
const int size = 5;
|
||||
|
||||
Vector2 p1 = separatorType switch
|
||||
{
|
||||
AnimSeparatorType.InBetween => new Vector2(origin.X + time, origin.Y + _timeBarHeight),
|
||||
AnimSeparatorType.End => origin with { X = origin.X + time },
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(separatorType), separatorType, null)
|
||||
};
|
||||
var p2 = new Vector2(p1.X, destination.Y);
|
||||
|
||||
uint color = separatorType switch
|
||||
{
|
||||
AnimSeparatorType.InBetween => 0xFF6F6F6F,
|
||||
AnimSeparatorType.End => 0xFF2E3E82,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(separatorType), separatorType, null)
|
||||
};
|
||||
|
||||
drawList.AddLine(p1, p2, color, 1f);
|
||||
switch (separatorType)
|
||||
{
|
||||
case AnimSeparatorType.InBetween:
|
||||
color = 0xFF30478C;
|
||||
var xl = p1.X - size;
|
||||
var xr = p1.X + size;
|
||||
var yb = origin.Y + _timeBarHeight - _timeHeight / 2.0f;
|
||||
|
||||
drawList.AddQuadFilled(origin with { X = xl }, origin with { X = xr }, new Vector2(xr, yb), new Vector2(xl, yb), color);
|
||||
drawList.AddTriangleFilled(new Vector2(xl, yb), new Vector2(xr, yb), p1, color);
|
||||
break;
|
||||
case AnimSeparatorType.End:
|
||||
drawList.AddTriangleFilled(p1, p1 with { X = p1.X - size }, p1 with { Y = p1.Y + size }, color);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(separatorType), separatorType, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using CUE4Parse_Conversion.Animations;
|
||||
using CUE4Parse.Utils;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace FModel.Views.Snooper.Models.Animations;
|
||||
|
||||
|
|
@ -10,27 +12,27 @@ public class Sequence : IDisposable
|
|||
public float ElapsedTime;
|
||||
public readonly string Name;
|
||||
public readonly int MaxFrame;
|
||||
public readonly float FramesPerSecond;
|
||||
public readonly float StartPos;
|
||||
public readonly float AnimStartTime;
|
||||
public readonly float AnimEndTime;
|
||||
public readonly float TimePerFrame;
|
||||
public readonly float StartTime;
|
||||
public readonly float Duration;
|
||||
public readonly float EndTime;
|
||||
public readonly int LoopingCount;
|
||||
|
||||
public float TimePerFrame => 1.0f / FramesPerSecond;
|
||||
public float EndPos => AnimEndTime / TimePerFrame;
|
||||
|
||||
public readonly Transform[][] BonesTransform;
|
||||
|
||||
public bool IsComplete => ElapsedTime > Duration;
|
||||
// public bool IsComplete => Frame >= MaxFrame;
|
||||
|
||||
public Sequence(CAnimSequence sequence, Skeleton skeleton, bool rotationOnly)
|
||||
{
|
||||
Frame = 0;
|
||||
ElapsedTime = 0.0f;
|
||||
Reset();
|
||||
|
||||
Name = sequence.Name;
|
||||
MaxFrame = sequence.NumFrames - 1;
|
||||
FramesPerSecond = sequence.Rate;
|
||||
StartPos = sequence.StartPos;
|
||||
AnimStartTime = sequence.AnimStartTime;
|
||||
AnimEndTime = sequence.AnimEndTime;
|
||||
TimePerFrame = 1.0f / sequence.Rate;
|
||||
StartTime = sequence.StartPos / TimePerFrame;
|
||||
Duration = sequence.AnimEndTime / TimePerFrame;
|
||||
EndTime = StartTime + Duration;
|
||||
LoopingCount = sequence.LoopingCount;
|
||||
|
||||
BonesTransform = new Transform[skeleton.BonesTransformByIndex.Count][];
|
||||
|
|
@ -81,10 +83,29 @@ public class Sequence : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
public void Update(float deltaSeconds)
|
||||
public float Update(float deltaSeconds)
|
||||
{
|
||||
ElapsedTime += deltaSeconds / TimePerFrame;
|
||||
var delta = deltaSeconds / TimePerFrame;
|
||||
ElapsedTime += delta;
|
||||
Frame = Math.Min(ElapsedTime.FloorToInt(), MaxFrame);
|
||||
return delta;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
ElapsedTime = 0.0f;
|
||||
Frame = 0;
|
||||
}
|
||||
|
||||
|
||||
private readonly float _height = 20.0f;
|
||||
public void DrawSequence(ImDrawListPtr drawList, Vector2 origin, Vector2 ratio, int index)
|
||||
{
|
||||
var height = _height * (index % 2) + _height;
|
||||
var p1 = new Vector2(origin.X + StartTime * ratio.X, origin.Y + height);
|
||||
var p2 = new Vector2(origin.X + EndTime * ratio.X, origin.Y + height + _height);
|
||||
drawList.AddRectFilled(p1, p2, 0xFF175F17);
|
||||
drawList.AddText(p1 with { X = p1.X + 2.5f }, 0xFF000000, Name);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
|
|||
|
|
@ -95,29 +95,22 @@ public class Skeleton : IDisposable
|
|||
_ssbo = new BufferObject<Matrix4x4>(InvertedBonesMatrixByIndex.Length, BufferTarget.ShaderStorageBuffer);
|
||||
}
|
||||
|
||||
public void Render(float deltaSeconds = 0f, bool update = false)
|
||||
public void UpdateMatrices(float deltaSeconds)
|
||||
{
|
||||
if (!IsLoaded) return;
|
||||
|
||||
_ssbo.BindBufferBase(1);
|
||||
|
||||
if (!HasAnim)
|
||||
{
|
||||
for (int boneIndex = 0; boneIndex < InvertedBonesMatrixByIndex.Length; boneIndex++)
|
||||
{
|
||||
_ssbo.Update(boneIndex, Matrix4x4.Identity);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (update) Anim.Update(deltaSeconds);
|
||||
Anim.Update(deltaSeconds);
|
||||
for (int boneIndex = 0; boneIndex < InvertedBonesMatrixByIndex.Length; boneIndex++)
|
||||
{
|
||||
_ssbo.Update(boneIndex, InvertedBonesMatrixByIndex[boneIndex] * Anim.InterpolateBoneTransform(boneIndex));
|
||||
}
|
||||
if (update) Anim.CheckForNextSequence();
|
||||
}
|
||||
|
||||
_ssbo.Unbind();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -246,9 +246,10 @@ public class Model : IDisposable
|
|||
Transforms.Add(transform);
|
||||
}
|
||||
|
||||
public void UpdateMatrices(Options options)
|
||||
public void UpdateMatrices(Options options, float deltaSeconds = 0f, bool update = false)
|
||||
{
|
||||
var worldMatrix = UpdateMatrices();
|
||||
if (update && HasSkeleton) Skeleton.UpdateMatrices(deltaSeconds);
|
||||
foreach (var socket in Sockets)
|
||||
{
|
||||
var boneMatrix = Matrix4x4.Identity;
|
||||
|
|
@ -370,7 +371,7 @@ public class Model : IDisposable
|
|||
IsSetup = true;
|
||||
}
|
||||
|
||||
public void Render(float deltaSeconds, Shader shader, bool outline = false)
|
||||
public void Render(Shader shader, bool outline = false)
|
||||
{
|
||||
if (outline) GL.Disable(EnableCap.DepthTest);
|
||||
if (TwoSided) GL.Disable(EnableCap.CullFace);
|
||||
|
|
@ -382,7 +383,6 @@ public class Model : IDisposable
|
|||
|
||||
_vao.Bind();
|
||||
shader.SetUniform("uMorphTime", MorphTime);
|
||||
if (HasSkeleton) Skeleton.Render(deltaSeconds, !outline);
|
||||
if (!outline)
|
||||
{
|
||||
shader.SetUniform("uUvCount", UvCount);
|
||||
|
|
@ -413,7 +413,6 @@ public class Model : IDisposable
|
|||
|
||||
_vao.Bind();
|
||||
shader.SetUniform("uMorphTime", MorphTime);
|
||||
if (HasSkeleton) Skeleton.Render();
|
||||
|
||||
foreach (var section in Sections)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -135,9 +135,9 @@ public class Renderer : IDisposable
|
|||
// render model pass
|
||||
foreach (var model in Options.Models.Values)
|
||||
{
|
||||
model.UpdateMatrices(Options);
|
||||
model.UpdateMatrices(Options, deltaSeconds, model.Show);
|
||||
if (!model.Show) continue;
|
||||
model.Render(deltaSeconds, _shader);
|
||||
model.Render(_shader);
|
||||
}
|
||||
|
||||
{ // light pass
|
||||
|
|
@ -157,7 +157,7 @@ public class Renderer : IDisposable
|
|||
if (Options.TryGetModel(out var selected) && selected.Show)
|
||||
{
|
||||
_outline.Render(viewMatrix, CameraOp.Position, projMatrix);
|
||||
selected.Render(deltaSeconds, _outline, true);
|
||||
selected.Render(_outline, true);
|
||||
}
|
||||
|
||||
// picking pass (dedicated FBO, binding to 0 afterward)
|
||||
|
|
|
|||
|
|
@ -362,7 +362,7 @@ public class Material : IDisposable
|
|||
var origin = new Vector2(canvasP0.X + _scrolling.X, canvasP0.Y + _scrolling.Y);
|
||||
var absoluteMiddle = canvasSize / 2.0f;
|
||||
|
||||
ImGui.InvisibleButton("texture_inspector_canvas", canvasSize, ImGuiButtonFlags.MouseButtonLeft | ImGuiButtonFlags.MouseButtonRight);
|
||||
ImGui.InvisibleButton("texture_inspector_canvas", canvasSize, ImGuiButtonFlags.MouseButtonLeft);
|
||||
if (ImGui.IsItemActive() && ImGui.IsMouseDragging(ImGuiMouseButton.Left))
|
||||
{
|
||||
_scrolling.X += io.MouseDelta.X;
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ public class SnimGui
|
|||
DrawDockSpace(s.Size);
|
||||
|
||||
SectionWindow("Material Inspector", s.Renderer, DrawMaterialInspector, false);
|
||||
AnimationWindow("Timeline", s.Renderer, (icons, skeleton) => skeleton.Anim.ImGuiTimeline());
|
||||
AnimationWindow("Timeline", s.Renderer, (icons, skeleton) => skeleton.Anim.ImGuiTimeline(Controller.FontSemiBold));
|
||||
|
||||
Window("World", () => DrawWorld(s), false);
|
||||
|
||||
|
|
@ -770,12 +770,14 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
|
||||
private void AnimationWindow(string name, Renderer renderer, Action<Dictionary<string, Texture>, Skeleton> content, bool styled = true)
|
||||
{
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
|
||||
MeshWindow(name, renderer, (icons, model) =>
|
||||
{
|
||||
if (!model.HasSkeleton) CenteredTextColored(_errorColor, "No Skeleton To Animate");
|
||||
else if (!model.Skeleton.HasAnim) CenteredTextColored(_errorColor, "Mesh Not Animated");
|
||||
else content(icons, model.Skeleton);
|
||||
}, styled);
|
||||
ImGui.PopStyleVar();
|
||||
}
|
||||
|
||||
private void PopStyleCompact() => ImGui.PopStyleVar(2);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user