1 /*
2 Copyright (c) 2017-2018 Timur Gafarov
3 
4 Boost Software License - Version 1.0 - August 17th, 2003
5 Permission is hereby granted, free of charge, to any person or organization
6 obtaining a copy of the software and accompanying documentation covered by
7 this license (the "Software") to use, reproduce, display, distribute,
8 execute, and transmit the Software, and to prepare derivative works of the
9 Software, and to permit third-parties to whom the Software is furnished to
10 do so, all subject to the following:
11 
12 The copyright notices in the Software and this entire statement, including
13 the above license grant, this restriction and the following disclaimer,
14 must be included in all copies of the Software, in whole or in part, and
15 all derivative works of the Software, unless such copies or derivative
16 works are solely in the form of machine-executable object code generated by
17 a source language processor.
18 
19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
22 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
23 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
24 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 DEALINGS IN THE SOFTWARE.
26 */
27 
28 module dagon.graphics.clustered;
29 
30 import std.stdio;
31 import std.math;
32 import std.conv;
33 import std.random;
34 
35 import dlib.core.memory;
36 import dlib.math.vector;
37 import dlib.container.array;
38 import dlib.image.color;
39 import dlib.geometry.frustum;
40 
41 import derelict.opengl;
42 
43 import dagon.core.ownership;
44 import dagon.graphics.view;
45 import dagon.graphics.rc;
46 import dagon.logics.entity;
47 import dagon.logics.behaviour;
48 
49 float clampf(float x, float mi, float ma)
50 {
51     if (x < mi) return mi;
52     else if (x > ma) return ma;
53     else return x;
54 }
55 
56 struct Box
57 {
58     Vector2f pmin;
59     Vector2f pmax;
60 }
61 
62 struct Circle
63 {
64     Vector2f center;
65     float radius;
66 }
67 
68 Box cellBox(uint x, uint y, float cellSize, float domainWidth)
69 {
70     Box b;
71     b.pmin = Vector2f(x, y) * cellSize - domainWidth * 0.5f;
72     b.pmax = b.pmin + cellSize;
73     return b;
74 }
75 
76 bool circleBoxIsec(Circle c, Box b)
77 {
78     if (c.center.x > b.pmin.x && c.center.x < b.pmax.x &&
79         c.center.y > b.pmin.y && c.center.y < b.pmax.y)
80         return true; // sphere's center is inside the box
81 
82     Vector2f closest = c.center;
83     for (int i = 0; i < 2; i++)
84     {
85         float v = c.center[i];
86         if (v < b.pmin[i]) v = b.pmin[i];
87         if (v > b.pmax[i]) v = b.pmax[i];
88         closest[i] = v;
89     }
90 
91     return (distance(c.center, closest) <= c.radius);
92 }
93 
94 class LightSource
95 {
96     Vector3f position;
97     Vector3f color;
98     float radius; // max light attenuation radius
99     float areaRadius; // light's own radius
100     float energy;
101     
102     this(Vector3f pos, Vector3f col, float attRadius, float areaRadius, float energy)
103     {
104         this.position = pos;
105         this.color = col;
106         this.radius = attRadius;
107         this.areaRadius = areaRadius;
108         this.energy = energy;
109     }
110 }
111 
112 enum uint maxLightsPerNode = 8;
113 
114 struct LightCluster
115 {
116     Box box;
117     
118     uint[maxLightsPerNode] lights;
119     uint numLights = 0;
120 }
121 
122 // TODO: move this to dlib.geometry.frustum
123 bool frustumIntersectsSphere(ref Frustum f, Vector3f center, float radius)
124 {
125 	float d;
126 
127 	foreach(i, ref p; f.planes)
128     {
129 		// find the signed distance to this plane
130 		d = p.distance(center);
131 
132 		// if this distance is > sphere.radius, we are outside
133 		if (d > radius)
134 			return false;
135 	}
136 
137 	return true;
138 }
139 
140 class ClusteredLightManager: Owner
141 {    
142     DynamicArray!LightSource lightSources;
143     LightCluster[] clusterData;
144     
145     uint[] clusters;
146     Vector3f[] lights;
147     uint[] lightIndices;
148     
149     Vector3f position;
150     
151     Vector2f clustersPosition;
152     float sceneSize = 200.0f;
153     float invSceneSize;
154     float clusterSize;
155     uint domainSize = 100;
156     
157     uint numLightAttributes = 4;
158     
159     uint maxNumLights;
160     uint maxNumIndices = 2048;
161     
162     uint currentlyVisibleLights = 0;
163     
164     GLuint clusterTexture;
165     GLuint lightTexture;
166     GLuint indexTexture;
167     
168     this(float sceneSize, uint numClusters, Owner o)
169     {
170         super(o);
171         
172         position = Vector3f(0, 0, 0);
173         
174         this.sceneSize = sceneSize;
175         this.domainSize = numClusters;
176         
177         invSceneSize = 1.0f / sceneSize;
178         
179         clusterSize = sceneSize / cast(float)domainSize;
180         clustersPosition = Vector2f(-sceneSize * 0.5f, -sceneSize * 0.5f);
181         clusterData = New!(LightCluster[])(domainSize * domainSize);
182         
183         foreach(y; 0..domainSize)
184         foreach(x; 0..domainSize)
185         {
186             LightCluster* c = &clusterData[y * domainSize + x];
187             c.box = cellBox(x, y, clusterSize, sceneSize);
188             c.numLights = 0;
189         }
190 
191         clusters = New!(uint[])(domainSize * domainSize);
192         
193         foreach(ref c; clusters)
194             c = 0;
195             
196         maxNumLights = maxNumIndices / maxLightsPerNode;
197         
198         lights = New!(Vector3f[])(maxNumLights * numLightAttributes);
199         foreach(ref l; lights)
200             l = Vector3f(0, 0, 0);
201 
202         lightIndices = New!(uint[])(maxNumIndices);
203         
204         // 2D texture to store light clusters
205         glGenTextures(1, &clusterTexture);
206         glActiveTexture(GL_TEXTURE0);
207         glBindTexture(GL_TEXTURE_2D, clusterTexture);
208         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
209         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
210         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
211         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
212         glTexImage2D(GL_TEXTURE_2D, 0, GL_R32UI, domainSize, domainSize, 0, GL_RED_INTEGER, GL_UNSIGNED_INT, clusters.ptr);
213         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
214         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
215         glBindTexture(GL_TEXTURE_2D, 0);
216 
217         // 2D texture to store light data
218         glGenTextures(1, &lightTexture);
219         glBindTexture(GL_TEXTURE_2D, lightTexture);
220         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
221         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
222         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
223         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
224         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, maxNumLights, numLightAttributes, 0, GL_RGB, GL_FLOAT, lights.ptr);
225         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
226         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
227         glBindTexture(GL_TEXTURE_2D, 0);
228         
229         // 1D texture to store light indices per cluster
230         glGenTextures(1, &indexTexture);
231         glBindTexture(GL_TEXTURE_1D, indexTexture);
232         glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
233         glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
234         glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
235         glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
236         glTexImage1D(GL_TEXTURE_1D, 0, GL_R32UI, maxNumIndices, 0, GL_RED_INTEGER, GL_UNSIGNED_INT, lightIndices.ptr);
237         glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_BASE_LEVEL, 0);
238         glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAX_LEVEL, 0);
239         glBindTexture(GL_TEXTURE_1D, 0);
240     }
241     
242     ~this()
243     {
244         if (glIsTexture(clusterTexture))
245             glDeleteTextures(1, &clusterTexture);
246             
247         if (glIsTexture(lightTexture))
248             glDeleteTextures(1, &lightTexture);
249             
250         if (glIsTexture(indexTexture))
251             glDeleteTextures(1, &indexTexture);
252     
253         Delete(clusters);
254         Delete(lights);
255         Delete(lightIndices);
256         
257         foreach(light; lightSources)
258             Delete(light);
259         
260         lightSources.free();
261         Delete(clusterData);
262     }
263     
264     LightSource addLight(Vector3f position, Color4f color, float energy, float radius, float areaRadius = 0.0f)
265     {        
266         lightSources.append(New!LightSource(position, color.rgb, radius, areaRadius, energy));
267         
268         if (lightSources.length >= maxNumLights)
269             writeln("Warning: lights number exceeds index buffer capability (", maxNumLights, ")");
270         
271         return lightSources.data[$-1];
272     }
273     
274     void bindClusterTexture()
275     {
276         glBindTexture(GL_TEXTURE_2D, clusterTexture);
277     }
278     void unbindClusterTexture()
279     {
280         glBindTexture(GL_TEXTURE_2D, 0);
281     }
282     
283     void bindLightTexture()
284     {
285         glBindTexture(GL_TEXTURE_2D, lightTexture);
286     }
287     void unbindLightTexture()
288     {
289         glBindTexture(GL_TEXTURE_2D, 0);
290     }
291     
292     void bindIndexTexture()
293     {
294         glBindTexture(GL_TEXTURE_1D, indexTexture);
295     }
296     void unbindIndexTexture()
297     {
298         glBindTexture(GL_TEXTURE_1D, 0);
299     }
300     
301     void update(RenderingContext* rc)
302     {
303         position = rc.cameraPosition;
304     
305         foreach(ref v; clusters)
306             v = 0;
307             
308         foreach(ref c; clusterData)
309         {        
310             c.numLights = 0;
311         }
312         
313         currentlyVisibleLights = 0;
314 
315         foreach(i, light; lightSources.data)
316         {
317             if (frustumIntersectsSphere(rc.frustum, light.position, light.radius))
318             {
319                 currentlyVisibleLights++;
320                 
321                 if (currentlyVisibleLights < maxNumLights)
322                 {
323                     uint index = currentlyVisibleLights - 1;
324                     
325                     lights[index] = light.position;
326                     lights[maxNumLights + index] = light.color;
327                     lights[maxNumLights * 2 + index] = Vector3f(light.radius, light.areaRadius, light.energy);
328 
329                     Vector3f lightPosLocal = light.position - position;
330                     Vector2f lightPosXZ = Vector2f(lightPosLocal.x, lightPosLocal.z);
331                     Circle lightCircle = Circle(lightPosXZ, light.radius);
332                     
333                     uint x1 = cast(uint)clampf(floor((lightCircle.center.x - lightCircle.radius + sceneSize * 0.5f) / clusterSize), 0, domainSize-1);
334                     uint y1 = cast(uint)clampf(floor((lightCircle.center.y - lightCircle.radius + sceneSize * 0.5f) / clusterSize), 0, domainSize-1);
335                     uint x2 = cast(uint)clampf(x1 + ceil(lightCircle.radius + lightCircle.radius) + 1, 0, domainSize-1);
336                     uint y2 = cast(uint)clampf(y1 + ceil(lightCircle.radius + lightCircle.radius) + 1, 0, domainSize-1);
337 
338                     foreach(y; y1..y2)
339                     foreach(x; x1..x2)
340                     {
341                         Box b = cellBox(x, y, clusterSize, sceneSize);
342                         if (circleBoxIsec(lightCircle, b))
343                         {
344                             auto c = &clusterData[y * domainSize + x];
345                             if (c.numLights < maxLightsPerNode)
346                             {
347                                 c.lights[c.numLights] = index;
348                                 c.numLights = c.numLights + 1;
349                             }
350                         }
351                     }
352                 }
353                 else
354                     break;
355             }
356         }
357         
358         uint offset = 0;
359         foreach(ci, ref c; clusterData)
360         if (offset < maxNumIndices)
361         {
362             if (offset + c.numLights > maxNumIndices)
363                 break;
364         
365             if (c.numLights)
366             {                
367                 foreach(i; 0..c.numLights)
368                     lightIndices[offset + cast(uint)i] = c.lights[i];
369 
370                 clusters[ci] = offset | (c.numLights << 16);
371                 
372                 offset += c.numLights;
373             }
374             else
375             {
376                 clusters[ci] = 0;
377             }
378         }
379 
380         glBindTexture(GL_TEXTURE_2D, clusterTexture);
381         glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, domainSize, domainSize, GL_RED_INTEGER, GL_UNSIGNED_INT, clusters.ptr);
382         glBindTexture(GL_TEXTURE_2D, 0);
383         
384         glBindTexture(GL_TEXTURE_2D, lightTexture);
385         glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, maxNumLights, numLightAttributes, GL_RGB, GL_FLOAT, lights.ptr);
386         glBindTexture(GL_TEXTURE_2D, 0);
387         
388         glBindTexture(GL_TEXTURE_1D, indexTexture);
389         glTexSubImage1D(GL_TEXTURE_1D, 0, 0, maxNumIndices, GL_RED_INTEGER, GL_UNSIGNED_INT, lightIndices.ptr);
390         glBindTexture(GL_TEXTURE_1D, 0);
391     }
392 }
393 
394 // Attach a light to Entity
395 class LightBehaviour: Behaviour
396 {
397     LightSource light;
398 
399     this(Entity e, LightSource light)
400     {
401         super(e);
402         
403         this.light = light;
404     }
405 
406     override void update(double dt)
407     {
408         light.position = entity.position;
409     }
410 }