| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029 |
- using System;
- using Microsoft.Xna.Framework;
- using Microsoft.Xna.Framework.Graphics;
- using MonoGame.Extended.Tests.Fixtures;
- using MonoGame.Extended.ViewportAdapters;
- namespace MonoGame.Extended.Tests;
- [Collection("GraphicsTest")]
- public sealed class OrthographicCameraTests
- {
- private readonly GraphicsTestFixture _graphicsFixture;
- public OrthographicCameraTests(GraphicsTestFixture graphicsTestFixture)
- {
- _graphicsFixture = graphicsTestFixture;
- }
- [Fact]
- public void SetPosition_WorldBoundsDisabled_SetsValueWithoutClamping()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- camera.DisableWorldBounds();
- Vector2 expectedPosition = new Vector2(100, 100);
- camera.Position = expectedPosition;
- Assert.Equal(expectedPosition, camera.Position);
- }
- [Fact]
- public void SetPosition_WorldBoundsEnabled_ClampsToMinimumBounds()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- Viewport viewport = _graphicsFixture.GraphicsDevice.Viewport;
- Rectangle worldBounds = new Rectangle(0, 0, viewport.Width * 2, viewport.Height * 2);
- camera.EnableWorldBounds(worldBounds);
- camera.Position = new Vector2(-100, -100);
- Vector2 expectedPosition = new Vector2(0, 0);
- Assert.Equal(expectedPosition, camera.Position);
- }
- [Fact]
- public void SetPosition_WorldBoundsEnabled_ClampsToMaximumBounds()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- Viewport viewport = _graphicsFixture.GraphicsDevice.Viewport;
- Rectangle worldBounds = new Rectangle(0, 0, viewport.Width * 2, viewport.Height * 2);
- camera.EnableWorldBounds(worldBounds);
- camera.Position = new Vector2(viewport.Width, viewport.Height) * 3;
- Vector2 expectedPosition = new Vector2(worldBounds.Right - viewport.Width, worldBounds.Bottom - viewport.Height);
- Assert.Equal(expectedPosition, camera.Position);
- }
- [Fact]
- public void SetPosition_WorldBoundsEnabled_DoesNotClampWhenWithinBounds()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- Viewport viewport = _graphicsFixture.GraphicsDevice.Viewport;
- Rectangle worldBounds = new Rectangle(0, 0, viewport.Width * 2, viewport.Height * 2);
- camera.EnableWorldBounds(worldBounds);
- Vector2 expectedPosition = new Vector2(viewport.Width, viewport.Height);
- camera.Position = expectedPosition;
- Assert.Equal(expectedPosition, camera.Position);
- }
- [Fact]
- public void SetPosition_WorldBoundsSmallerThanCamera_CentersOnWorldBounds()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- Rectangle worldBounds = new Rectangle(100, 200, 50, 50);
- camera.EnableWorldBounds(worldBounds);
- camera.Position = new Vector2(1000, 1000);
- Vector2 expectedCenter = worldBounds.Center.ToVector2();
- Assert.Equal(expectedCenter, camera.Center);
- }
- [Fact]
- public void SetZoom_DefaultLimits_SetsValueWithoutClamping()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- camera.DisableWorldBounds();
- float expectedZoom = 2.0f;
- camera.Zoom = expectedZoom;
- Assert.Equal(expectedZoom, camera.Zoom);
- }
- [Fact]
- public void SetZoom_BelowMinimumZoom_ClampsToMinimum()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- camera.DisableWorldBounds();
- camera.MinimumZoom = 1.0f;
- camera.Zoom = 0.9f;
- Assert.Equal(camera.MinimumZoom, camera.Zoom);
- }
- [Fact]
- public void SetZoom_AboveMaximumZoom_ClampsToMaximum()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- camera.DisableWorldBounds();
- camera.MaximumZoom = 1.0f;
- camera.Zoom = 1.1f;
- Assert.Equal(camera.MaximumZoom, camera.Zoom);
- }
- [Fact]
- public void SetZoom_WorldBoundsEnabled_BelowMinimumWorldBoundsZoom_ClampsToWorldBounds()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- Viewport viewport = _graphicsFixture.GraphicsDevice.Viewport;
- Rectangle worldBounds = new Rectangle(0, 0, viewport.Width * 2, viewport.Height * 2);
- camera.EnableWorldBounds(worldBounds);
- camera.IsZoomClampedToWorldBounds = true;
- // Viewport for testing is 800x480, so world bounds are 1600x960
- // Minimum zoom to keep view within bounds: max(800/1600, 480/960) = max(0.5, 0.5) = 0.5
- // So a zoom at 0.5 is at the world bounds minium, so we set lower than that to check clamping.
- camera.Zoom = 0.3f;
- Assert.Equal(0.5f, camera.Zoom);
- }
- [Fact]
- public void SetZoom_WorldBoundsEnabled_AboveMaximumWorldBoundsZoom_ClampsToWorldBounds()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- Viewport viewport = _graphicsFixture.GraphicsDevice.Viewport;
- Rectangle worldBounds = new Rectangle(0, 0, viewport.Width * 2, viewport.Height * 2);
- camera.EnableWorldBounds(worldBounds);
- camera.IsZoomClampedToWorldBounds = true;
- // Viewport for testing is 800x480, so world bounds are 1600x960
- // Minimum zoom to keep view within bounds: max(800/1600, 480/960) = max(0.5, 0.5) = 0.5
- // So a zoom at 0.5 is at the world bounds minium, so we set lower than that to check clamping.
- camera.Zoom = 0.3f;
- Assert.Equal(0.5f, camera.Zoom);
- }
- [Fact]
- public void SetZoom_ExplicitMaximumZoom_TakesPrecedenceOverWorldBoundsMinimum()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- Viewport viewport = _graphicsFixture.GraphicsDevice.Viewport;
- Rectangle worldBounds = new Rectangle(0, 0, viewport.Width * 2, viewport.Height * 2);
- camera.EnableWorldBounds(worldBounds);
- camera.IsZoomClampedToWorldBounds = true;
- // Set explicit maximum BELOW what world bounds minimum requires (0.5)
- camera.MaximumZoom = 0.4f;
- // Try to set zoom to world bounds minimum
- camera.Zoom = 0.5f;
- // Explicit MaximumZoom should take precedence
- Assert.Equal(0.4f, camera.Zoom);
- }
- [Fact]
- public void SetZoom_WorldBoundsEnabled_ClampsPositionAfterZoomChange()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- Viewport viewport = _graphicsFixture.GraphicsDevice.Viewport;
- Rectangle worldBounds = new Rectangle(0, 0, viewport.Width, viewport.Height);
- // Position camera at edge of world bounds
- camera.Position = new Vector2(viewport.Width, viewport.Height);
- camera.EnableWorldBounds(worldBounds);
- camera.IsZoomClampedToWorldBounds = true;
- // Zoom out
- // this should force position adjustment to keep view in bounds
- camera.Zoom = 0.5f;
- // Zoom clamped to 1.0 (camera sees 800×480, same as world bounds)
- Assert.Equal(1.0f, camera.Zoom);
- // With zoom 1.0 and world bounds = viewport size, only valid position is (0, 0)
- Assert.Equal(0f, camera.Position.X, 2);
- Assert.Equal(0f, camera.Position.Y, 2);
- }
- [Fact]
- public void SetMinimumZoom_AboveCurrentZoom_ClampsCurrentZoom()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- camera.Zoom = 0.5f;
- camera.MinimumZoom = 1.0f;
- Assert.Equal(1.0f, camera.Zoom);
- }
- [Fact]
- public void SetMaximumZoom_BelowCurrentZoom_ClampsCurrentZoom()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- camera.Zoom = 2.0f;
- camera.MaximumZoom = 1.0f;
- Assert.Equal(1.0f, camera.Zoom);
- }
- [Fact]
- public void SetMinimumZoom_Negative_ThrowsArgumentOutOfRangeException()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- Assert.Throws<ArgumentOutOfRangeException>(() => camera.MinimumZoom = -1.0f);
- }
- [Fact]
- public void SetMaximumZoom_Negative_ThrowsArgumentOutOfRangeException()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- Assert.Throws<ArgumentOutOfRangeException>(() => camera.MaximumZoom = -1.0f);
- }
- [Fact]
- public void SetPitch_BelowMinimum_ClampsToMinimum()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- camera.MinimumPitch = 1.0f;
- camera.Pitch = 0.9f;
- Assert.Equal(camera.MinimumPitch, camera.Pitch);
- }
- [Fact]
- public void SetPitch_AboveMaximum_ClampsToMaximum()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- camera.MaximumPitch = 1.0f;
- camera.Pitch = 1.1f;
- Assert.Equal(camera.MaximumPitch, camera.Pitch);
- }
- [Fact]
- public void SetMinimumPitch_Negative_ThrowsArgumentOutOfRangeException()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- Assert.Throws<ArgumentOutOfRangeException>(() => camera.MinimumPitch = -0.01f);
- }
- [Fact]
- public void SetMaximumPitch_Negative_ThrowsArgumentOutOfRangeException()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- Assert.Throws<ArgumentOutOfRangeException>(() => camera.MaximumPitch = -0.01f);
- }
- [Fact]
- public void BoundingRectangle_WithMovement_ReturnsCorrectBounds()
- {
- DefaultViewportAdapter viewportAdapter = new DefaultViewportAdapter(_graphicsFixture.GraphicsDevice);
- OrthographicCamera camera = new OrthographicCamera(viewportAdapter);
- // Move right 2, then down 3
- Vector2 movement = new Vector2(2, 3);
- camera.Move(new Vector2(movement.X, 0));
- camera.Move(new Vector2(0, movement.Y));
- RectangleF boundingRectangle = camera.BoundingRectangle;
- Assert.Equal(movement.X, boundingRectangle.Left, 2);
- Assert.Equal(movement.Y, boundingRectangle.Top, 2);
- Assert.Equal(movement.X + viewportAdapter.VirtualWidth, boundingRectangle.Right, 2);
- Assert.Equal(movement.Y + viewportAdapter.VirtualHeight, boundingRectangle.Bottom, 2);
- }
- [Fact]
- public void BoundingRectangle_WithZoom_ReturnsCorrectBounds()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- camera.Zoom = 2f;
- RectangleF boundingRectangle = camera.BoundingRectangle;
- // With 2x zoom on 800x480 viewport, camera sees 400x240 area
- Assert.Equal(400f, boundingRectangle.Width, 2);
- Assert.Equal(240f, boundingRectangle.Height, 2);
- }
- [Fact]
- public void ContainsPoint_WithDefaultCamera_ReturnsCorrectContainment()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- Viewport viewport = _graphicsFixture.GraphicsDevice.Viewport;
- Assert.Equal(ContainmentType.Contains, camera.Contains(new Point(1, 1)));
- Assert.Equal(ContainmentType.Contains, camera.Contains(new Point(viewport.Width - 1, viewport.Height - 1)));
- Assert.Equal(ContainmentType.Disjoint, camera.Contains(new Point(-1, -1)));
- Assert.Equal(ContainmentType.Disjoint, camera.Contains(new Point(viewport.Width + 1, viewport.Height + 1)));
- }
- [Fact]
- public void ContainsVector2_WithDefaultCamera_ReturnsCorrectContainment()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- Viewport viewport = _graphicsFixture.GraphicsDevice.Viewport;
- Assert.Equal(ContainmentType.Contains, camera.Contains(new Vector2(viewport.Width - 0.5f, viewport.Height - 0.5f)));
- Assert.Equal(ContainmentType.Contains, camera.Contains(new Vector2(0.5f, 0.5f)));
- Assert.Equal(ContainmentType.Disjoint, camera.Contains(new Vector2(-0.5f, -0.5f)));
- Assert.Equal(ContainmentType.Disjoint, camera.Contains(new Vector2(viewport.Width + 0.5f, viewport.Height + 0.5f)));
- Assert.Equal(ContainmentType.Disjoint, camera.Contains(new Vector2(-0.5f, viewport.Height / 2f)));
- Assert.Equal(ContainmentType.Contains, camera.Contains(new Vector2(0.5f, viewport.Height / 2f)));
- Assert.Equal(ContainmentType.Contains, camera.Contains(new Vector2(viewport.Width - 0.5f, viewport.Height / 2f)));
- Assert.Equal(ContainmentType.Disjoint, camera.Contains(new Vector2(viewport.Width + 0.5f, viewport.Height / 2f)));
- }
- [Fact]
- public void ContainsRectangle_WithDefaultCamera_ReturnsCorrectContainment()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- Assert.Equal(ContainmentType.Intersects, camera.Contains(new Rectangle(-50, -50, 100, 100)));
- Assert.Equal(ContainmentType.Contains, camera.Contains(new Rectangle(50, 50, 100, 100)));
- Assert.Equal(ContainmentType.Disjoint, camera.Contains(new Rectangle(850, 500, 100, 100)));
- }
- [Fact]
- public void ContainsRectangle_FullyContained_ReturnsContains()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- Rectangle fullyContainedRect = new Rectangle(100, 100, 200, 200);
- ContainmentType result = camera.Contains(fullyContainedRect);
- Assert.Equal(ContainmentType.Contains, result);
- }
- [Fact]
- public void ContainsRectangle_PartiallyContained_ReturnsIntersects()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- Rectangle partiallyContainedRect = new Rectangle(-50, -50, 100, 100);
- ContainmentType result = camera.Contains(partiallyContainedRect);
- Assert.Equal(ContainmentType.Intersects, result);
- }
- [Fact]
- public void EnableWorldBounds_SetsWorldBoundsAndFlag()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- Rectangle worldBounds = new Rectangle(0, 0, 800, 600);
- camera.EnableWorldBounds(worldBounds);
- Assert.Equal(worldBounds, camera.WorldBounds);
- Assert.True(camera.IsClampedToWorldBounds);
- }
- [Fact]
- public void DisableWorldBounds_ClearsWorldBoundsAndFlag()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- camera.EnableWorldBounds(new Rectangle(0, 0, 800, 600));
- camera.DisableWorldBounds();
- Assert.Equal(Rectangle.Empty, camera.WorldBounds);
- Assert.False(camera.IsClampedToWorldBounds);
- }
- [Fact]
- public void Move_WithoutRotation_TranslatesPosition()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- Vector2 originalPosition = camera.Position;
- Vector2 movement = new Vector2(10, 20);
- camera.Move(movement);
- Assert.Equal(originalPosition + movement, camera.Position);
- }
- [Fact]
- public void Move_WithRotation_TranslatesPositionRelativeToRotation()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- // 90 degrees
- camera.Rotation = MathHelper.PiOver2;
- // Move right in world space
- Vector2 movement = new Vector2(10, 0);
- camera.Move(movement);
- // With 90 degree rotation, moving "right" should actually move "up" in screen space
- // The movement is transformed by the inverse rotation
- Vector2 expectedMovement = Vector2.Transform(movement, Matrix.CreateRotationZ(-camera.Rotation));
- Assert.Equal(expectedMovement.X, camera.Position.X, 3);
- Assert.Equal(expectedMovement.Y, camera.Position.Y, 3);
- }
- [Fact]
- public void Rotate_IncreasesRotation()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- var deltaRotation = MathHelper.PiOver4;
- camera.Rotate(deltaRotation);
- Assert.Equal(deltaRotation, camera.Rotation, 5);
- }
- [Fact]
- public void ZoomIn_IncreasesZoom()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- float originalZoom = camera.Zoom;
- camera.ZoomIn(1);
- Assert.Equal(originalZoom + 1, camera.Zoom);
- }
- [Fact]
- public void ZoomOut_DecreasesZoom()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- float originalZoom = camera.Zoom;
- camera.ZoomOut(1);
- Assert.Equal(originalZoom - 1, camera.Zoom);
- }
- [Fact]
- public void PitchUp_IncreasesPitch()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- float originalPitch = camera.Pitch;
- camera.PitchUp(1);
- Assert.Equal(originalPitch + 1, camera.Pitch);
- }
- [Fact]
- public void PitchDown_DecreasesPitch()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- float originalPitch = camera.Pitch;
- camera.PitchDown(1);
- Assert.Equal(originalPitch - 1, camera.Pitch);
- }
- [Fact]
- public void LookAt_SetsPositionCorrectly()
- {
- DefaultViewportAdapter viewportAdapter = new DefaultViewportAdapter(_graphicsFixture.GraphicsDevice);
- OrthographicCamera camera = new OrthographicCamera(viewportAdapter);
- Vector2 targetPosition = new Vector2(100, 200);
- camera.LookAt(targetPosition);
- Vector2 expectedPosition = targetPosition - new Vector2(viewportAdapter.VirtualWidth, viewportAdapter.VirtualHeight) * 0.5f;
- Assert.Equal(expectedPosition, camera.Position);
- }
- [Fact]
- public void ScreenToWorld_WithCameraMovement_TransformsCorrectly()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- camera.Position = new Vector2(100, 200);
- // Screen position at origin
- Vector2 screenPosition = Vector2.Zero;
- Vector2 worldPosition = camera.ScreenToWorld(screenPosition);
- // World position should account for camera offset
- Assert.Equal(100, worldPosition.X, 2);
- Assert.Equal(200, worldPosition.Y, 2);
- }
- [Fact]
- public void WorldToScreen_WithCameraMovement_TransformsCorrectly()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- camera.Position = new Vector2(100, 200);
- // World position at camera position
- Vector2 worldPosition = new Vector2(100, 200);
- Vector2 screenPosition = camera.WorldToScreen(worldPosition);
- // Should appear at screen origin
- Assert.Equal(0, screenPosition.X, 2);
- Assert.Equal(0, screenPosition.Y, 2);
- }
- [Fact]
- public void ScreenToWorld_WithZoom_TransformsCorrectly()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- camera.Zoom = 2.0f;
- Vector2 screenPosition = new Vector2(100, 100);
- Vector2 worldPosition = camera.ScreenToWorld(screenPosition);
- // With default camera:
- // - Origin is at (400, 240) - the viewport center
- // - Position is at (0, 0)
- // - Screen (100, 100) is 300px left and 140px up from Origin
- // - With 2x zoom: world offset is (300/2, 140/2) = (150, 70) from Origin
- // - World position: Origin - offset = (400 - 150, 240 - 70) = (250, 170)
- Assert.Equal(250, worldPosition.X, 2);
- Assert.Equal(170, worldPosition.Y, 2);
- }
- [Fact]
- public void WorldToScreen_WithZoom_TransformsCorrectly()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- camera.Zoom = 2.0f;
- Vector2 worldPosition = new Vector2(100, 100);
- Vector2 screenPosition = camera.WorldToScreen(worldPosition);
- // With default camera:
- // - Origin is at (400, 240) - the viewport center
- // - Position is at (0, 0)
- // - Camera.Center is at (400, 240)
- // - World (100, 100) is 300px left and 140px up from Camera.Center
- // - With 2x zoom: screen offset is (300 * 2, 140 * 2) = (600, 280) from Origin
- // - Screen position: Origin - offset = (400 - 600, 240 - 280) = (-200, -40)
- Assert.Equal(-200, screenPosition.X, 2);
- Assert.Equal(-40, screenPosition.Y, 2);
- }
- [Fact]
- public void WorldToScreen_RoundTrip_ReturnsOriginalPosition()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- camera.Position = new Vector2(100, 200);
- camera.Zoom = 1.5f;
- Vector2 originalWorld = new Vector2(250, 350);
- Vector2 screen = camera.WorldToScreen(originalWorld);
- Vector2 backToWorld = camera.ScreenToWorld(screen);
- Assert.Equal(originalWorld.X, backToWorld.X, 2);
- Assert.Equal(originalWorld.Y, backToWorld.Y, 2);
- }
- [Fact]
- public void GetViewMatrix_ReturnsValidMatrix()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- Matrix viewMatrix = camera.GetViewMatrix();
- Assert.Equal(Matrix.Identity, viewMatrix);
- }
- [Fact]
- public void GetInverseViewMatrix_IsInverseOfViewMatrix()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- camera.Position = new Vector2(10, 20);
- camera.Rotation = MathHelper.PiOver4;
- camera.Zoom = 2f;
- Matrix viewMatrix = camera.GetViewMatrix();
- Matrix inverseViewMatrix = camera.GetInverseViewMatrix();
- Matrix shouldBeIdentity = Matrix.Multiply(viewMatrix, inverseViewMatrix);
- // Check if the result is close to identity matrix
- AssertExtensions.Equal(Matrix.Identity, shouldBeIdentity, 3);
- }
- [Fact]
- public void GetViewMatrix_WithParallaxFactor_ReturnsValidMatrix()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- // Set non-zero position to make parallax effect visible
- camera.Position = new Vector2(100, 50);
- Vector2 parallaxFactor = new Vector2(0.5f, 0.5f);
- Matrix viewMatrix = camera.GetViewMatrix(parallaxFactor);
- Assert.NotEqual(Matrix.Identity, viewMatrix);
- }
- [Fact]
- public void GetViewMatrix_WithParallaxFactor_AppliesCorrectTransformation()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- camera.Position = new Vector2(100, 60);
- Vector2 parallaxFactor = new Vector2(0.5f, 0.25f);
- Matrix parallaxMatrix = camera.GetViewMatrix(parallaxFactor);
- // Default parallax factor of (1,1)
- Matrix normalMatrix = camera.GetViewMatrix();
- // The matrices should be different when parallax factor is not (1,1) and position is not zero
- Assert.NotEqual(normalMatrix, parallaxMatrix);
- Assert.NotEqual(Matrix.Identity, parallaxMatrix);
- Assert.NotEqual(Matrix.Identity, normalMatrix);
- // With position (100, 60) and parallax (0.5, 0.25):
- // Expected translation = -(100 * 0.5, 60 * 0.25) = (-50, -15)
- // This should be reflected in the view matrix M41, M42 values
- Assert.Equal(-50f, parallaxMatrix.M41, 1);
- Assert.Equal(-15f, parallaxMatrix.M42, 1);
- }
- [Fact]
- public void GetBoundingFrustum_ReturnsValidFrustum()
- {
- var camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- Viewport viewport = _graphicsFixture.GraphicsDevice.Viewport;
- BoundingFrustum boundingFrustum = camera.GetBoundingFrustum();
- Vector3[] corners = boundingFrustum.GetCorners();
- // Verify we have 8 corners (standard frustum)
- Assert.Equal(8, corners.Length);
- // Check near plane corners (Z = 1)
- Assert.Equal(0, corners[0].X, 2);
- Assert.Equal(0, corners[0].Y, 2);
- Assert.Equal(1, corners[0].Z, 2);
- Assert.Equal(viewport.Width, corners[1].X, 2);
- Assert.Equal(0, corners[1].Y, 2);
- Assert.Equal(1, corners[1].Z, 2);
- Assert.Equal(viewport.Width, corners[2].X, 2);
- Assert.Equal(viewport.Height, corners[2].Y, 2);
- Assert.Equal(1, corners[2].Z, 2);
- Assert.Equal(0, corners[3].X, 2);
- Assert.Equal(viewport.Height, corners[3].Y, 2);
- Assert.Equal(1, corners[3].Z, 2);
- // Check far plane corners (Z = 0)
- Assert.Equal(0, corners[4].X, 2);
- Assert.Equal(0, corners[4].Y, 2);
- Assert.Equal(0, corners[4].Z, 2);
- Assert.Equal(viewport.Width, corners[5].X, 2);
- Assert.Equal(0, corners[5].Y, 2);
- Assert.Equal(0, corners[5].Z, 2);
- Assert.Equal(viewport.Width, corners[6].X, 2);
- Assert.Equal(viewport.Height, corners[6].Y, 2);
- Assert.Equal(0, corners[6].Z, 2);
- Assert.Equal(0, corners[7].X, 2);
- Assert.Equal(viewport.Height, corners[7].Y, 2);
- Assert.Equal(0, corners[7].Z, 2);
- }
- [Fact]
- public void ZoomIn_WithZoomCenter_KeepsZoomCenterFixedOnScreen()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- camera.Position = Vector2.Zero;
- camera.Zoom = 1.0f;
- Vector2 zoomCenter = new Vector2(100, 100);
- Vector2 screenBefore = camera.WorldToScreen(zoomCenter);
- camera.ZoomIn(0.5f, zoomCenter);
- Vector2 screenAfter = camera.WorldToScreen(zoomCenter);
- Assert.Equal(screenBefore.X, screenAfter.X, 1);
- Assert.Equal(screenBefore.Y, screenAfter.Y, 1);
- }
- [Fact]
- public void ZoomOut_WithZoomCenter_KeepsZoomCenterFixedOnScreen()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- camera.Position = Vector2.Zero;
- camera.Zoom = 2.0f;
- Vector2 zoomCenter = new Vector2(100, 100);
- Vector2 screenBefore = camera.WorldToScreen(zoomCenter);
- camera.ZoomOut(0.5f, zoomCenter);
- Vector2 screenAfter = camera.WorldToScreen(zoomCenter);
- Assert.Equal(screenBefore.X, screenAfter.X, 1);
- Assert.Equal(screenBefore.Y, screenAfter.Y, 1);
- }
- [Fact]
- public void ZoomIn_WithZoomCenter_ClampedByMinimumZoom_DoesNotAdjustPosition()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- camera.MaximumZoom = 2.0f;
- camera.Zoom = camera.MaximumZoom;
- camera.Position = new Vector2(50, 50);
- Vector2 zoomCenter = new Vector2(100, 100);
- Vector2 positionBefore = camera.Position;
- camera.ZoomIn(1.0f, zoomCenter);
- Assert.Equal(2.0f, camera.Zoom);
- Assert.Equal(positionBefore, camera.Position);
- }
- [Fact]
- public void ZoomOut_WithZoomCenter_ClampedByMinimumZoom_DoesNotAdjustPosition()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- camera.MinimumZoom = 0.5f;
- camera.Zoom = camera.MinimumZoom;
- camera.Position = new Vector2(50, 50);
- Vector2 zoomCenter = new Vector2(100, 100);
- Vector2 positionBefore = camera.Position;
- camera.ZoomOut(1.0f, zoomCenter);
- Assert.Equal(0.5f, camera.Zoom);
- Assert.Equal(positionBefore, camera.Position);
- }
- [Fact]
- public void ZoomIn_WithZoomCenter_AtOrigin_AdjustsPositionCorrectly()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- camera.Position = new Vector2(100, 100);
- camera.Zoom = 1.0f;
- Vector2 zoomCenter = camera.Origin;
- Vector2 screenBefore = camera.WorldToScreen(zoomCenter);
- camera.ZoomIn(0.5f, zoomCenter);
- Vector2 screenAfter = camera.WorldToScreen(zoomCenter);
- Assert.Equal(screenBefore.X, screenAfter.X, 1);
- Assert.Equal(screenBefore.Y, screenAfter.Y, 1);
- }
- [Fact]
- public void ZoomOut_WithZoomCenter_AtOrigin_AdjustsPositionCorrectly()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- camera.Position = new Vector2(100, 100);
- camera.Zoom = 2.0f;
- Vector2 zoomCenter = camera.Origin;
- Vector2 screenBefore = camera.WorldToScreen(zoomCenter);
- camera.ZoomOut(0.5f, zoomCenter);
- Vector2 screenAfter = camera.WorldToScreen(zoomCenter);
- Assert.Equal(screenBefore.X, screenAfter.X, 1);
- Assert.Equal(screenBefore.Y, screenAfter.Y, 1);
- }
- [Fact]
- public void ZoomIn_WithZoomCenter_WorldBoundsEnabled_RespectsBounds()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- Viewport viewport = _graphicsFixture.GraphicsDevice.Viewport;
- Rectangle worldBounds = new Rectangle(0, 0, viewport.Width * 2, viewport.Height * 2);
- camera.EnableWorldBounds(worldBounds);
- camera.Position = new Vector2(viewport.Width / 2, viewport.Height / 2);
- camera.Zoom = 1.0f;
- Vector2 zoomCenter = new Vector2(200, 200);
- camera.ZoomIn(0.5f, zoomCenter);
- Assert.True(camera.Position.X >= 0);
- Assert.True(camera.Position.Y >= 0);
- Assert.True(camera.Position.X <= worldBounds.Right - viewport.Width / camera.Zoom);
- Assert.True(camera.Position.Y <= worldBounds.Bottom - viewport.Height / camera.Zoom);
- }
- [Fact]
- public void ZoomOut_WithZoomCenter_WorldBoundsEnabled_RespectsBounds()
- {
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- Viewport viewport = _graphicsFixture.GraphicsDevice.Viewport;
- Rectangle worldBounds = new Rectangle(0, 0, viewport.Width * 2, viewport.Height * 2);
- camera.EnableWorldBounds(worldBounds);
- camera.Position = new Vector2(viewport.Width / 2, viewport.Height / 2);
- camera.Zoom = 2.0f;
- Vector2 zoomCenter = new Vector2(200, 200);
- camera.ZoomOut(0.5f, zoomCenter);
- Assert.True(camera.Position.X >= 0);
- Assert.True(camera.Position.Y >= 0);
- Assert.True(camera.Position.X <= worldBounds.Right - viewport.Width / camera.Zoom);
- Assert.True(camera.Position.Y <= worldBounds.Bottom - viewport.Height / camera.Zoom);
- }
- // -----------------------------------------------------------------------------
- // Tests for issue #793
- // OrthographicCamera.ScreenToWorld and similar methods interact poorly with
- // window not at (0,0)
- // https://github.com/MonoGame-Extended/Monogame-Extended/issues/793
- // -----------------------------------------------------------------------------
- //
- // The goal of these tests is to verify that coordinate transformations between
- // world-space and screen-space behave correctly when the viewport has a non-zero
- // origin (e.g. the window is offset or letterboxed).
- //
- // Specifically:
- // - DefaultViewportAdapter: viewport offset should NOT affect transformations.
- // - ScalingViewportAdapter/BoxingViewportAdapter: offset IS part of transformation.
- //
- // Each test ensures that ScreenToWorld() and WorldToScreen() remain consistent
- // inverses of one another under these different scenarios.
- //
- [Trait("Issue", "#793")]
- [Collection("GraphicsTest")]
- public class OrthographicCameraIssue793Tests
- {
- private readonly GraphicsTestFixture _graphicsFixture;
- public OrthographicCameraIssue793Tests(GraphicsTestFixture graphicsTestFixture)
- {
- _graphicsFixture = graphicsTestFixture;
- }
- // -------------------------------------------------------------------------
- // Test 1: DefaultViewportAdapter (non-scaling)
- // -------------------------------------------------------------------------
- // Verifies that for a simple viewport offset (e.g., window not at (0,0)),
- // the ScreenToWorld transformation ignores viewport origin and maps directly.
- // Mouse/touch input coordinates (from MouseState) are already window-relative,
- // so no offset adjustment should occur.
- [Fact]
- public void ScreenToWorld_WithNonZeroViewportOrigin_TransformsCorrectly()
- {
- Viewport originalViewport = _graphicsFixture.GraphicsDevice.Viewport;
- try
- {
- _graphicsFixture.GraphicsDevice.Viewport = new Viewport(100, 50, 800, 480);
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- Vector2 screenPosition = new Vector2(200, 150);
- Vector2 worldPosition = camera.ScreenToWorld(screenPosition);
- // Expectation:
- // With viewport origin offset, but no scaling or zoom,
- // the mapping should remain 1:1 with window coordinates.
- Assert.Equal(200, worldPosition.X, 2);
- Assert.Equal(150, worldPosition.Y, 2);
- }
- finally
- {
- _graphicsFixture.GraphicsDevice.Viewport = originalViewport;
- }
- }
- // -------------------------------------------------------------------------
- // Test 2: Round-trip transformation with DefaultViewportAdapter
- // -------------------------------------------------------------------------
- // Ensures that WorldToScreen() and ScreenToWorld() are true inverses even when
- // the viewport has a non-zero origin. The result after round-tripping should
- // return to the original world position (within floating-point tolerance).
- [Fact]
- public void WorldToScreen_RoundTrip_WithNonZeroViewportOrigin_ReturnsOriginalPosition()
- {
- Viewport originalViewport = _graphicsFixture.GraphicsDevice.Viewport;
- try
- {
- _graphicsFixture.GraphicsDevice.Viewport = new Viewport(100, 50, 800, 480);
- OrthographicCamera camera = new OrthographicCamera(_graphicsFixture.GraphicsDevice);
- camera.Position = new Vector2(100, 200);
- camera.Zoom = 1.5f;
- Vector2 originalWorld = new Vector2(250, 350);
- Vector2 screen = camera.WorldToScreen(originalWorld);
- Vector2 backToWorld = camera.ScreenToWorld(screen);
- Assert.Equal(originalWorld.X, backToWorld.X, 2);
- Assert.Equal(originalWorld.Y, backToWorld.Y, 2);
- }
- finally
- {
- _graphicsFixture.GraphicsDevice.Viewport = originalViewport;
- }
- }
- // -------------------------------------------------------------------------
- // Test 3: BoxingViewportAdapter (scaling)
- // -------------------------------------------------------------------------
- // Verifies that when using a scaling viewport adapter (BoxingViewportAdapter),
- // ScreenToWorld() correctly accounts for the viewport offset, which defines
- // where the virtual coordinate system is drawn inside the window.
- //
- // In this case, the viewport offset IS part of the coordinate transformation.
- [Fact]
- public void ScreenToWorld_WithBoxingViewportAdapter_TransformsCorrectly()
- {
- Viewport originalViewport = _graphicsFixture.GraphicsDevice.Viewport;
- try
- {
- int virtualWidth = 400;
- int virtualHeight = 240;
- // Create a boxing viewport adapter with a virtual resolution smaller
- // than the actual window. This will introduce a viewport offset
- BoxingViewportAdapter viewportAdapter = new BoxingViewportAdapter(
- _graphicsFixture.Game.Window,
- _graphicsFixture.GraphicsDevice,
- virtualWidth,
- virtualHeight);
- // Forces recalculation of the viewport region inside the window.
- viewportAdapter.Reset();
- OrthographicCamera camera = new OrthographicCamera(viewportAdapter);
- // Retrieve the actual viewport dimensions and offset
- Viewport viewport = _graphicsFixture.GraphicsDevice.Viewport;
- float scaleX = (float)viewport.Width / virtualWidth;
- float scaleY = (float)viewport.Height / virtualHeight;
- // The center of the viewport in window space
- Vector2 windowPosition = new Vector2(viewport.X + viewport.Width * 0.5f,
- viewport.Y + viewport.Height * 0.5f);
- Vector2 worldPosition = camera.ScreenToWorld(windowPosition);
- // Should map to center of virtual space (200, 120)
- Assert.Equal(virtualWidth * 0.5f, worldPosition.X, 2);
- Assert.Equal(virtualHeight * 0.5f, worldPosition.Y, 2);
- }
- finally
- {
- _graphicsFixture.GraphicsDevice.Viewport = originalViewport;
- }
- }
- // -------------------------------------------------------------------------
- // Test 4: Round-trip transformation with BoxingViewportAdapter
- // -------------------------------------------------------------------------
- // Ensures that WorldToScreen() and ScreenToWorld() remain perfect inverses
- // when scaling and offset are both involved.
- //
- // This confirms that the viewport offset and scaling transformations
- // are correctly applied and undone in opposite order.
- [Fact]
- public void WorldToScreen_RoundTrip_WithBoxingViewportAdapter_ReturnsOriginalPosition()
- {
- Viewport originalViewport = _graphicsFixture.GraphicsDevice.Viewport;
- try
- {
- int virtualWidth = 400;
- int virtualHeight = 240;
- BoxingViewportAdapter viewportAdapter = new BoxingViewportAdapter(_graphicsFixture.Game.Window,
- _graphicsFixture.GraphicsDevice,
- virtualWidth,
- virtualHeight);
- viewportAdapter.Reset();
- OrthographicCamera camera = new OrthographicCamera(viewportAdapter);
- camera.Position = new Vector2(50, 100);
- camera.Zoom = 1.5f;
- Vector2 originalWorld = new Vector2(125, 175);
- // Round trip world -> screen -> world
- Vector2 screen = camera.WorldToScreen(originalWorld);
- Vector2 backToWorld = camera.ScreenToWorld(screen);
- // Expect round-trip consistency
- Assert.Equal(originalWorld.X, backToWorld.X, 2);
- Assert.Equal(originalWorld.Y, backToWorld.Y, 2);
- }
- finally
- {
- _graphicsFixture.GraphicsDevice.Viewport = originalViewport;
- }
- }
- }
- }
|