rotation only animation

This commit is contained in:
4sval 2023-02-06 00:12:46 +01:00
parent f288791b71
commit 0b7ed2cf7f
8 changed files with 120 additions and 64 deletions

@ -1 +1 @@
Subproject commit c6fcae365d91d7d9501e2be79e78dfba136b4670
Subproject commit c2143de8bda93c02f5b07657b9110c477ec1d5ff

View File

@ -647,6 +647,13 @@ namespace FModel.Settings
set => SetProperty(ref _showGrid, value);
}
private bool _animateWithRotationOnly;
public bool AnimateWithRotationOnly
{
get => _animateWithRotationOnly;
set => SetProperty(ref _animateWithRotationOnly, value);
}
private Camera.WorldMode _cameraMode = Camera.WorldMode.Arcball;
public Camera.WorldMode CameraMode
{

View File

@ -1,8 +1,9 @@
using System;
using System;
using System.Collections.Generic;
using System.Numerics;
using CUE4Parse_Conversion.Animations;
using CUE4Parse.Utils;
using ImGuiNET;
namespace FModel.Views.Snooper.Models.Animations;
@ -15,9 +16,9 @@ public class Animation : IDisposable
public readonly Dictionary<int, int> TrackIndexByBoneIndex;
public readonly Transform[][] BoneTransforms;
public float TimePerFrame => 1.0f / FramesPerSecond;
private float TimePerFrame => 1.0f / FramesPerSecond;
public Animation(Skeleton skeleton, CAnimSet anim)
public Animation(Skeleton skeleton, CAnimSet anim, bool rotationOnly)
{
Frame = 0;
ElapsedTime = 0;
@ -39,12 +40,13 @@ public class Animation : IDisposable
throw new ArgumentNullException($"no transform for bone '{boneIndex}'");
TrackIndexByBoneIndex[boneIndex] = trackIndex;
var boneOrientation = originalTransform.Rotation;
var bonePosition = originalTransform.Position;
var boneScale = originalTransform.Scale;
for (var frame = 0; frame < BoneTransforms[trackIndex].Length; frame++)
{
var boneOrientation = originalTransform.Rotation;
var bonePosition = originalTransform.Position;
var boneScale = originalTransform.Scale;
sequence.Tracks[trackIndex].GetBonePosition(frame, MaxFrame, false, ref bonePosition, ref boneOrientation);
if (frame < sequence.Tracks[trackIndex].KeyScale.Length)
boneScale = sequence.Tracks[trackIndex].KeyScale[frame];
@ -70,19 +72,31 @@ public class Animation : IDisposable
{
Relation = bone.ParentIndex >= 0 ? BoneTransforms[bone.ParentIndex][frame].Matrix : originalTransform.Relation,
Rotation = boneOrientation,
Position = bonePosition,
Position = rotationOnly ? originalTransform.Position : bonePosition,
Scale = boneScale
};
}
}
}
public void Update(float deltaSeconds)
{
ElapsedTime += deltaSeconds / TimePerFrame;
Frame = ElapsedTime.FloorToInt() % MaxFrame;
}
public Matrix4x4 InterpolateBoneTransform(int trackIndex)
{
Frame = ElapsedTime.FloorToInt() % MaxFrame; // interpolate here
// interpolate here
return BoneTransforms[trackIndex][Frame].Matrix;
}
public void ImGuiTimeline()
{
ImGui.Text($"Frame: {Frame}/{MaxFrame}");
ImGui.Text($"FPS: {FramesPerSecond}");
}
public void Dispose()
{
TrackIndexByBoneIndex.Clear();

View File

@ -18,6 +18,7 @@ public class Skeleton : IDisposable
public readonly bool IsLoaded;
public Animation Anim;
public bool HasAnim => Anim != null;
public Skeleton()
{
@ -62,9 +63,9 @@ public class Skeleton : IDisposable
}
}
public void SetAnimation(CAnimSet anim)
public void SetAnimation(CAnimSet anim, bool rotationOnly)
{
Anim = new Animation(this, anim);
Anim = new Animation(this, anim, rotationOnly);
}
public void SetPoseUniform(Shader shader)
@ -77,13 +78,14 @@ public class Skeleton : IDisposable
shader.SetUniform($"uFinalBonesMatrix[{boneIndex}]", Matrix4x4.Identity);
}
}
public void SetUniform(Shader shader, float deltaSeconds = 0f, bool updateElapsedTime = false)
public void SetUniform(Shader shader, float deltaSeconds = 0f, bool update = false)
{
if (!IsLoaded) return;
if (Anim == null) SetPoseUniform(shader);
if (!HasAnim) SetPoseUniform(shader);
else
{
if (!updateElapsedTime) Anim.ElapsedTime += deltaSeconds / Anim.TimePerFrame;
if (update) Anim.Update(deltaSeconds);
foreach (var boneIndex in BonesTransformByIndex.Keys)
{
if (boneIndex >= Constants.MAX_BONE_UNIFORM)

View File

@ -382,7 +382,7 @@ public class Model : IDisposable
_vao.Bind();
shader.SetUniform("uMorphTime", MorphTime);
if (HasSkeleton) Skeleton.SetUniform(shader, deltaSeconds, outline);
if (HasSkeleton) Skeleton.SetUniform(shader, deltaSeconds, !outline);
if (!outline)
{
shader.SetUniform("uUvCount", UvCount);

View File

@ -36,6 +36,7 @@ public class Renderer : IDisposable
public bool ShowSkybox;
public bool ShowGrid;
public bool ShowLights;
public bool AnimateWithRotationOnly;
public int VertexColor;
public Camera CameraOp { get; }
@ -53,6 +54,7 @@ public class Renderer : IDisposable
ShowSkybox = UserSettings.Default.ShowSkybox;
ShowGrid = UserSettings.Default.ShowGrid;
AnimateWithRotationOnly = UserSettings.Default.AnimateWithRotationOnly;
VertexColor = 0; // default
}
@ -93,7 +95,7 @@ public class Renderer : IDisposable
model.Skeleton?.UnrealSkeleton.ConvertAnims(animSequence) is not { } anim || anim.Sequences.Count == 0)
return;
model.Skeleton.SetAnimation(anim);
model.Skeleton.SetAnimation(anim, AnimateWithRotationOnly);
Options.AnimateMesh(false);
}
@ -408,6 +410,7 @@ public class Renderer : IDisposable
if (_saveCameraMode) UserSettings.Default.CameraMode = CameraOp.Mode;
UserSettings.Default.ShowSkybox = ShowSkybox;
UserSettings.Default.ShowGrid = ShowGrid;
UserSettings.Default.AnimateWithRotationOnly = AnimateWithRotationOnly;
}
public void Dispose()

View File

@ -341,12 +341,15 @@ public class Material : IDisposable
var texture = GetSelectedTexture() ?? fallback;
if (ImGui.BeginTable("texture_inspector", 2, ImGuiTableFlags.SizingStretchProp))
{
SnimGui.Layout("Type");ImGui.Text($" : ({texture.Format}) {texture.Name}");
SnimGui.TooltipCopy("(?) Click to Copy Path", texture.Path);
SnimGui.Layout("Guid");ImGui.Text($" : {texture.Guid.ToString(EGuidFormats.UniqueObjectGuid)}");
SnimGui.Layout("Import");ImGui.Text($" : {texture.ImportedWidth}x{texture.ImportedHeight}");
SnimGui.Layout("Export");ImGui.Text($" : {texture.Width}x{texture.Height}");
ImGui.EndTable();
SnimGui.NoFramePaddingOnY(() =>
{
SnimGui.Layout("Type");ImGui.Text($" : ({texture.Format}) {texture.Name}");
SnimGui.TooltipCopy("(?) Click to Copy Path", texture.Path);
SnimGui.Layout("Guid");ImGui.Text($" : {texture.Guid.ToString(EGuidFormats.UniqueObjectGuid)}");
SnimGui.Layout("Import");ImGui.Text($" : {texture.ImportedWidth}x{texture.ImportedHeight}");
SnimGui.Layout("Export");ImGui.Text($" : {texture.Width}x{texture.Height}");
ImGui.EndTable();
});
}
var largest = ImGui.GetContentRegionAvail();

View File

@ -9,6 +9,7 @@ using System.Numerics;
using System.Text;
using FModel.Settings;
using FModel.Views.Snooper.Models;
using FModel.Views.Snooper.Models.Animations;
using FModel.Views.Snooper.Shading;
using OpenTK.Graphics.OpenGL4;
@ -71,8 +72,8 @@ public class SnimGui
DrawDockSpace(s.Size);
SectionWindow("Material Inspector", s.Renderer, DrawMaterialInspector, false);
AnimationWindow("Timeline", s.Renderer, (icons, skeleton) => skeleton.Anim.ImGuiTimeline());
// Window("Timeline", () => {});
Window("World", () => DrawWorld(s), false);
DrawSockets(s);
@ -138,7 +139,9 @@ public class SnimGui
ImGui.Checkbox("", ref s.Renderer.ShowGrid);
ImGui.PopID();Layout("Lights");ImGui.PushID(3);
ImGui.Checkbox("", ref s.Renderer.ShowLights);
ImGui.PopID();Layout("Vertex Colors");ImGui.PushID(4);
ImGui.PopID();Layout("Animate With Rotation Only");ImGui.PushID(4);
ImGui.Checkbox("", ref s.Renderer.AnimateWithRotationOnly);
ImGui.PopID();Layout("Vertex Colors");ImGui.PushID(5);
ImGui.Combo("vertex_colors", ref s.Renderer.VertexColor,
"Default\0Diffuse Only\0Colors\0Normals\0Texture Coordinates\0");
ImGui.PopID();
@ -442,20 +445,23 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
{
if (ImGui.BeginTable("model_details", 2, ImGuiTableFlags.SizingStretchProp))
{
Layout("Entity");ImGui.Text($" : ({model.Type}) {model.Name}");
Layout("Guid");ImGui.Text($" : {s.Renderer.Options.SelectedModel.ToString(EGuidFormats.UniqueObjectGuid)}");
if (model.HasSkeleton)
NoFramePaddingOnY(() =>
{
Layout("Skeleton");ImGui.Text($" : {model.Skeleton.UnrealSkeleton.Name}");
Layout("Bones");ImGui.Text($" : x{model.Skeleton.UnrealSkeleton.BoneTree.Length}");
}
else
{
Layout("Two Sided");ImGui.Text($" : {model.TwoSided}");
}
Layout("Sockets");ImGui.Text($" : x{model.Sockets.Length}");
Layout("Entity");ImGui.Text($" : ({model.Type}) {model.Name}");
Layout("Guid");ImGui.Text($" : {s.Renderer.Options.SelectedModel.ToString(EGuidFormats.UniqueObjectGuid)}");
if (model.HasSkeleton)
{
Layout("Skeleton");ImGui.Text($" : {model.Skeleton.UnrealSkeleton.Name}");
Layout("Bones");ImGui.Text($" : x{model.Skeleton.UnrealSkeleton.BoneTree.Length}");
}
else
{
Layout("Two Sided");ImGui.Text($" : {model.TwoSided}");
}
Layout("Sockets");ImGui.Text($" : x{model.Sockets.Length}");
ImGui.EndTable();
ImGui.EndTable();
});
}
if (ImGui.BeginTabBar("tabbar_details", ImGuiTabBarFlags.None))
{
@ -609,36 +615,39 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.CollapsingHeader("Properties"))
{
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.TreeNode("Base"))
NoFramePaddingOnY(() =>
{
material.ImGuiBaseProperties("base");
ImGui.TreePop();
}
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.TreeNode("Base"))
{
material.ImGuiBaseProperties("base");
ImGui.TreePop();
}
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.TreeNode("Scalars"))
{
material.ImGuiDictionaries("scalars", material.Parameters.Scalars, true);
ImGui.TreePop();
}
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.TreeNode("Switchs"))
{
material.ImGuiDictionaries("switchs", material.Parameters.Switchs, true);
ImGui.TreePop();
}
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.TreeNode("Colors"))
{
material.ImGuiColors(material.Parameters.Colors);
ImGui.TreePop();
}
if (ImGui.TreeNode("All Textures"))
{
material.ImGuiDictionaries("textures", material.Parameters.Textures);
ImGui.TreePop();
}
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.TreeNode("Scalars"))
{
material.ImGuiDictionaries("scalars", material.Parameters.Scalars, true);
ImGui.TreePop();
}
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.TreeNode("Switchs"))
{
material.ImGuiDictionaries("switchs", material.Parameters.Switchs, true);
ImGui.TreePop();
}
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.TreeNode("Colors"))
{
material.ImGuiColors(material.Parameters.Colors);
ImGui.TreePop();
}
if (ImGui.TreeNode("All Textures"))
{
material.ImGuiDictionaries("textures", material.Parameters.Textures);
ImGui.TreePop();
}
});
}
}
@ -772,6 +781,16 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
}, styled);
}
private void AnimationWindow(string name, Renderer renderer, Action<Dictionary<string, Texture>, Skeleton> content, bool styled = true)
{
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);
}
private void PopStyleCompact() => ImGui.PopStyleVar(2);
private void PushStyleCompact()
{
@ -779,6 +798,13 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
ImGui.PushStyleVar(ImGuiStyleVar.CellPadding, new Vector2(0, 1));
}
public static void NoFramePaddingOnY(Action content)
{
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, new Vector2(8, 0));
content();
ImGui.PopStyleVar();
}
private void NoMeshSelected() => CenteredTextColored(_errorColor, "No Mesh Selected");
private void NoSectionSelected() => CenteredTextColored(_errorColor, "No Section Selected");
private void CenteredTextColored(Vector4 color, string text)
@ -797,6 +823,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
{
ImGui.TableNextRow();
ImGui.TableSetColumnIndex(0);
ImGui.AlignTextToFramePadding();
ImGui.Spacing();ImGui.SameLine();ImGui.Text(name);
if (tooltip) TooltipCopy(name);
ImGui.TableSetColumnIndex(1);