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()
{