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