From 91de529c79fa8bfa6cf3188baa4804c1c5b75103 Mon Sep 17 00:00:00 2001 From: Andrew Opalach Date: Sat, 30 Aug 2025 20:12:07 -0400 Subject: Improve special casing, increase precision of look - New defaults. Signed-off-by: Andrew Opalach --- Config.cs | 12 +-- Plugin.cs | 307 +++++++++++++++++++++++++++++++++---------------------------- README.txt | 3 +- 3 files changed, 175 insertions(+), 147 deletions(-) diff --git a/Config.cs b/Config.cs index 10d7a39..59eb76a 100755 --- a/Config.cs +++ b/Config.cs @@ -17,9 +17,9 @@ namespace MHWNewCamera public const float DEFAULT_SPEED = 3.00f; public const float DEFAULT_ZOOM_SPEED = 1.0f; public const float DEFAULT_SPEED_MODIFIER = 0.25f; - public const float DEFAULT_SENSITIVITY = 2.0f; - public const int DEFAULT_DEADZONE = 4500; - public const float DEFAULT_PITCH_LIMIT = 88.25f; + public const float DEFAULT_SENSITIVITY = 1.95f; + public const int DEFAULT_DEADZONE = 4750; + public const double DEFAULT_PITCH_LIMIT = 88.25; public Settings() { @@ -36,16 +36,16 @@ namespace MHWNewCamera public float CameraSpeedModifier { get; set; } public float CameraSensitivity { get; set; } public int StickDeadzone { get; set; } - public float CameraPitchLimit { get; set; } + public double CameraPitchLimit { get; set; } } public struct Preset { - public float FOV { get; set; } + public double FOV { get; set; } public float Forward { get; set; } public float Left { get; set; } public float Up { get; set; } - public float Roll { get; set; } + public double Roll { get; set; } } public String Name => "MHWNewCamera"; diff --git a/Plugin.cs b/Plugin.cs index 19aafa4..e2b1c40 100755 --- a/Plugin.cs +++ b/Plugin.cs @@ -17,13 +17,19 @@ using System.Diagnostics; // @TODO: // - Ability to disable more inputs in freecam. /* +PadDown = 0x198, PadOld = 0x19C, PadTrg = 0x1A0, PadRel = 0x1A4, PadChg = 0x1A8 uint PadDown = MemoryUtil.Read(sMhSteamController.Instance + 0x198); -uint PadOld = MemoryUtil.Read(sMhSteamController.Instance + 0x19C); -uint PadTrg = MemoryUtil.Read(sMhSteamController.Instance + 0x1A0); -uint PadRel = MemoryUtil.Read(sMhSteamController.Instance + 0x1A4); -uint PadChg = MemoryUtil.Read(sMhSteamController.Instance + 0x1A8); -MemoryUtil.WriteBytes(sMhSteamController.Instance + 0x198, [0x0, 0x0, 0x0, 0x0]); +MemoryUtil.WriteBytes(sMhSteamController.Instance + 0x198, BitConverter.GetBytes(PadRel)); */ +// Known Issues: +// - Leaving dialoge when vp.Camera.Up is slightly off will not apply camera offsets when it should. +// - Test Case: Go up to the handler with the camera left of the player. Talk to her and click Post a New Quest. +// Then after the open book animation, leave the dialoge and the camera should jump. +// - Getting up from cart doesn't apply offsets when it should. +// - Greatly increasing FOV to mitigate culling when camera is rolled causes issues. +// - HUD overlays misplaced. +// - Other various hard to explain graphical errors. +// - Offsetting the camera too much can easily break culling if the camera is behind the ground/wall/ceiling. namespace MHWNewCamera { @@ -32,7 +38,7 @@ namespace MHWNewCamera public string Name => "MHW New Camera"; public string Author => "Akon City Software"; - private const float DEFAULT_FOV = 60.0f; + private const double DEFAULT_FOV = 60.0; private bool modDisabled = false; private bool modToggleDisabled = false; @@ -42,20 +48,20 @@ namespace MHWNewCamera private float cameraSpeedModifier; private float cameraSensitivity; private int stickDeadzone; - private float cameraPitchLimit; + private double cameraPitchLimit; - private float cameraFov = DEFAULT_FOV; + private double cameraFov = DEFAULT_FOV; private float cameraForward = 0.0f; private float cameraLeft = 0.0f; private float cameraXOffset = 0.0f; private float cameraYOffset = 0.0f; private float cameraZOffset = 0.0f; - private float cameraPitch = 0.0f; - private float cameraYaw = 0.0f; - private float cameraRoll = 0.0f; + private double cameraPitch = 0.0; + private double cameraYaw = 0.0; + private double cameraRoll = 0.0; - private float? preZoomFov = null; - private float? preZoomRoll = null; + private double? preZoomFov = null; + private double? preZoomRoll = null; private bool freeCamera = false; private bool toggleFreeCamera = false; @@ -67,12 +73,11 @@ namespace MHWNewCamera private Camera? targetCamera = null; private Player? lastPlayer = null; private float previousFov = -1.0f; - private bool decreasingFov = false; private int previousCameraAnimState = 0; private bool changeAfterMapOpened = true; private bool changeAfterGetUp = true; private bool changeAfterInsideTent = true; - private bool resetFovInView = false; + private float? resetFovInView = null; private delegate void CalculateCameraDelegate(nint unknownPtr); private Hook? calculateCameraHook; @@ -227,11 +232,16 @@ namespace MHWNewCamera return fov == 20.000038f || fov == 20.017189f || fov == 20.00004f; } - // Observed when seeing a seasonal gathering hub for the first time, leaving your tent, - // and getting up from carting. private bool assumeOnRails(Viewport vp, Camera camera) { - return vp.Camera != null && vp.Camera != camera && vp.Camera.FarClip == 4000000 && vp.Camera.Fix; + // 1. Seasonal gathering hub cutscene. + // 2. Zoom into quest board or the handler's book. + // 3. Leaving tent. + // 4. Getting up from carting. + // 5. Leaving dialoge with NPC. + // Checking Up.Y != 1.0f is an attempt to include only #2. + return vp.Camera != null && vp.Camera.Instance != camera.Instance && vp.Camera.FarClip == 4000000 && + vp.Camera.Fix && vp.Camera.Up.Y != 1.0f; } private bool assumeInOpenMap(float fov) @@ -269,7 +279,7 @@ namespace MHWNewCamera // Assume that FOV will be greater than or equal to 51.0 before sitting at the canteen. // Also, assume that if FOV is equal to 50.0 in a different situation, previousFov // will be less than 51.0 because it would have been lowering last frame. - // This is easily the most fragile check in this code. + // This is easily the most fragile check here. if (previousFov >= 51.0f) { if (Input.IsPressed(Button.Cross) || (Input.IsDown(Button.Triangle) || Input.IsReleased(Button.Triangle))) @@ -287,7 +297,7 @@ namespace MHWNewCamera private bool assumeInEnterTent(Camera camera, int animState) { - if (camera.Up[1] != 1.0f && animState == 5) + if (camera.Up.Y != 1.0f && animState == 5) { if (!changeAfterInsideTent) { @@ -310,42 +320,44 @@ namespace MHWNewCamera return false; } - private void setPerspective(Camera camera, Vector3 pos, Quaternion forward) - { - Quaternion left = forward * Quaternion.CreateFromRotationMatrix(Matrix4x4.CreateRotationY(Single.DegreesToRadians(180.0f))); - pos.X += forward.X * cameraForward; - pos.Y += forward.Y * cameraForward; - pos.Z += forward.Z * cameraForward; - pos.X -= left.X * cameraLeft; - pos.Z -= left.Z * cameraLeft; - pos.X += cameraXOffset; - pos.Y += cameraYOffset; - pos.Z += cameraZOffset; - camera.Position = pos; - camera.FieldOfView = cameraFov; - } - - private void adjustFovForCulling(Viewport vp, Camera camera) + private void maybeSetFovForCulling(Viewport vp, Camera camera) { /* I don't think this would be correct. As of now I'm pretty sure roll is only * considered after this point, in CalculateViewHook. - if (cameraRoll == 90.0f || cameraRoll == 270.0f) + if (cameraRoll == 90.0 || cameraRoll == 270.0) { camera.AspectRatio = vp.Region.Height / (float)vp.Region.Width; } */ - if (cameraRoll != 0.0f && camera.FieldOfView < 130.0f) + if (cameraRoll != 0.0 && camera.FieldOfView < 130.0f) { - // The closer the FOV is to 180, the more odd behavior - // occurs. 130 seems like a OK default for when the camera is rolled. + // The closer the FOV is to 180, the more odd behavior occurs. + // 130 seems like a OK default for when the camera is rolled. + // This will break HUD overlays like "!" on top of NPCs and the tent exit cutscene. + resetFovInView = camera.FieldOfView; camera.FieldOfView = 130.0f; } + /* else if (camera.FieldOfView < 60.0f) { - // This would break HUD overlays like "!" on top of NPCs and the tent exit cutscene. - //camera.FieldOfView = 60.0f; + resetFovInView = camera.FieldOfView; + camera.FieldOfView = 60.0f; } - resetFovInView = true; + */ + } + + private void setPerspective(Camera camera, Vector3 pos, Quaternion forward) + { + Quaternion left = forward * Quaternion.CreateFromRotationMatrix(Matrix4x4.CreateRotationY(Single.DegreesToRadians(180.0f))); + pos.X += forward.X * cameraForward; + pos.Y += forward.Y * cameraForward; + pos.Z += forward.Z * cameraForward; + pos.X -= left.X * cameraLeft; + pos.Z -= left.Z * cameraLeft; + pos.X += cameraXOffset; + pos.Y += cameraYOffset; + pos.Z += cameraZOffset; + camera.Position = pos; } private static Quaternion getForward(Vector3 pos, Vector3 target) @@ -353,59 +365,60 @@ namespace MHWNewCamera return new Quaternion(target.X - pos.X, target.Y - pos.Y, target.Z - pos.Z, 0.0f); } + // If freeCamera (camera.Move = false), this hook won't run. private void CalculateCameraHook(nint unknownPtr) { - calculateCameraHook!.Original(unknownPtr); - - if (modDisabled) return; + if (modDisabled) + { + calculateCameraHook!.Original(unknownPtr); + return; + } Viewport vp = CameraSystem.MainViewport; - Camera? camera = vp.Camera; + Camera? camera = offsetPerspective ? targetCamera : vp.Camera; + if (camera == null) return; - if (offsetPerspective && !freeCamera) + bool applyPerspective = offsetPerspective; + if (offsetPerspective) { - camera = targetCamera; - if (camera == null) return; - - decreasingFov = previousFov >= 0.0f && previousFov > camera.FieldOfView; int cameraAnimState = MemoryUtil.Read(camera.Instance + 0x240); - - bool applyPerspective = camera.Fix && camera.Move; - // This can cause a jump if leaving tent looking down (camera above character) - // then looking up (decreasingFov = true). - //applyPerspective &= !(assumeOnRails(vp, camera) && decreasingFov); + applyPerspective = camera.Fix && camera.Move; + applyPerspective &= !assumeOnRails(vp, camera); applyPerspective &= !assumeInQuestBoard(camera.FieldOfView); applyPerspective &= !assumeInOpenMap(camera.FieldOfView); applyPerspective &= !assumeInCanteen(camera.FieldOfView, previousFov); applyPerspective &= !assumeInEnterTent(camera, cameraAnimState); - - previousFov = camera.FieldOfView; previousCameraAnimState = cameraAnimState; - - if (applyPerspective) - { - Quaternion forward = Quaternion.Normalize(getForward(camera.Position, camera.Target)); - setPerspective(camera, camera.Position, forward); - adjustFovForCulling(vp, camera); - } } - else if (camera != null) + + previousFov = camera.FieldOfView; + + calculateCameraHook!.Original(unknownPtr); + + if (applyPerspective) { - // Track this just for the debug display. - previousFov = camera.FieldOfView; + Quaternion forward = Quaternion.Normalize(getForward(camera.Position, camera.Target)); + setPerspective(camera, camera.Position, forward); + camera.FieldOfView = (float)((cameraFov * previousFov) / 60.0); } - if (camera != null && toggleFreeCamera && !freeCamera) + if (toggleFreeCamera && !freeCamera) { freeCamera = true; camera.Move = false; preZoomFov = cameraFov; preZoomRoll = cameraRoll; - // Camera position could have changed. + if (applyPerspective) + { + cameraFov = camera.FieldOfView; + } + // Camera position might have changed. Quaternion forward = Quaternion.Normalize(getForward(camera.Position, camera.Target)); - cameraPitch = Single.RadiansToDegrees((float)Math.Asin(forward.Y)); - cameraYaw = Single.RadiansToDegrees((float)Math.Atan2(forward.Z, forward.X)); + cameraPitch = Double.RadiansToDegrees(Math.Asin(forward.Y)); + cameraYaw = Double.RadiansToDegrees(Math.Atan2(forward.Z, forward.X)); } + + if (applyPerspective) maybeSetFovForCulling(vp, camera); } private void CalculateViewHook(nint unknownPtr) @@ -413,28 +426,26 @@ namespace MHWNewCamera if (modDisabled) { calculateViewHook!.Original(unknownPtr); + return; } - else + + Viewport vp = CameraSystem.MainViewport; + bool notFreecam = offsetPerspective && !freeCamera; + Camera? camera = notFreecam ? targetCamera : vp.Camera; + + bool updateView = freeCamera || offsetPerspective; + if (resetFovInView != null && updateView && camera != null) { - Viewport vp = CameraSystem.MainViewport; - bool updateView = freeCamera || offsetPerspective; - if (resetFovInView) - { - if (updateView) - { - Camera? camera; - if ((camera = (freeCamera ? vp.Camera : targetCamera)) != null) - { - camera.FieldOfView = cameraFov; - } - } - resetFovInView = false; - } - calculateViewHook!.Original(unknownPtr); - if (updateView) - { - vp.ViewMatrix *= Matrix4x4.CreateRotationZ(Single.DegreesToRadians(cameraRoll)); - } + camera.FieldOfView = (float)resetFovInView; + resetFovInView = null; + } + + calculateViewHook!.Original(unknownPtr); + + if (updateView && cameraRoll != 0.0) + { + Matrix4x4 rollRotation = Matrix4x4.CreateRotationZ((float)Double.DegreesToRadians(cameraRoll)); + vp.ViewMatrix *= rollRotation; } } @@ -494,7 +505,7 @@ namespace MHWNewCamera freeCamera = false; if (preZoomRoll != null) { - cameraRoll = (float)preZoomRoll; + cameraRoll = (double)preZoomRoll; preZoomRoll = null; } } @@ -520,7 +531,7 @@ namespace MHWNewCamera ImGui.Checkbox("Disable Toggle Bind", ref modToggleDisabled); if (ImGui.BeginItemTooltip()) { - ImGui.Text("Disable button combination for toggling freecam."); + ImGui.Text("Disable button combination (Hold RStick + Press LT) for toggling free camera."); ImGui.EndTooltip(); } @@ -543,7 +554,11 @@ namespace MHWNewCamera } ImGui.PushID("Perspective"); - ImGui.DragFloat("Field of View", ref cameraFov, 0.4f, 1.0f, 179.0f); + float singleFov = (float)cameraFov; + if (ImGui.DragFloat("Field of View", ref singleFov, 0.4f, 1.0f, 179.0f)) + { + cameraFov = singleFov; + } ImGui.DragFloat("Forward", ref cameraForward, 0.4f); ImGui.DragFloat("Left", ref cameraLeft, 0.4f); ImGui.DragFloat("Up", ref cameraYOffset, 0.4f); @@ -686,6 +701,10 @@ namespace MHWNewCamera if (ImGui.CollapsingHeader("DEBUG")) { ImGui.PushID("Camera"); + if (targetCamera != null) + { + ImGui.Text($"Target Camera: {targetCamera.Instance:x}"); + } ImGui.Text("Camera/Viewport:"); Viewport vp; for (int i = 0; i < 8; i++) @@ -739,17 +758,17 @@ namespace MHWNewCamera if (i == 0) { ImGui.Text("Rotation:"); - float pitch = cameraPitch; - if (ImGui.InputFloat("Pitch", ref pitch, 0.25f)) + double pitch = cameraPitch; + if (ImGui.InputDouble("Pitch", ref pitch, 0.25)) { cameraPitch = pitch; } - float yaw = cameraYaw; - if (ImGui.InputFloat("Yaw", ref yaw, 0.25f)) + double yaw = cameraYaw; + if (ImGui.InputDouble("Yaw", ref yaw, 0.25)) { cameraYaw = yaw; } - float roll = cameraRoll; + float roll = (float)cameraRoll; if (ImGui.DragFloat("Roll", ref roll, 0.25f)) { cameraRoll = roll; @@ -758,7 +777,7 @@ namespace MHWNewCamera preZoomRoll = roll; } } - float pitchLimit = cameraPitchLimit; + float pitchLimit = (float)cameraPitchLimit; if (ImGui.InputFloat("Pitch Limit", ref pitchLimit, 0.0f, 0.0f, null, ImGuiInputTextFlags.EnterReturnsTrue)) { cameraPitchLimit = pitchLimit; @@ -1033,28 +1052,13 @@ namespace MHWNewCamera } } - if (!modToggleDisabled && Input.IsDown(Button.L2) && Input.IsPressed(Button.L1)) + if (!modToggleDisabled && (!Input.IsPressed(Button.R3) && Input.IsDown(Button.R3)) && Input.IsPressed(Button.L2)) { toggleFreeCamera = !toggleFreeCamera; } - if (!toggleFreeCamera && freeCamera) - { - freeCamera = false; - camera.Move = true; - cameraFov = (float)preZoomFov; - preZoomFov = null; - cameraRoll = (float)preZoomRoll; - preZoomRoll = null; - } - if (freeCamera) { - if (!unlockMovementHeld && Input.IsPressed(Button.L1)) - { - unlockMovementToggled = !unlockMovementToggled; - } - if (!unlockMovementHeld && Input.IsPressed(Button.R2)) { unlockMovementHeld = true; @@ -1064,6 +1068,11 @@ namespace MHWNewCamera unlockMovementHeld = false; } + if (unlockMovementHeld && Input.IsPressed(Button.L1)) + { + unlockMovementToggled = !unlockMovementToggled; + } + if (Input.IsDown(Button.R2)) { if (Input.IsDown(Button.L2)) @@ -1087,9 +1096,9 @@ namespace MHWNewCamera { int PadLy = MemoryUtil.Read(sMhSteamController.Instance + 0x1bc); if (Math.Abs(PadLy) < stickDeadzone) PadLy = 0; - float adjustedZoomSpeed = cameraZoomSpeed * (deltaTime * cameraFov); - float Ly = PadLy / (Int16.MaxValue / adjustedZoomSpeed); - cameraFov = Math.Clamp(cameraFov - Ly, 1.0f, 179.0f); + double adjustedZoomSpeed = cameraZoomSpeed * (deltaTime * cameraFov); + double Ly = PadLy / (Int16.MaxValue / adjustedZoomSpeed); + cameraFov = Math.Clamp(cameraFov - Ly, 1.0, 179.0); } else if (!(unlockMovementHeld || unlockMovementToggled)) { @@ -1097,11 +1106,11 @@ namespace MHWNewCamera if (Math.Abs(PadLy) < stickDeadzone) PadLy = 0; int PadLx = MemoryUtil.Read(sMhSteamController.Instance + 0x1b8); if (Math.Abs(PadLx) < stickDeadzone) PadLx = 0; - float adjustedSpeed = cameraSpeed * (deltaTime * 100.0f); - float Lx = PadLx / (Int16.MaxValue / adjustedSpeed); - float Ly = PadLy / (Int16.MaxValue / adjustedSpeed); + double adjustedSpeed = cameraSpeed * (deltaTime * 100.0); + double Lx = PadLx / (Int16.MaxValue / adjustedSpeed); + double Ly = PadLy / (Int16.MaxValue / adjustedSpeed); Quaternion forward = getForward(camera.Position, camera.Target); - float buttonOffset = adjustedSpeed / 2.0f; + double buttonOffset = adjustedSpeed / 2.0; if (Input.IsDown(Button.L2)) { buttonOffset *= cameraSpeedModifier; @@ -1111,11 +1120,11 @@ namespace MHWNewCamera } if (Input.IsDown(Button.Down)) { - camera.Position.Y -= buttonOffset; + camera.Position.Y -= (float)buttonOffset; } if (Input.IsDown(Button.Up)) { - camera.Position.Y += buttonOffset; + camera.Position.Y += (float)buttonOffset; } if (Input.IsDown(Button.Left)) { @@ -1127,26 +1136,44 @@ namespace MHWNewCamera } forward = Quaternion.Normalize(forward); Quaternion left = forward * Quaternion.CreateFromRotationMatrix(Matrix4x4.CreateRotationY(Single.DegreesToRadians(180.0f))); - camera.Position.X += left.X * Lx; - camera.Position.Z += left.Z * Lx; - camera.Position.X += forward.X * Ly; - camera.Position.Y += forward.Y * Ly; - camera.Position.Z += forward.Z * Ly; + camera.Position.X += (float)(left.X * Lx); + camera.Position.Z += (float)(left.Z * Lx); + camera.Position.X += (float)(forward.X * Ly); + camera.Position.Y += (float)(forward.Y * Ly); + camera.Position.Z += (float)(forward.Z * Ly); } int PadRx = MemoryUtil.Read(sMhSteamController.Instance + 0x1b0); if (Math.Abs(PadRx) < stickDeadzone) PadRx = 0; int PadRy = MemoryUtil.Read(sMhSteamController.Instance + 0x1b4); if (Math.Abs(PadRy) < stickDeadzone) PadRy = 0; - float adjustedSensitivity = cameraSensitivity * (deltaTime * cameraFov); - float Rx = PadRx / (Int16.MaxValue / adjustedSensitivity); - float Ry = PadRy / (Int16.MaxValue / adjustedSensitivity); + double adjustedSensitivity = cameraSensitivity * (deltaTime * cameraFov); + double Rx = PadRx / (Int16.MaxValue / adjustedSensitivity); + double Ry = PadRy / (Int16.MaxValue / adjustedSensitivity); cameraYaw += Rx; cameraPitch = Math.Clamp(cameraPitch + Ry, -cameraPitchLimit, cameraPitchLimit); - camera.Target.X = camera.Position.X + (float)(Math.Cos(Single.DegreesToRadians(cameraPitch)) * Math.Cos(Single.DegreesToRadians(cameraYaw))); - camera.Target.Y = camera.Position.Y + (float)(Math.Sin(Single.DegreesToRadians(cameraPitch))); - camera.Target.Z = camera.Position.Z + (float)(Math.Cos(Single.DegreesToRadians(cameraPitch)) * Math.Sin(Single.DegreesToRadians(cameraYaw))); - camera.FieldOfView = cameraFov; - adjustFovForCulling(vp, camera); + // This is the same value the game uses for the player camera. Oddly the in-game view mode + // uses 1.0 like I did here before, which is really bad for precision. + double dist = 700.0 - cameraForward; + camera.Target.X = camera.Position.X + (float)(dist * Math.Cos(Double.DegreesToRadians(cameraPitch)) * Math.Cos(Double.DegreesToRadians(cameraYaw))); + camera.Target.Y = camera.Position.Y + (float)(dist * Math.Sin(Double.DegreesToRadians(cameraPitch))); + camera.Target.Z = camera.Position.Z + (float)(dist * Math.Cos(Double.DegreesToRadians(cameraPitch)) * Math.Sin(Double.DegreesToRadians(cameraYaw))); + camera.FieldOfView = (float)cameraFov; + if (!toggleFreeCamera) + { + freeCamera = false; + camera.Move = true; + if (preZoomFov != null) + { + cameraFov = (double)preZoomFov; + preZoomFov = null; + } + if (preZoomRoll != null) + { + cameraRoll = (double)preZoomRoll; + preZoomRoll = null; + } + } + maybeSetFovForCulling(vp, camera); } } } diff --git a/README.txt b/README.txt index 3233d50..5c681f9 100644 --- a/README.txt +++ b/README.txt @@ -1,3 +1,4 @@ == MHWNewCamera +https://www.nexusmods.com/monsterhunterworld/mods/8300 -// vim: set syntax=asciidoc: +// vim: set ft=asciidoc: -- cgit v1.2.3-101-g0448