using Raylib_cs; using System; using System.Numerics; namespace PhysicsEngine { public class Camera { public const float PixelsPerMeter = 100.0f; private const float GridSpacing = 1.0f; // meters private const float ZoomLerpSpeed = 30.0f; private const float MoveLerpSpeed = 20.0f; private const float ZoomFactor = 0.1f; private const float MinZoom = 0.2f; private const float MaxZoom = 50.0f; public Camera2D RaylibCamera { get; private set; } public Vector2 Position => RaylibCamera.Target / PixelsPerMeter; public float Zoom => RaylibCamera.Zoom; private float targetZoom; private Vector2 targetPosition; private Vector2 dragStartMouseScreen; private Vector2 dragStartTarget; private bool isDragging = false; public Camera() { RaylibCamera = new Camera2D { Target = Vector2.Zero, Offset = new Vector2(Raylib.GetScreenWidth() / 2.0f, Raylib.GetScreenHeight() / 2.0f), Rotation = 0.0f, Zoom = 1.0f }; targetZoom = 1.0f; targetPosition = RaylibCamera.Target; } public void Update(float frameTime) { Camera2D cam = RaylibCamera; cam.Offset = new Vector2(Raylib.GetScreenWidth() / 2.0f, Raylib.GetScreenHeight() / 2.0f); // panning if (Raylib.IsMouseButtonPressed(MouseButton.Middle)) { isDragging = true; dragStartMouseScreen = Raylib.GetMousePosition(); dragStartTarget = targetPosition; } if (isDragging) { if (Raylib.IsMouseButtonDown(MouseButton.Middle)) { Vector2 currentMouseScreen = Raylib.GetMousePosition(); Vector2 screenDelta = currentMouseScreen - dragStartMouseScreen; targetPosition = dragStartTarget - (screenDelta / cam.Zoom); cam.Target = targetPosition; } else { isDragging = false; } } else { // arrow key panning float moveSpeed = 5.0f * PixelsPerMeter; if (Raylib.IsKeyDown(KeyboardKey.Right)) targetPosition.X += moveSpeed * frameTime; if (Raylib.IsKeyDown(KeyboardKey.Left)) targetPosition.X -= moveSpeed * frameTime; if (Raylib.IsKeyDown(KeyboardKey.Down)) targetPosition.Y += moveSpeed * frameTime; if (Raylib.IsKeyDown(KeyboardKey.Up)) targetPosition.Y -= moveSpeed * frameTime; } // recenter if (Raylib.IsKeyPressed(KeyboardKey.Space)) { targetPosition = Vector2.Zero; targetZoom = 1.0f; isDragging = false; cam.Target = Vector2.Zero; cam.Zoom = 1.0f; } // zooming float wheel = Raylib.GetMouseWheelMove(); if (wheel != 0.0f) { targetZoom *= MathF.Pow(1 + ZoomFactor, wheel); targetZoom = Math.Clamp(targetZoom, MinZoom, MaxZoom); } // lerp zoom if (MathF.Abs(cam.Zoom - targetZoom) > 0.0001f) { Vector2 mouseWorldBefore = Raylib.GetScreenToWorld2D(Raylib.GetMousePosition(), cam); cam.Zoom += (targetZoom - cam.Zoom) * ZoomLerpSpeed * frameTime; cam.Zoom = Math.Clamp(cam.Zoom, MinZoom, MaxZoom); Vector2 mouseWorldAfter = Raylib.GetScreenToWorld2D(Raylib.GetMousePosition(), cam); targetPosition += (mouseWorldBefore - mouseWorldAfter); cam.Target = targetPosition; if (isDragging) { dragStartMouseScreen = Raylib.GetMousePosition(); dragStartTarget = targetPosition; } } else if (!isDragging) { cam.Target.X += (targetPosition.X - cam.Target.X) * MoveLerpSpeed * frameTime; cam.Target.Y += (targetPosition.Y - cam.Target.Y) * MoveLerpSpeed * frameTime; } RaylibCamera = cam; } public void DrawGrid() { float screenW = Raylib.GetScreenWidth(); float screenH = Raylib.GetScreenHeight(); float cellPixels = GridSpacing * PixelsPerMeter * RaylibCamera.Zoom; float subCellPixels = cellPixels / 5.0f; Vector2 originScreen = Raylib.GetWorldToScreen2D(Vector2.Zero, RaylibCamera); Color gridColor = MuiDarkColor.SurfaceVariant.ToColor(); Color subGridColor = new Color((byte)gridColor.R, (byte)gridColor.G, (byte)gridColor.B, (byte)75); Color mainGridColor = new Color((byte)gridColor.R, (byte)gridColor.G, (byte)gridColor.B, (byte)150); // subgrid lines float startSubX = originScreen.X % subCellPixels; if (startSubX > 0) startSubX -= subCellPixels; for (float x = startSubX; x <= screenW; x += subCellPixels) { float relativeToOriginX = x - originScreen.X; int indexX = (int)Math.Round(relativeToOriginX / subCellPixels); if (indexX % 5 == 0) continue; int ix = (int)Math.Round(x); Raylib.DrawLineEx(new Vector2(ix, 0), new Vector2(ix, screenH), 2.0f, subGridColor); } float startSubY = originScreen.Y % subCellPixels; if (startSubY > 0) startSubY -= subCellPixels; for (float y = startSubY; y <= screenH; y += subCellPixels) { float relativeToOriginY = y - originScreen.Y; int indexY = (int)Math.Round(relativeToOriginY / subCellPixels); if (indexY % 5 == 0) continue; int iy = (int)Math.Round(y); Raylib.DrawLineEx(new Vector2(0, iy), new Vector2(screenW, iy), 2.0f, subGridColor); } // main grid lines float startMainX = originScreen.X % cellPixels; if (startMainX > 0) startMainX -= cellPixels; for (float x = startMainX; x <= screenW; x += cellPixels) { int ix = (int)Math.Round(x); Raylib.DrawLineEx(new Vector2(ix, 0), new Vector2(ix, screenH), 3.0f, mainGridColor); } float startMainY = originScreen.Y % cellPixels; if (startMainY > 0) startMainY -= cellPixels; for (float y = startMainY; y <= screenH; y += cellPixels) { int iy = (int)Math.Round(y); Raylib.DrawLineEx(new Vector2(0, iy), new Vector2(screenW, iy), 3.0f, mainGridColor); } } public void DrawHUD() { float screenH = Raylib.GetScreenHeight(); string hudText = $"X: {Position.X:F2}m Y: {Position.Y:F2}m Zoom: {Zoom:F2}x"; Program.DrawText(hudText, 20, (int)screenH - 40, 20, MuiDarkColor.TextPrimary.ToColor()); } } }