diff --git a/FModel/Resources/default.frag b/FModel/Resources/default.frag index d37cab11..438d1147 100644 --- a/FModel/Resources/default.frag +++ b/FModel/Resources/default.frag @@ -241,7 +241,6 @@ void main() result = mix(result, result * color, m.b); } result = vec3(uParameters.Ao.AmbientOcclusion) * result * m.r; - result += CalcLight(layer, normals, vec3(0.0), vec3(0.25), m.g, false); } vec2 coords = fTexCoords; diff --git a/FModel/Settings/UserSettings.cs b/FModel/Settings/UserSettings.cs index 4b55c496..40b59eec 100644 --- a/FModel/Settings/UserSettings.cs +++ b/FModel/Settings/UserSettings.cs @@ -12,6 +12,7 @@ using CUE4Parse.UE4.Assets.Exports.Material; using FModel.Framework; using FModel.ViewModels; using FModel.ViewModels.ApiEndpoints.Models; +using FModel.Views.Snooper; using Newtonsoft.Json; namespace FModel.Settings @@ -614,6 +615,13 @@ namespace FModel.Settings set => SetProperty(ref _showGrid, value); } + private Camera.WorldMode _cameraMode = Camera.WorldMode.Arcball; + public Camera.WorldMode CameraMode + { + get => _cameraMode; + set => SetProperty(ref _cameraMode, value); + } + private bool _previewStaticMeshes = true; public bool PreviewStaticMeshes { diff --git a/FModel/Views/Snooper/Camera.cs b/FModel/Views/Snooper/Camera.cs index 2672d2af..520c24a6 100644 --- a/FModel/Views/Snooper/Camera.cs +++ b/FModel/Views/Snooper/Camera.cs @@ -1,21 +1,32 @@ using System; using System.Numerics; +using CUE4Parse.UE4.Objects.Core.Math; +using FModel.Settings; using ImGuiNET; +using OpenTK.Windowing.GraphicsLibraryFramework; namespace FModel.Views.Snooper; public class Camera { + public enum WorldMode + { + FlyCam, + Arcball + } + public Vector3 Position; public Vector3 Direction; + public Vector3 Focus => Position - Direction; public Vector3 Up = Vector3.UnitY; + public WorldMode Mode = UserSettings.Default.CameraMode; public float Yaw = -90f; public float Pitch = 0f; public float Zoom = 60f; public float Speed = 1f; - public float Near = 0.01f; public float Far = 100f; + public float Near => 0.01f; public float AspectRatio = 16f / 9f; public Camera() @@ -26,51 +37,173 @@ public class Camera InitDirection(); } - public Camera(Vector3 position, Vector3 direction, float near, float far, float speed) + public Camera(FBox box, float far) + { + Far = far; + Teleport(FVector.ZeroVector, box, true); + } + + public Camera(Vector3 position, Vector3 direction, float far, float speed) { Position = position; Direction = direction; - Near = near; Far = far; Speed = speed; InitDirection(); } + public void Teleport(FVector instancePos, FBox box, bool updateSpeed = false) + { + box.GetCenterAndExtents(out var center, out var extents); + center = center.ToMapVector(); + center += instancePos; + var distance = extents.AbsMax(); + + Position = new Vector3(instancePos.X, center.Y, instancePos.Z + distance * 2); + Direction = new Vector3(center.X, center.Y, center.Z); + if (updateSpeed) Speed = distance; + + InitDirection(); + } + private void InitDirection() { // trigonometric math to calculate the cam's yaw/pitch based on position and direction to look var yaw = MathF.Atan((-Position.X - Direction.X) / (Position.Z - Direction.Z)); var pitch = MathF.Atan((Position.Y - Direction.Y) / (Position.Z - Direction.Z)); - ModifyDirection(Helper.RadiansToDegrees(yaw), Helper.RadiansToDegrees(pitch)); + Modify(Helper.RadiansToDegrees(yaw), Helper.RadiansToDegrees(pitch)); } - public void ModifyZoom(float zoomAmount) + public void Modify(Vector2 mouseDelta) + { + var lookSensitivity = Mode switch + { + WorldMode.FlyCam => 0.1f, + WorldMode.Arcball => 0.01f, + _ => throw new ArgumentOutOfRangeException() + }; + mouseDelta *= lookSensitivity; + Modify(mouseDelta.X, mouseDelta.Y); + } + private void Modify(float xOffset, float yOffset) + { + switch (Mode) + { + case WorldMode.FlyCam: + { + Yaw += xOffset; + Pitch -= yOffset; + Pitch = Math.Clamp(Pitch, -89f, 89f); + + var direction = Vector3.Zero; + var yaw = Helper.DegreesToRadians(Yaw); + var pitch = Helper.DegreesToRadians(Pitch); + direction.X = MathF.Cos(yaw) * MathF.Cos(pitch); + direction.Y = MathF.Sin(pitch); + direction.Z = MathF.Sin(yaw) * MathF.Cos(pitch); + Direction = Vector3.Normalize(direction); + break; + } + case WorldMode.Arcball: + { + var up = -Up; + var rotationX = Matrix4x4.CreateFromAxisAngle(up, xOffset); + Position = Vector3.Transform(Focus, rotationX) + Direction; + + var right = Vector3.Normalize(Vector3.Cross(up, Focus)); + var rotationY = Matrix4x4.CreateFromAxisAngle(right, yOffset); + Position = Vector3.Transform(Focus, rotationY) + Direction; + break; + } + default: + throw new ArgumentOutOfRangeException(); + } + } + + public void Modify(KeyboardState keyboard, float time) + { + var multiplier = keyboard.IsKeyDown(Keys.LeftShift) ? 2f : 1f; + var moveSpeed = Speed * multiplier * time; + + var focus = Mode switch + { + WorldMode.FlyCam => Direction, + WorldMode.Arcball => -Focus, + _ => throw new ArgumentOutOfRangeException() + }; + + if (keyboard.IsKeyDown(Keys.W)) + Position += moveSpeed * focus; + if (keyboard.IsKeyDown(Keys.S)) + Position -= moveSpeed * focus; + + switch (Mode) + { + case WorldMode.FlyCam: + { + if (keyboard.IsKeyDown(Keys.A)) + Position -= Vector3.Normalize(Vector3.Cross(focus, Up)) * moveSpeed; + if (keyboard.IsKeyDown(Keys.D)) + Position += Vector3.Normalize(Vector3.Cross(focus, Up)) * moveSpeed; + if (keyboard.IsKeyDown(Keys.E)) + Position += moveSpeed * Up; + if (keyboard.IsKeyDown(Keys.Q)) + Position -= moveSpeed * Up; + break; + } + case WorldMode.Arcball: + { + if (keyboard.IsKeyDown(Keys.A)) + { + var d = Vector3.Normalize(Vector3.Cross(focus, Up)) * moveSpeed; + Position -= d; + Direction -= d; + } + if (keyboard.IsKeyDown(Keys.D)) + { + var d = Vector3.Normalize(Vector3.Cross(focus, Up)) * moveSpeed; + Position += d; + Direction += d; + } + if (keyboard.IsKeyDown(Keys.E)) + { + var d = moveSpeed * Up; + Position += d; + Direction += d; + } + if (keyboard.IsKeyDown(Keys.Q)) + { + var d = moveSpeed * Up; + Position -= d; + Direction -= d; + } + break; + } + default: + throw new ArgumentOutOfRangeException(); + } + + if (keyboard.IsKeyDown(Keys.X)) + ModifyZoom(-.5f); + if (keyboard.IsKeyDown(Keys.C)) + ModifyZoom(+.5f); + } + + private void ModifyZoom(float zoomAmount) { //We don't want to be able to zoom in too close or too far away so clamp to these values Zoom = Math.Clamp(Zoom - zoomAmount, 1.0f, 89f); } - public void ModifyDirection(float xOffset, float yOffset) - { - Yaw += xOffset; - Pitch -= yOffset; - - //We don't want to be able to look behind us by going over our head or under our feet so make sure it stays within these bounds - Pitch = Math.Clamp(Pitch, -89f, 89f); - - var direction = Vector3.Zero; - var yaw = Helper.DegreesToRadians(Yaw); - var pitch = Helper.DegreesToRadians(Pitch); - direction.X = MathF.Cos(yaw) * MathF.Cos(pitch); - direction.Y = MathF.Sin(pitch); - direction.Z = MathF.Sin(yaw) * MathF.Cos(pitch); - Direction = Vector3.Normalize(direction); - } - public Matrix4x4 GetViewMatrix() { - return Matrix4x4.CreateLookAt(Position, Position + Direction, Up); + return Mode switch + { + WorldMode.FlyCam => Matrix4x4.CreateLookAt(Position, Position + Direction, Up), + WorldMode.Arcball => Matrix4x4.CreateLookAt(Position, Direction, Up), + _ => throw new ArgumentOutOfRangeException() + }; } public Matrix4x4 GetProjectionMatrix() @@ -84,13 +217,18 @@ public class Camera private const ImGuiSliderFlags _clamp = ImGuiSliderFlags.AlwaysClamp; public void ImGuiCamera() { - ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, new System.Numerics.Vector2(8, 3)); - ImGui.PushStyleVar(ImGuiStyleVar.CellPadding, new System.Numerics.Vector2(0, 1)); + ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, new Vector2(8, 3)); + ImGui.PushStyleVar(ImGuiStyleVar.CellPadding, new Vector2(0, 1)); if (ImGui.BeginTable("camera_editor", 2)) { - SnimGui.Layout("Speed");ImGui.PushID(1); + SnimGui.Layout("Mode"); + ImGui.PushID(1);var m = (int) Mode; + ImGui.Combo("world_mode", ref m, "Fly Cam\0Arcball\0"); + Mode = (WorldMode) m;ImGui.PopID(); + + SnimGui.Layout("Speed");ImGui.PushID(2); ImGui.DragFloat("", ref Speed, _step, _zero, _infinite, "%.2f m/s", _clamp); - ImGui.PopID();SnimGui.Layout("Far Plane");ImGui.PushID(2); + ImGui.PopID();SnimGui.Layout("Far Plane");ImGui.PushID(3); ImGui.DragFloat("", ref Far, 0.1f, 0.1f, Far * 2f, "%.2f m", _clamp); ImGui.PopID(); diff --git a/FModel/Views/Snooper/Models/Model.cs b/FModel/Views/Snooper/Models/Model.cs index 0da18c58..f4e57576 100644 --- a/FModel/Views/Snooper/Models/Model.cs +++ b/FModel/Views/Snooper/Models/Model.cs @@ -11,6 +11,7 @@ using CUE4Parse.UE4.Assets.Exports; using CUE4Parse.UE4.Assets.Exports.Material; using CUE4Parse.UE4.Assets.Exports.SkeletalMesh; using CUE4Parse.UE4.Assets.Exports.StaticMesh; +using CUE4Parse.UE4.Objects.Core.Math; using FModel.Extensions; using FModel.Services; using FModel.Settings; @@ -41,6 +42,7 @@ public class Model : IDisposable public readonly bool HasVertexColors; public readonly bool HasMorphTargets; public readonly int UvCount; + public readonly FBox Box; public uint[] Indices; public float[] Vertices; public Section[] Sections; @@ -68,12 +70,20 @@ public class Model : IDisposable Name = Path.SubstringAfterLast('/').SubstringBefore('.'); Type = export.ExportType; UvCount = 1; + Box = new FBox(new FVector(-.65f), new FVector(.65f)); Transforms = new List(); } public Model(UStaticMesh export, CStaticMesh staticMesh) : this(export, staticMesh, Transform.Identity) {} - public Model(UStaticMesh export, CStaticMesh staticMesh, Transform transform) : this(export, export.Materials, null, staticMesh.LODs.Count, staticMesh.LODs[0], staticMesh.LODs[0].Verts, transform) {} - private Model(USkeletalMesh export, CSkeletalMesh skeletalMesh, Transform transform) : this(export, export.Materials, export.Skeleton, skeletalMesh.LODs.Count, skeletalMesh.LODs[0], skeletalMesh.LODs[0].Verts, transform) {} + + public Model(UStaticMesh export, CStaticMesh staticMesh, Transform transform) : this(export, export.Materials, null, staticMesh.LODs.Count, staticMesh.LODs[0], staticMesh.LODs[0].Verts, transform) + { + Box = staticMesh.BoundingBox *= Constants.SCALE_DOWN_RATIO; + } + private Model(USkeletalMesh export, CSkeletalMesh skeletalMesh, Transform transform) : this(export, export.Materials, export.Skeleton, skeletalMesh.LODs.Count, skeletalMesh.LODs[0], skeletalMesh.LODs[0].Verts, transform) + { + Box = skeletalMesh.BoundingBox *= Constants.SCALE_DOWN_RATIO; + } public Model(USkeletalMesh export, CSkeletalMesh skeletalMesh) : this(export, skeletalMesh, Transform.Identity) { var morphTargets = export.MorphTargets; diff --git a/FModel/Views/Snooper/Renderer.cs b/FModel/Views/Snooper/Renderer.cs index 417a5e79..044bce80 100644 --- a/FModel/Views/Snooper/Renderer.cs +++ b/FModel/Views/Snooper/Renderer.cs @@ -143,11 +143,7 @@ public class Renderer : IDisposable private Camera SetupCamera(FBox box) { var far = box.Max.AbsMax(); - var center = box.GetCenter(); - return new Camera( - new Vector3(0f, center.Z, box.Max.Y * 3), - new Vector3(center.X, center.Z, center.Y), - 0.01f, far * 50f, far / 1.5f); + return new Camera(box, far * 50f); } private Camera LoadStaticMesh(UStaticMesh original) @@ -165,7 +161,7 @@ public class Renderer : IDisposable Options.Models[guid] = new Model(original, mesh); Options.SelectModel(guid); - return SetupCamera(mesh.BoundingBox *= Constants.SCALE_DOWN_RATIO); + return SetupCamera(Options.Models[guid].Box); } private Camera LoadSkeletalMesh(USkeletalMesh original) @@ -175,7 +171,7 @@ public class Renderer : IDisposable Options.Models[guid] = new Model(original, mesh); Options.SelectModel(guid); - return SetupCamera(mesh.BoundingBox *= Constants.SCALE_DOWN_RATIO); + return SetupCamera(Options.Models[guid].Box); } private Camera LoadMaterialInstance(UMaterialInstance original) @@ -185,12 +181,12 @@ public class Renderer : IDisposable Options.Models[guid] = new Cube(original); Options.SelectModel(guid); - return SetupCamera(new FBox(new FVector(-.65f), new FVector(.65f))); + return SetupCamera(Options.Models[guid].Box); } private Camera LoadWorld(CancellationToken cancellationToken, UWorld original, Transform transform) { - var cam = new Camera(new Vector3(0f, 5f, 5f), Vector3.Zero, 0.01f, 1000f, 5f); + var cam = new Camera(new Vector3(0f, 5f, 5f), Vector3.Zero, 1000f, 5f); if (original.PersistentLevel.Load() is not { } persistentLevel) return cam; @@ -224,7 +220,7 @@ public class Renderer : IDisposable cam = new Camera( new Vector3(position.X, position.Y, position.Z), new Vector3(direction.X, direction.Y, direction.Z), - 0.01f, far * 25f, Math.Max(5f, far / 10f)); + far * 25f, Math.Max(5f, far / 10f)); } private void WorldLight(UObject actor) diff --git a/FModel/Views/Snooper/SnimGui.cs b/FModel/Views/Snooper/SnimGui.cs index 3ab7af6c..b8df4f06 100644 --- a/FModel/Views/Snooper/SnimGui.cs +++ b/FModel/Views/Snooper/SnimGui.cs @@ -351,7 +351,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio if (ImGui.Selectable("Teleport To")) { var instancePos = model.Transforms[model.SelectedInstance].Position; - s.Camera.Position = new Vector3(instancePos.X, instancePos.Y, instancePos.Z); + s.Camera.Teleport(instancePos, model.Box); } if (ImGui.Selectable("Delete")) s.Renderer.Options.Models.Remove(guid); @@ -672,12 +672,9 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio } } - const float lookSensitivity = 0.1f; - if (ImGui.IsMouseDragging(ImGuiMouseButton.Left, lookSensitivity) && _viewportFocus) + if (ImGui.IsMouseDragging(ImGuiMouseButton.Left) && _viewportFocus) { - var io = ImGui.GetIO(); - var delta = io.MouseDelta * lookSensitivity; - s.Camera.ModifyDirection(delta.X, delta.Y); + s.Camera.Modify(ImGui.GetIO().MouseDelta); } // if left button up and mouse was in viewport diff --git a/FModel/Views/Snooper/Snooper.cs b/FModel/Views/Snooper/Snooper.cs index 271513ed..a59860ab 100644 --- a/FModel/Views/Snooper/Snooper.cs +++ b/FModel/Views/Snooper/Snooper.cs @@ -1,10 +1,10 @@ using System; using System.ComponentModel; -using System.Numerics; using System.Runtime.InteropServices; using System.Threading; using System.Windows; using CUE4Parse.UE4.Assets.Exports; +using FModel.Settings; using FModel.Views.Snooper.Buffers; using ImGuiNET; using OpenTK.Graphics.OpenGL4; @@ -61,6 +61,7 @@ public class Snooper : GameWindow Renderer.Save(); } + UserSettings.Default.CameraMode = Camera.Mode; GLFW.SetWindowShouldClose(WindowPtr, value); // start / stop game loop IsVisible = !value; } @@ -82,7 +83,7 @@ public class Snooper : GameWindow private unsafe void LoadWindowIcon() { - var info = Application.GetResourceStream(new Uri($"/FModel;component/Resources/engine.png", UriKind.Relative)); + var info = Application.GetResourceStream(new Uri("/FModel;component/Resources/engine.png", UriKind.Relative)); using var img = SixLabors.ImageSharp.Image.Load(info.Stream); var memoryGroup = img.GetPixelMemoryGroup(); Memory array = new byte[memoryGroup.TotalLength * sizeof(Rgba32)]; @@ -162,24 +163,7 @@ public class Snooper : GameWindow if (!IsVisible || ImGui.GetIO().WantTextInput) return; - var multiplier = KeyboardState.IsKeyDown(Keys.LeftShift) ? 2f : 1f; - var moveSpeed = Camera.Speed * multiplier * (float) e.Time; - if (KeyboardState.IsKeyDown(Keys.W)) - Camera.Position += moveSpeed * Camera.Direction; - if (KeyboardState.IsKeyDown(Keys.S)) - Camera.Position -= moveSpeed * Camera.Direction; - if (KeyboardState.IsKeyDown(Keys.A)) - Camera.Position -= Vector3.Normalize(Vector3.Cross(Camera.Direction, Camera.Up)) * moveSpeed; - if (KeyboardState.IsKeyDown(Keys.D)) - Camera.Position += Vector3.Normalize(Vector3.Cross(Camera.Direction, Camera.Up)) * moveSpeed; - if (KeyboardState.IsKeyDown(Keys.E)) - Camera.Position += moveSpeed * Camera.Up; - if (KeyboardState.IsKeyDown(Keys.Q)) - Camera.Position -= moveSpeed * Camera.Up; - if (KeyboardState.IsKeyDown(Keys.X)) - Camera.ModifyZoom(-.5f); - if (KeyboardState.IsKeyDown(Keys.C)) - Camera.ModifyZoom(+.5f); + Camera.Modify(KeyboardState, (float) e.Time); if (KeyboardState.IsKeyPressed(Keys.H)) WindowShouldClose(true, false);