From 08381703f79377a5bc48fc0745620f8815695b05 Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Sat, 13 Jun 2026 05:21:24 +0300 Subject: [PATCH] Add WorldCenter property to CameraState for effective camera positioning Enhanced the CameraState struct with a new WorldCenter property that calculates the effective position of the camera after bounds-clamping. This property is intended to be used for zoom-to-cursor functionality, ensuring that the repositioning aligns with what is rendered. Added unit tests to verify that WorldCenter reflects the unclamped camera position and correctly accounts for bounds clamping, distinguishing it from the raw camera position. Tests: WorldCenter_EqualsUnclampedCameraPosition, WorldCenter_ReflectsBoundsClamp_UnlikeRawPosition. --- src/MrGameEng.Graphics/CameraMath.cs | 9 ++++++++ .../CameraMathTests.cs | 23 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/MrGameEng.Graphics/CameraMath.cs b/src/MrGameEng.Graphics/CameraMath.cs index 9804ec4..e5b8f8a 100644 --- a/src/MrGameEng.Graphics/CameraMath.cs +++ b/src/MrGameEng.Graphics/CameraMath.cs @@ -33,6 +33,15 @@ public readonly struct CameraState /// Physical-screen to virtual-pixel mapping. public required ViewportMapping Mapping { get; init; } + /// + /// World point at the centre of the virtual screen — the camera's effective position + /// after bounds-clamping, i.e. what the view is actually built around. Prefer this over the raw + /// when anchoring zoom-to-cursor, so the reposition matches what is + /// rendered even while the camera is clamped against . + /// + public Vector2 WorldCenter => + Vector2.Transform(new Vector2(VirtualWidth / 2f, VirtualHeight / 2f), InverseView); + /// Converts a physical screen point to world coordinates. public Vector2 ScreenToWorld(Vector2 screen) { diff --git a/tests/MrGameEng.Graphics.Tests/CameraMathTests.cs b/tests/MrGameEng.Graphics.Tests/CameraMathTests.cs index d6cc6e1..f2459ad 100644 --- a/tests/MrGameEng.Graphics.Tests/CameraMathTests.cs +++ b/tests/MrGameEng.Graphics.Tests/CameraMathTests.cs @@ -83,6 +83,29 @@ public class CameraMathTests AssertVector(new Vector2(0f, 200f), state.ScreenToWorld(Vector2.Zero)); } + [Fact] + public void WorldCenter_EqualsUnclampedCameraPosition() + { + var camera = new Camera(new Vector2(640f, 360f), zoom: 2f); + + var state = CameraMath.Compute(camera, 1280, 720, ViewportMapping.Identity); + + AssertVector(camera.Position, state.WorldCenter); + } + + [Fact] + public void WorldCenter_ReflectsBoundsClamp_UnlikeRawPosition() + { + var bounds = new RectF(0f, 0f, 2000f, 1000f); + var camera = new Camera(new Vector2(-500f, 500f), bounds: bounds); + + var state = CameraMath.Compute(camera, 800, 600, ViewportMapping.Identity); + + // Raw position is (-500, 500); only X clamps (to half-width 400 from the left world edge), + // Y (500) is already inside [300, 700]. The effective centre the view is built around is (400, 500). + AssertVector(new Vector2(400f, 500f), state.WorldCenter); + } + [Fact] public void Mapping_CentersVirtualResolutionInWiderWindow() {