1 module re.gfx.lighting;
2 
3 import re.core;
4 import re.ecs;
5 import re.ng.manager;
6 import re.ng.scene3d;
7 import re.gfx;
8 import re.math;
9 import std.algorithm;
10 import std.container.array;
11 static import raylib;
12 
13 /// acts as a manager for Light3D components
14 class SceneLightManager : Manager, Updatable {
15     /// max lights supported by shader
16     private enum max_lights = 4;
17     private Array!(ShaderLight) _lights;
18     private Array!Light3D _comps;
19     private int light_count;
20 
21     /// the lighting shader
22     public Shader shader;
23 
24     private enum ShaderLightType {
25         LIGHT_DIRECTIONAL,
26         LIGHT_POINT
27     }
28 
29     private struct ShaderLight {
30         int type;
31         Vector3 position;
32         Vector3 target;
33         Color color;
34         bool enabled;
35 
36         // Shader locations
37         int enabledLoc;
38         int typeLoc;
39         int posLoc;
40         int targetLoc;
41         int colorLoc;
42     }
43 
44     this() {
45         // load the shader
46         shader = Core.content.load_shader("shader/basic_lighting.vert",
47                 "shader/basic_lighting.frag");
48         // get some shader locations
49         shader.locs[raylib.ShaderLocationIndex.LOC_MATRIX_MODEL] = raylib.GetShaderLocation(shader,
50                 "matModel");
51         shader.locs[raylib.ShaderLocationIndex.LOC_VECTOR_VIEW] = raylib.GetShaderLocation(shader,
52                 "viewPos");
53 
54         // ambient light level
55         auto ambient_loc = raylib.GetShaderLocation(shader, "ambient");
56         immutable auto col_ambient = 0.4;
57         float[4] ambient_val = [col_ambient, col_ambient, col_ambient, 1];
58         raylib.SetShaderValue(shader, ambient_loc, &ambient_val,
59                 raylib.ShaderUniformDataType.UNIFORM_VEC4);
60 
61         // specular shine
62         auto shine_loc = raylib.GetShaderLocation(shader, "shine");
63         float shine_amt = 16.0;
64         raylib.SetShaderValue(shader, shine_loc, &shine_amt,
65                 raylib.ShaderUniformDataType.UNIFORM_FLOAT);
66 
67         _lights.reserve(max_lights);
68         _comps.reserve(max_lights);
69     }
70 
71     override void update() {
72         // update camera view pos in light shader
73         float[3] camera_pos = [
74             (cast(Scene3D) scene).cam.transform.position.x, (cast(Scene3D) scene)
75             .cam.transform.position.y, (cast(Scene3D) scene).cam.transform.position.z
76         ];
77         raylib.SetShaderValue(shader, shader.locs[raylib.ShaderLocationIndex.LOC_VECTOR_VIEW],
78                 &camera_pos, raylib.ShaderUniformDataType.UNIFORM_VEC3);
79 
80         // update lights
81         for (int i = 0; i < light_count; i++) {
82             // sync fields
83             _lights[i].position = _comps[i].transform.position;
84             _lights[i].color = _comps[i].color;
85             _lights[i].enabled = _comps[i].light_enabled;
86 
87             // update shader values
88             update_shader_lights(shader, _lights[i]);
89         }
90     }
91 
92     override void destroy() {
93         while (light_count > 0) {
94             unregister(_comps[0]);
95         }
96     }
97 
98     private void register(Light3D light_comp) {
99         assert(light_count < max_lights, "maximum light count exceeded.");
100         // add a light
101         _lights.insertBack(set_light(light_count, ShaderLightType.LIGHT_POINT,
102                 light_comp.transform.position, Vector3Zero, light_comp.color, shader));
103         _comps.insertBack(light_comp);
104         // set internal light reference
105         light_comp._light = _lights[light_count];
106         light_count++;
107     }
108 
109     private void unregister(Light3D light_comp) {
110         import std.range : dropExactly, takeOne;
111 
112         auto removed_index = cast(int) _comps[].countUntil(light_comp);
113         // clear all lights
114         for (int i = 0; i < light_count; i++) {
115             clear_light(i, shader);
116         }
117         _comps.linearRemove(_comps[].dropExactly(removed_index).takeOne);
118         _lights.linearRemove(_lights[].dropExactly(removed_index).takeOne);
119         light_count--; // we're removing a light
120         // ensure our lengths match
121         assert(_lights.length == light_count);
122         assert(_lights.length == _comps.length);
123         // reactivate the lights
124         for (int i = 0; i < light_count; i++) {
125             // update shader
126             _lights[i] = set_light(i, ShaderLightType.LIGHT_POINT,
127                     _comps[i].transform.position, Vector3Zero, _comps[i].color, shader);
128             // set associated light
129             _comps[i]._light = _lights[i];
130         }
131     }
132 
133     // - ported from rlights
134 
135     private static ShaderLight set_light(int index, ShaderLightType type,
136             Vector3 pos, Vector3 target, Color color, Shader shader, bool enabled = true) {
137         ShaderLight light;
138 
139         light.enabled = enabled;
140         light.type = type;
141         light.position = pos;
142         light.target = target;
143         light.color = color;
144 
145         char[32] enabledName = "lights[x].enabled\0";
146         char[32] typeName = "lights[x].type\0";
147         char[32] posName = "lights[x].position\0";
148         char[32] targetName = "lights[x].target\0";
149         char[32] colorName = "lights[x].color\0";
150 
151         // Set location name [x] depending on lights count
152         enabledName[7] = cast(char)('0' + index);
153         typeName[7] = cast(char)('0' + index);
154         posName[7] = cast(char)('0' + index);
155         targetName[7] = cast(char)('0' + index);
156         colorName[7] = cast(char)('0' + index);
157 
158         light.enabledLoc = raylib.GetShaderLocation(shader, cast(char*) enabledName);
159         light.typeLoc = raylib.GetShaderLocation(shader, cast(char*) typeName);
160         light.posLoc = raylib.GetShaderLocation(shader, cast(char*) posName);
161         light.targetLoc = raylib.GetShaderLocation(shader, cast(char*) targetName);
162         light.colorLoc = raylib.GetShaderLocation(shader, cast(char*) colorName);
163 
164         update_shader_lights(shader, light);
165 
166         return light;
167     }
168 
169     private static void clear_light(int index, Shader shader) {
170         // reset the light
171         set_light(index, ShaderLightType.LIGHT_POINT, Vector3Zero, Vector3Zero,
172                 Colors.BLANK, shader, false);
173     }
174 
175     // Send light properties to shader
176     // NOTE: ShaderLight shader locations should be available 
177     private static void update_shader_lights(Shader shader, ShaderLight light) {
178         // Send to shader light enabled state and type
179         raylib.SetShaderValue(shader, light.enabledLoc, &light.enabled,
180                 raylib.ShaderUniformDataType.UNIFORM_INT);
181         raylib.SetShaderValue(shader, light.typeLoc, &light.type,
182                 raylib.ShaderUniformDataType.UNIFORM_INT);
183 
184         // Send to shader light position values
185         float[3] position = [
186             light.position.x, light.position.y, light.position.z
187         ];
188         raylib.SetShaderValue(shader, light.posLoc, &position,
189                 raylib.ShaderUniformDataType.UNIFORM_VEC3);
190 
191         // Send to shader light target position values
192         float[3] target = [light.target.x, light.target.y, light.target.z];
193         raylib.SetShaderValue(shader, light.targetLoc, &target,
194                 raylib.ShaderUniformDataType.UNIFORM_VEC3);
195 
196         // Send to shader light color values
197         float[4] color = [
198             cast(float) light.color.r / cast(float) 255,
199             cast(float) light.color.g / cast(float) 255,
200             cast(float) light.color.b / cast(float) 255,
201             cast(float) light.color.a / cast(float) 255
202         ];
203         raylib.SetShaderValue(shader, light.colorLoc, &color,
204                 raylib.ShaderUniformDataType.UNIFORM_VEC4);
205     }
206 }
207 
208 /// represents a 3D light
209 class Light3D : Component, Renderable3D {
210     mixin Reflect;
211     private SceneLightManager _mgr;
212     private enum phys_size = 0.2;
213     private SceneLightManager.ShaderLight _light;
214 
215     /// the color of the light
216     public Color color;
217 
218     /// whether the light is enabled
219     public bool light_enabled = true;
220 
221     /// creates a new light with a given color
222     this(Color color = Colors.WHITE) {
223         this.color = color;
224     }
225 
226     override void setup() {
227         // register the light in the manager
228         auto mgr = entity.scene.get_manager!SceneLightManager();
229         assert(!mgr.isNull, "scene did not have SceneLightManager registered."
230                 ~ "please add that to the scene before creating this component.");
231         _mgr = mgr.get;
232         _mgr.register(this);
233     }
234 
235     override void destroy() {
236         _mgr.unregister(this);
237     }
238 
239     @property BoundingBox bounds() {
240         auto size = Vector3(phys_size, phys_size, phys_size);
241         return BoundingBox(entity.position - size, entity.position + size);
242     }
243 
244     void render() {
245     }
246 
247     void debug_render() {
248         import re.ng.diag.render;
249 
250         raylib.DrawSphereEx(entity.position, phys_size, 8, 8, color);
251         raylib.DrawSphereWires(entity.position, phys_size * 1.5, 2, 2, DebugRender.debug_color);
252     }
253 }