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.particles;
29 
30 import std.random;
31 import std.algorithm;
32 
33 import dlib.core.memory;
34 import dlib.math.vector;
35 import dlib.math.matrix;
36 import dlib.math.transformation;
37 import dlib.math.interpolation;
38 import dlib.math.utils;
39 import dlib.image.color;
40 import dlib.container.array;
41 
42 import derelict.opengl;
43 import dagon.logics.behaviour;
44 import dagon.logics.entity;
45 import dagon.graphics.texture;
46 import dagon.graphics.view;
47 import dagon.graphics.rc;
48 import dagon.graphics.material;
49 import dagon.graphics.materials.generic;
50 import dagon.graphics.mesh;
51 
52 struct Particle
53 {
54     Color4f startColor;
55     Color4f color;
56     Vector3f position;
57     Vector3f acceleration;
58     Vector3f velocity;
59     Vector3f gravityVector;
60     Vector2f scale;
61     float lifetime;
62     float time;
63     bool move;
64     bool active;
65 }
66 
67 abstract class ForceField: Behaviour
68 {
69     this(Entity e, ParticleSystem psys)
70     {
71         super(e);
72         psys.addForceField(this);
73     }
74 
75     void upadte(double dt)
76     {
77     }
78 
79     void affect(ref Particle p);
80 }
81 
82 class Attractor: ForceField
83 {
84     float g;
85 
86     this(Entity e, ParticleSystem psys, float magnitude)
87     {
88         super(e, psys);
89         g = magnitude;
90     }
91 
92     override void affect(ref Particle p)
93     {
94         Vector3f r = p.position - entity.position;
95         float d = max(EPSILON, r.length);
96         p.acceleration += r * -g / (d * d);
97     }
98 }
99 
100 class Deflector: ForceField
101 {
102     float g;
103 
104     this(Entity e, ParticleSystem psys, float magnitude)
105     {
106         super(e, psys);
107         g = magnitude;
108     }
109 
110     override void affect(ref Particle p)
111     {
112         Vector3f r = p.position - entity.position;
113         float d = max(EPSILON, r.length);
114         p.acceleration += r * g / (d * d);
115     }
116 }
117 
118 class Vortex: ForceField
119 {
120     float g1;
121     float g2;
122 
123     this(Entity e, ParticleSystem psys, float tangentMagnitude, float normalMagnitude)
124     {
125         super(e, psys);
126         g1 = tangentMagnitude;
127         g2 = normalMagnitude;
128     }
129 
130     override void affect(ref Particle p)
131     {
132         Vector3f direction = entity.transformation.forward;
133         float proj = dot(p.position, direction);
134         Vector3f pos = entity.position + direction * proj;
135         Vector3f r = p.position - pos;
136         float d = max(EPSILON, r.length);
137         Vector3f t = lerp(r, cross(r, direction), 0.25f);
138         p.acceleration += direction * g2 - t * g1 / (d * d);
139     }
140 }
141 
142 class BlackHole: ForceField
143 {
144     float g;
145 
146     this(Entity e, ParticleSystem psys, float magnitude)
147     {
148         super(e, psys);
149         g = magnitude;
150     }
151 
152     override void affect(ref Particle p)
153     {
154         Vector3f r = p.position - entity.position;
155         float d = r.length;
156         if (d <= 0.001f)
157             p.time = p.lifetime;
158         else
159             p.acceleration += r * -g / (d * d);
160     }
161 }
162 
163 class ColorChanger: ForceField
164 {
165     Color4f color;
166     float outerRadius;
167     float innerRadius;
168 
169     this(Entity e, ParticleSystem psys, Color4f color, float outerRadius, float innerRadius)
170     {
171         super(e, psys);
172         this.color = color;
173         this.outerRadius = outerRadius;
174         this.innerRadius = innerRadius;
175     }
176 
177     override void affect(ref Particle p)
178     {
179         Vector3f r = p.position - entity.position;
180         float t = clamp((r.length - innerRadius) / outerRadius, 0.0f, 1.0f);
181         p.color = lerp(color, p.color, t);
182     }
183 }
184 
185 class ParticleSystem: Behaviour
186 {
187     Particle[] particles;
188     DynamicArray!ForceField forceFields;
189     
190     float airFrictionDamping = 0.98f;
191     
192     float minLifetime = 1.0f;
193     float maxLifetime = 3.0f;
194     
195     float minSize = 0.25f;
196     float maxSize = 1.0f;
197     Vector2f scaleStep = Vector2f(0, 0);
198     
199     float initialPositionRandomRadius = 0.0f;
200     
201     float minInitialSpeed = 1.0f;
202     float maxInitialSpeed = 5.0f;
203     
204     Vector3f initialDirection = Vector3f(0, 1, 0);
205     float initialDirectionRandomFactor = 1.0f;
206     
207     Color4f startColor = Color4f(1, 1, 0, 1);
208     Color4f endColor = Color4f(1, 0, 0, 0);
209     
210     bool emitting = true;
211     
212     bool haveParticlesToDraw = false;
213     
214     Vector3f[4] vertices;
215     Vector2f[4] texcoords;
216     uint[3][2] indices;
217     
218     GLuint vao = 0;
219     GLuint vbo = 0;
220     GLuint tbo = 0;
221     GLuint eao = 0;
222     
223     Matrix4x4f invViewMatRot;
224     
225     GenericMaterial material;
226 
227     this(Entity e, uint numParticles)
228     {
229         super(e);
230         
231         particles = New!(Particle[])(numParticles);
232         foreach(ref p; particles)
233         {
234             resetParticle(p);
235         }
236         
237         vertices[0] = Vector3f(-0.5f, 0.5f, 0);
238         vertices[1] = Vector3f(-0.5f, -0.5f, 0);
239         vertices[2] = Vector3f(0.5f, -0.5f, 0);
240         vertices[3] = Vector3f(0.5f, 0.5f, 0);
241         
242         texcoords[0] = Vector2f(0, 0);
243         texcoords[1] = Vector2f(0, 1);
244         texcoords[2] = Vector2f(1, 1);
245         texcoords[3] = Vector2f(1, 0);
246         
247         indices[0][0] = 0;
248         indices[0][1] = 1;
249         indices[0][2] = 2;
250         
251         indices[1][0] = 0;
252         indices[1][1] = 2;
253         indices[1][2] = 3;
254         
255         glGenBuffers(1, &vbo);
256         glBindBuffer(GL_ARRAY_BUFFER, vbo);
257         glBufferData(GL_ARRAY_BUFFER, vertices.length * float.sizeof * 3, vertices.ptr, GL_STATIC_DRAW); 
258 
259         glGenBuffers(1, &tbo);
260         glBindBuffer(GL_ARRAY_BUFFER, tbo);
261         glBufferData(GL_ARRAY_BUFFER, texcoords.length * float.sizeof * 2, texcoords.ptr, GL_STATIC_DRAW);
262 
263         glGenBuffers(1, &eao);
264         glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eao);
265         glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.length * uint.sizeof * 3, indices.ptr, GL_STATIC_DRAW);
266 
267         glGenVertexArrays(1, &vao);
268         glBindVertexArray(vao);
269         glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eao);
270     
271         glEnableVertexAttribArray(VertexAttrib.Vertices);
272         glBindBuffer(GL_ARRAY_BUFFER, vbo);
273         glVertexAttribPointer(VertexAttrib.Vertices, 3, GL_FLOAT, GL_FALSE, 0, null);
274     
275         glEnableVertexAttribArray(VertexAttrib.Texcoords);
276         glBindBuffer(GL_ARRAY_BUFFER, tbo);
277         glVertexAttribPointer(VertexAttrib.Texcoords, 2, GL_FLOAT, GL_FALSE, 0, null);
278 
279         glBindVertexArray(0);
280     }
281 
282     ~this()
283     {
284         Delete(particles);
285         forceFields.free();
286     }
287     
288     void addForceField(ForceField ff)
289     {
290         forceFields.append(ff);
291     }
292 
293     void resetParticle(ref Particle p)
294     {
295         if (initialPositionRandomRadius > 0.0f)
296         {
297             float randomDist = uniform(0.0f, initialPositionRandomRadius);
298             p.position = entity.absolutePosition + randomUnitVector3!float * randomDist;
299         }
300         else
301             p.position = entity.absolutePosition;
302         Vector3f r = randomUnitVector3!float;
303 
304         float initialSpeed;
305         if (maxInitialSpeed > minInitialSpeed)
306             initialSpeed = uniform(minInitialSpeed, maxInitialSpeed);
307         else
308             initialSpeed = maxInitialSpeed;
309         p.velocity = lerp(initialDirection, r, initialDirectionRandomFactor) * initialSpeed;
310         
311         if (maxLifetime > minLifetime)
312             p.lifetime = uniform(minLifetime, maxLifetime);
313         else
314             p.lifetime = maxLifetime;
315         p.gravityVector = Vector3f(0, -1, 0);
316         
317         float s;
318         if (maxSize > maxSize)
319             s = uniform(maxSize, maxSize);
320         else
321             s = maxSize;
322             
323         p.scale = Vector2f(s, s);
324         p.time = 0.0f;
325         p.move = true;
326         p.startColor = startColor;
327         p.color = p.startColor;
328     }
329     
330     void updateParticle(ref Particle p, double dt)
331     {
332         p.time += dt;
333         
334         float t = p.time / p.lifetime;
335         p.color = lerp(startColor, endColor, t);
336         p.scale = p.scale + scaleStep * dt;
337         
338         if (p.move)
339         {
340             p.acceleration = Vector3f(0, 0, 0);
341                
342             foreach(ref ff; forceFields)
343             {
344                 ff.affect(p);
345             }
346                 
347             p.velocity += p.acceleration * dt;
348             p.velocity = p.velocity * airFrictionDamping;
349 
350             p.position += p.velocity * dt;
351         }
352         
353         p.color.a = lerp(startColor.a, endColor.a, t);
354     }
355     
356     override void update(double dt)
357     {
358         haveParticlesToDraw = false;
359         foreach(ref p; particles)
360         {
361             if (p.active)
362             {
363                 if (p.time < p.lifetime)
364                 {
365                     updateParticle(p, dt);
366                     haveParticlesToDraw = true;
367                 }
368                 else
369                     p.active = false;
370             }
371             else if (emitting)
372             {
373                 resetParticle(p);
374                 p.active = true;
375             }
376         }
377     }
378     
379     override void render(RenderingContext* rc)
380     {        
381         if (haveParticlesToDraw)
382         {            
383             foreach(ref p; particles)
384             if (p.time < p.lifetime)
385             {
386                 Matrix4x4f modelViewMatrix = 
387                     rc.viewMatrix * 
388                     translationMatrix(p.position) * 
389                     rc.invViewRotationMatrix * 
390                     scaleMatrix(Vector3f(p.scale.x, p.scale.y, 1.0f));
391                 
392                 RenderingContext rcLocal = *rc;
393                 rcLocal.modelViewMatrix = modelViewMatrix;
394 
395                 if (material)
396                 {
397                     material.particleColor = p.color;
398                     material.bind(&rcLocal);
399                 }
400                 
401                 glBindVertexArray(vao);
402                 glDrawElements(GL_TRIANGLES, cast(uint)indices.length * 3, GL_UNSIGNED_INT, cast(void*)0);
403                 glBindVertexArray(0);
404         
405                 if (material)
406                     material.unbind(&rcLocal);
407             }
408         }
409     }
410 }