summaryrefslogtreecommitdiff
path: root/source/graphics
diff options
context:
space:
mode:
authorhazel <hazel@hazelthats.me>2026-01-26 22:04:39 +0100
committerhazel <hazel@hazelthats.me>2026-01-26 22:04:39 +0100
commit567c422f8cd42eba2437f9a8c2522716a1649be7 (patch)
tree93c5b296f3b7c14b626d0aadf5cad37764c41c74 /source/graphics
downloadcelesteia-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.cs63
-rw-r--r--source/graphics/GraphicsManager.cs65
-rw-r--r--source/graphics/lighting/LightMap.cs139
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