aboutsummaryrefslogtreecommitdiff
path: root/source/graphics/lighting/LightMap.cs
blob: 4fb3b9d1c8b0b3473e298807452d6d4b35d27afc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
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;
        }
    }
}