diff options
| author | hazel <hazel@hazelthats.me> | 2026-01-26 22:04:39 +0100 |
|---|---|---|
| committer | hazel <hazel@hazelthats.me> | 2026-01-26 22:04:39 +0100 |
| commit | 567c422f8cd42eba2437f9a8c2522716a1649be7 (patch) | |
| tree | 93c5b296f3b7c14b626d0aadf5cad37764c41c74 /source/graphics | |
| download | celesteia-567c422f8cd42eba2437f9a8c2522716a1649be7.tar.gz celesteia-567c422f8cd42eba2437f9a8c2522716a1649be7.tar.bz2 celesteia-567c422f8cd42eba2437f9a8c2522716a1649be7.zip | |
celesteia archive, last updated april 9th 2024
Signed-off-by: hazel <hazel@hazelthats.me>
Diffstat (limited to 'source/graphics')
| -rw-r--r-- | source/graphics/Camera2D.cs | 63 | ||||
| -rw-r--r-- | source/graphics/GraphicsManager.cs | 65 | ||||
| -rw-r--r-- | source/graphics/lighting/LightMap.cs | 139 |
3 files changed, 267 insertions, 0 deletions
diff --git a/source/graphics/Camera2D.cs b/source/graphics/Camera2D.cs new file mode 100644 index 0000000..1bae764 --- /dev/null +++ b/source/graphics/Camera2D.cs @@ -0,0 +1,63 @@ +using System; +using Celesteia.Resources; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Celesteia.Graphics { + public class Camera2D { + private GraphicsDevice _graphicsDevice; + + // Viewport macros. + private int ViewportX => _graphicsDevice.Viewport.X; + private int ViewportY => _graphicsDevice.Viewport.Y; + private int ViewportWidth => _graphicsDevice.Viewport.Width; + private int ViewportHeight => _graphicsDevice.Viewport.Height; + + private int _zoom = 0; + public int ScaledZoom { get; private set; } = 0; + // The zoom value of the camera. + public int Zoom { + get { return _zoom; } + set { + _zoom = MathHelper.Clamp(value, 1, 8); + ScaledZoom = _zoom * ResourceManager.INVERSE_SPRITE_SCALING; + } + } + + public Camera2D(GraphicsDevice graphicsDevice) { + _graphicsDevice = graphicsDevice; + Zoom = 2; + } + + // The camera's center. + public Vector2 Center = Vector2.Zero; + + private float _rotation; + // The rotation applied to the camera. + public float Rotation { + get { return _rotation; } + set { _rotation = value % 360f; } + } + + /* + Creates a matrix with the following steps: + - Create a translation to match (0, 0) to the center point of the camera. + - Apply Z rotation. + - Scale according to zoom value and inverse sprite scaling. + - Always round the viewport width and height to prevent half-pixel rounding issues. + */ + private float maxScale = 0f; + public Matrix GetViewMatrix() { + maxScale = MathF.Max(MathF.Ceiling(ViewportWidth / 1920f), MathF.Ceiling(ViewportHeight / 1080f)); + return Matrix.CreateTranslation(-Center.X, -Center.Y, 0f) * + Matrix.CreateRotationZ(Rotation) * + Matrix.CreateScale(ScaledZoom, ScaledZoom, 1f) * + Matrix.CreateScale(maxScale, maxScale, 1f) * + Matrix.CreateTranslation(ViewportWidth / 2f, ViewportHeight / 2f, 0f); + } + + // Transform the viewport relative mouse position to the inverse view matrix to get the pointer's position in the world. + public Vector2 ScreenToWorld(Point point) + => Vector2.Transform(new Vector2(point.X - ViewportX, point.Y - ViewportY), Matrix.Invert(GetViewMatrix())); + } +}
\ No newline at end of file diff --git a/source/graphics/GraphicsManager.cs b/source/graphics/GraphicsManager.cs new file mode 100644 index 0000000..2e285cf --- /dev/null +++ b/source/graphics/GraphicsManager.cs @@ -0,0 +1,65 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Celesteia.Graphics { + public enum FullscreenMode { + Windowed, Fullscreen, Borderless + } + + public class GraphicsManager : GameComponent { + private new GameInstance Game => (GameInstance) base.Game; + private GraphicsDeviceManager _manager; + public GraphicsManager(GameInstance Game) : base(Game) { + _manager = new GraphicsDeviceManager(Game); + _manager.PreferHalfPixelOffset = true; + } + + private FullscreenMode _screenMode; + private bool _useBorderless = false; + private Rectangle _resolution = new Rectangle(0, 0, 1280, 720); + private Rectangle _lastBounds; + + public FullscreenMode FullScreen { + get { return _screenMode; } + set { _screenMode = value; ResolveResolution(); } + } + + public bool VSync = false; + + public bool IsFullScreen { + get { return (_screenMode != FullscreenMode.Windowed); } + } + + public Rectangle Resolution { + get { return _resolution; } + set { _lastBounds = _resolution = value; } + } + + public bool MSAA = false; + + private void ResolveResolution() { + if (!IsFullScreen) _resolution = _lastBounds; + else { + _lastBounds = Game.Window.ClientBounds; + _resolution = new Rectangle(0, 0, _manager.GraphicsDevice.Adapter.CurrentDisplayMode.Width, _manager.GraphicsDevice.Adapter.CurrentDisplayMode.Height); + } + } + + public void Apply() { + Game.Window.AllowUserResizing = true; + _manager.PreferMultiSampling = MSAA; + _manager.PreferredBackBufferWidth = _resolution.Width; + _manager.PreferredBackBufferHeight = _resolution.Height; + _manager.PreferredBackBufferFormat = SurfaceFormat.Color; + _manager.HardwareModeSwitch = (_screenMode == FullscreenMode.Borderless); + _manager.IsFullScreen = IsFullScreen; + _manager.SynchronizeWithVerticalRetrace = VSync; + _manager.ApplyChanges(); + } + + public GraphicsManager ToggleFullScreen() { + FullScreen = IsFullScreen ? FullscreenMode.Windowed : (_useBorderless ? FullscreenMode.Borderless : FullscreenMode.Fullscreen); + return this; + } + } +}
\ No newline at end of file diff --git a/source/graphics/lighting/LightMap.cs b/source/graphics/lighting/LightMap.cs new file mode 100644 index 0000000..4fb3b9d --- /dev/null +++ b/source/graphics/lighting/LightMap.cs @@ -0,0 +1,139 @@ +using System; +using Celesteia.Resources.Types; +using Microsoft.Xna.Framework; + +namespace Celesteia.Graphics.Lighting { + public class LightMap { + private bool[,] _emit; + private LightColor[,] _lightColors; + private int[,] _propagation; + + public readonly int Width; + public readonly int Height; + + public LightMap(int width, int height) { + Width = width; + Height = height; + + _emit = new bool[width, height]; + _lightColors = new LightColor[width, height]; + _propagation = new int[width, height]; + } + + public bool AddForeground(int x, int y, BlockLightProperties blockLight) { + if (blockLight.Emits) AddLight(x, y, blockLight.Emits, blockLight.Color, blockLight.Propagation); + else if (blockLight.Occludes) AddDarkness(x, y); + + return blockLight.Emits || blockLight.Occludes; + } + + public bool AddBackground(int x, int y, BlockLightProperties blockLight) { + if (blockLight.Occludes) { + if (blockLight.Emits) AddLight(x, y, blockLight.Emits, blockLight.Color, blockLight.Propagation); + else AddDarkness(x, y); + } + + return blockLight.Occludes; + } + + public void AddLight(int x, int y, bool emit, LightColor color, int propagation) { + if (!InMap(x, y)) return; + + _emit[x, y] = emit; + _lightColors[x, y] = color; + _propagation[x, y] = propagation; + } + + public void AddDarkness(int x, int y) => AddLight(x, y, false, LightColor.black, 0); + + public void Propagate() { + for (int x = 0; x < Width; x++) + for (int y = 0; y < Height; y++) + if (_emit[x, y]) + PropagateFrom(x, y, _lightColors[x, y], _propagation[x, y]); + } + + public bool InMap(int x, int y) => !(x < 0 || x >= Width || y < 0 || y >= Height); + + private float _normalDropoff = 0.7f; + private float _diagonalDropoff => _normalDropoff * _normalDropoff; + private int lookX; + private int lookY; + private LightColor _target; + private int distance; + private void PropagateFrom(int x, int y, LightColor color, int propagation) { + for (int i = -propagation; i <= propagation; i++) { + lookX = x + i; + for (int j = -propagation; j <= propagation; j++) { + lookY = y + j; + + if (!InMap(lookX, lookY)) continue; + if (_emit[lookX, lookY]) continue; + + distance = Math.Max(Math.Abs(i), Math.Abs(j)); + + _target = color * (float)(distance > propagation - 3 ? Math.Pow((i != 0 && j != 0) ? _diagonalDropoff : _normalDropoff, distance - (propagation - 3)) : 1); + + if (!_lightColors[lookX, lookY].Equals(LightColor.black)) + { + _target.R = MathF.Max(_target.R, _lightColors[lookX, lookY].R); + _target.G = MathF.Max(_target.G, _lightColors[lookX, lookY].G); + _target.B = MathF.Max(_target.B, _lightColors[lookX, lookY].B); + } + + _lightColors[lookX, lookY] = _target; + } + } + } + + private Color[] _colorMap; + public void CreateColorMap() { + _colorMap = new Color[Width * Height]; + + for (int y = 0; y < Height; y++) + for (int x = 0; x < Width; x++) + _colorMap[y * Width + x] = _lightColors[x, y].Color; + } + + public Color[] GetColors() => _colorMap; + public int GetColorCount() => _colorMap.Length; + } + + public struct LightColor { + public static LightColor black = new LightColor(0, 0, 0); + public static LightColor white = new LightColor(255f, 255f, 255f); + public static LightColor ambient = new LightColor(255f, 255f, 255f); + public static LightColor cave = new LightColor(40f, 40f, 40f); + + public float R; + public float G; + public float B; + + public Color Color => new Color(R / 255f, G / 255f, B / 255f); + + public LightColor(float r, float g, float b) { + R = Math.Clamp(r, 0, 255f); + G = Math.Clamp(g, 0, 255f); + B = Math.Clamp(b, 0, 255f); + } + + public bool IsCutoff(float cutoff) => R > cutoff || G > cutoff || B > cutoff; + public bool Overpowers(LightColor other) => R > other.R || G > other.G || B > other.B; + public bool Equals(LightColor other) => R == other.R && G == other.G && B == other.B; + public static LightColor FromColor(Color color) => new LightColor(color.R, color.G, color.B); + + public static LightColor operator *(LightColor a, LightColor b) { + a.R *= b.R; + a.G *= b.G; + a.B *= b.B; + return a; + } + + public static LightColor operator *(LightColor a, float multiplier) { + a.R *= multiplier; + a.G *= multiplier; + a.B *= multiplier; + return a; + } + } +}
\ No newline at end of file |
