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