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