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.core.ownership;
35 import dlib.math.vector;
36 import dlib.math.matrix;
37 import dlib.math.quaternion;
38 import dlib.math.transformation;
39 import dlib.math.interpolation;
40 import dlib.math.utils;
41 import dlib.image.color;
42 import dlib.container.array;
43 
44 import dagon.core.libs;
45 import dagon.logics.behaviour;
46 import dagon.logics.entity;
47 import dagon.graphics.texture;
48 import dagon.graphics.view;
49 import dagon.graphics.rc;
50 import dagon.graphics.material;
51 import dagon.graphics.mesh;
52 
53 struct Particle
54 {
55     Color4f startColor;
56     Color4f color;
57     Vector3f position;
58     Vector3f positionPrev;
59     Vector3f acceleration;
60     Vector3f velocity;
61     Vector3f gravityVector;
62     Vector3f scale;
63     float lifetime;
64     float time;
65     bool move;
66     bool active;
67 }
68 
69 abstract class ForceField: Behaviour
70 {
71     this(Entity e, ParticleSystem psys)
72     {
73         super(e);
74         psys.addForceField(this);
75     }
76 
77     void upadte(double dt)
78     {
79     }
80 
81     void affect(ref Particle p);
82 }
83 
84 class Attractor: ForceField
85 {
86     float g;
87 
88     this(Entity e, ParticleSystem psys, float magnitude)
89     {
90         super(e, psys);
91         g = magnitude;
92     }
93 
94     override void affect(ref Particle p)
95     {
96         Vector3f r = p.position - entity.position;
97         float d = max(EPSILON, r.length);
98         p.acceleration += r * -g / (d * d);
99     }
100 }
101 
102 class Deflector: ForceField
103 {
104     float g;
105 
106     this(Entity e, ParticleSystem psys, float magnitude)
107     {
108         super(e, psys);
109         g = magnitude;
110     }
111 
112     override void affect(ref Particle p)
113     {
114         Vector3f r = p.position - entity.position;
115         float d = max(EPSILON, r.length);
116         p.acceleration += r * g / (d * d);
117     }
118 }
119 
120 class Vortex: ForceField
121 {
122     float g1;
123     float g2;
124 
125     this(Entity e, ParticleSystem psys, float tangentMagnitude, float normalMagnitude)
126     {
127         super(e, psys);
128         g1 = tangentMagnitude;
129         g2 = normalMagnitude;
130     }
131 
132     override void affect(ref Particle p)
133     {
134         Vector3f direction = entity.transformation.forward;
135         float proj = dot(p.position, direction);
136         Vector3f pos = entity.position + direction * proj;
137         Vector3f r = p.position - pos;
138         float d = max(EPSILON, r.length);
139         Vector3f t = lerp(r, cross(r, direction), 0.25f);
140         p.acceleration += direction * g2 - t * g1 / (d * d);
141     }
142 }
143 
144 class BlackHole: ForceField
145 {
146     float g;
147 
148     this(Entity e, ParticleSystem psys, float magnitude)
149     {
150         super(e, psys);
151         g = magnitude;
152     }
153 
154     override void affect(ref Particle p)
155     {
156         Vector3f r = p.position - entity.position;
157         float d = r.length;
158         if (d <= 0.001f)
159             p.time = p.lifetime;
160         else
161             p.acceleration += r * -g / (d * d);
162     }
163 }
164 
165 class ColorChanger: ForceField
166 {
167     Color4f color;
168     float outerRadius;
169     float innerRadius;
170 
171     this(Entity e, ParticleSystem psys, Color4f color, float outerRadius, float innerRadius)
172     {
173         super(e, psys);
174         this.color = color;
175         this.outerRadius = outerRadius;
176         this.innerRadius = innerRadius;
177     }
178 
179     override void affect(ref Particle p)
180     {
181         Vector3f r = p.position - entity.position;
182         float t = clamp((r.length - innerRadius) / outerRadius, 0.0f, 1.0f);
183         p.color = lerp(color, p.color, t);
184     }
185 }
186 
187 
188 class Emitter: Behaviour
189 {
190     Particle[] particles;
191 
192     float minLifetime = 1.0f;
193     float maxLifetime = 3.0f;
194 
195     float minSize = 0.25f;
196     float maxSize = 1.0f;
197     Vector3f scaleStep = Vector3f(0, 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, 1, 1);
208     Color4f endColor = Color4f(1, 1, 1, 0);
209 
210     float airFrictionDamping = 0.98f;
211 
212     bool emitting = true;
213 
214     Material material;
215 
216     Entity particleEntity;
217 
218     this(Entity e, ParticleSystem psys, uint numParticles)
219     {
220         super(e);
221 
222         psys.addEmitter(this);
223 
224         particles = New!(Particle[])(numParticles);
225         foreach(ref p; particles)
226         {
227             resetParticle(p);
228         }
229     }
230 
231     ~this()
232     {
233         Delete(particles);
234     }
235 
236     void resetParticle(ref Particle p)
237     {
238         if (initialPositionRandomRadius > 0.0f)
239         {
240             float randomDist = uniform(0.0f, initialPositionRandomRadius);
241             p.position = entity.absolutePosition + randomUnitVector3!float * randomDist;
242         }
243         else
244             p.position = entity.absolutePosition;
245 
246         p.positionPrev = p.position;
247 
248         Vector3f r = randomUnitVector3!float;
249 
250         float initialSpeed;
251         if (maxInitialSpeed > minInitialSpeed)
252             initialSpeed = uniform(minInitialSpeed, maxInitialSpeed);
253         else
254             initialSpeed = maxInitialSpeed;
255         p.velocity = lerp(initialDirection, r, initialDirectionRandomFactor) * initialSpeed;
256 
257         if (maxLifetime > minLifetime)
258             p.lifetime = uniform(minLifetime, maxLifetime);
259         else
260             p.lifetime = maxLifetime;
261         p.gravityVector = Vector3f(0, -1, 0);
262 
263         float s;
264         if (maxSize > maxSize)
265             s = uniform(maxSize, maxSize);
266         else
267             s = maxSize;
268 
269         p.scale = Vector3f(s, s, s);
270         p.time = 0.0f;
271         p.move = true;
272         p.startColor = startColor;
273         p.color = p.startColor;
274     }
275 }
276 
277 class ParticleSystem: Owner
278 {
279     DynamicArray!Emitter emitters;
280     DynamicArray!ForceField forceFields;
281 
282     Vector3f[4] vertices;
283     Vector2f[4] texcoords;
284     uint[3][2] indices;
285 
286     GLuint vao = 0;
287     GLuint vbo = 0;
288     GLuint tbo = 0;
289     GLuint eao = 0;
290 
291     Matrix4x4f invViewMatRot;
292 
293     bool haveParticlesToDraw = false;
294 
295     bool useMotionBlur = true;
296 
297     this(Owner o)
298     {
299         super(o);
300 
301         vertices[0] = Vector3f(-0.5f, 0.5f, 0);
302         vertices[1] = Vector3f(-0.5f, -0.5f, 0);
303         vertices[2] = Vector3f(0.5f, -0.5f, 0);
304         vertices[3] = Vector3f(0.5f, 0.5f, 0);
305 
306         texcoords[0] = Vector2f(0, 0);
307         texcoords[1] = Vector2f(0, 1);
308         texcoords[2] = Vector2f(1, 1);
309         texcoords[3] = Vector2f(1, 0);
310 
311         indices[0][0] = 0;
312         indices[0][1] = 1;
313         indices[0][2] = 2;
314 
315         indices[1][0] = 0;
316         indices[1][1] = 2;
317         indices[1][2] = 3;
318 
319         glGenBuffers(1, &vbo);
320         glBindBuffer(GL_ARRAY_BUFFER, vbo);
321         glBufferData(GL_ARRAY_BUFFER, vertices.length * float.sizeof * 3, vertices.ptr, GL_STATIC_DRAW);
322 
323         glGenBuffers(1, &tbo);
324         glBindBuffer(GL_ARRAY_BUFFER, tbo);
325         glBufferData(GL_ARRAY_BUFFER, texcoords.length * float.sizeof * 2, texcoords.ptr, GL_STATIC_DRAW);
326 
327         glGenBuffers(1, &eao);
328         glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eao);
329         glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.length * uint.sizeof * 3, indices.ptr, GL_STATIC_DRAW);
330 
331         glGenVertexArrays(1, &vao);
332         glBindVertexArray(vao);
333         glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eao);
334 
335         glEnableVertexAttribArray(VertexAttrib.Vertices);
336         glBindBuffer(GL_ARRAY_BUFFER, vbo);
337         glVertexAttribPointer(VertexAttrib.Vertices, 3, GL_FLOAT, GL_FALSE, 0, null);
338 
339         glEnableVertexAttribArray(VertexAttrib.Texcoords);
340         glBindBuffer(GL_ARRAY_BUFFER, tbo);
341         glVertexAttribPointer(VertexAttrib.Texcoords, 2, GL_FLOAT, GL_FALSE, 0, null);
342 
343         glBindVertexArray(0);
344     }
345 
346     ~this()
347     {
348         emitters.free();
349         forceFields.free();
350     }
351 
352     void addForceField(ForceField ff)
353     {
354         forceFields.append(ff);
355     }
356 
357     void addEmitter(Emitter em)
358     {
359         emitters.append(em);
360     }
361 
362     void updateParticle(Emitter e, ref Particle p, double dt)
363     {
364         p.time += dt;
365 
366         float t = p.time / p.lifetime;
367         p.color = lerp(e.startColor, e.endColor, t);
368         p.scale = p.scale + e.scaleStep * dt;
369 
370         if (p.move)
371         {
372             p.acceleration = Vector3f(0, 0, 0);
373 
374             foreach(ref ff; forceFields)
375             {
376                 ff.affect(p);
377             }
378 
379             p.velocity += p.acceleration * dt;
380             p.velocity = p.velocity * e.airFrictionDamping;
381 
382             p.positionPrev = p.position;
383             p.position += p.velocity * dt;
384         }
385 
386         p.color.a = lerp(e.startColor.a, e.endColor.a, t);
387     }
388 
389     void update(double dt)
390     {
391         haveParticlesToDraw = false;
392 
393         foreach(e; emitters)
394         foreach(ref p; e.particles)
395         {
396             if (p.active)
397             {
398                 if (p.time < p.lifetime)
399                 {
400                     updateParticle(e, p, dt);
401                     haveParticlesToDraw = true;
402                 }
403                 else
404                     p.active = false;
405             }
406             else if (e.emitting)
407             {
408                 e.resetParticle(p);
409                 p.active = true;
410             }
411         }
412     }
413 
414     void render(RenderingContext* rc)
415     {
416         if (haveParticlesToDraw)
417         {
418             foreach(e; emitters)
419             if (e.entity.visible)
420             {
421                 if (e.material)
422                     e.entity.material = e.material;
423 
424                 foreach(ref p; e.particles)
425                 if (p.time < p.lifetime)
426                 {
427                     if (e.particleEntity)
428                         renderEntityParticle(e, p, rc);
429                     else
430                         renderBillboardParticle(e, p, rc);
431                 }
432             }
433         }
434     }
435 
436     void renderEntityParticle(Emitter e, ref Particle p, RenderingContext* rc)
437     {
438         auto rcLocal = *rc;
439 
440         Matrix4x4f trans =
441             translationMatrix(p.position);
442 
443         Matrix4x4f prevTrans =
444             translationMatrix(p.positionPrev);
445 
446         auto absTrans = e.particleEntity.absoluteTransformation;
447         auto invAbsTrans = e.particleEntity.invAbsoluteTransformation;
448         auto prevAbsTrans = e.particleEntity.prevAbsoluteTransformation;
449 
450         e.particleEntity.absoluteTransformation = trans;
451         e.particleEntity.invAbsoluteTransformation = trans.inverse;
452         e.particleEntity.prevAbsoluteTransformation = prevTrans;
453 
454         foreach(child; e.particleEntity.children)
455         {
456             child.updateTransformation();
457         }
458 
459         e.particleEntity.render(&rcLocal);
460 
461         e.particleEntity.absoluteTransformation = absTrans;
462         e.particleEntity.invAbsoluteTransformation = invAbsTrans;
463         e.particleEntity.prevAbsoluteTransformation = prevAbsTrans;
464     }
465 
466     void renderBillboardParticle(Emitter e, ref Particle p, RenderingContext* rc)
467     {
468         Matrix4x4f trans = translationMatrix(p.position);
469         Matrix4x4f prevTrans = translationMatrix(p.positionPrev);
470 
471         Matrix4x4f modelViewMatrix =
472             rc.viewMatrix *
473             translationMatrix(p.position) *
474             rc.invViewRotationMatrix *
475             scaleMatrix(Vector3f(p.scale.x, p.scale.y, 1.0f));
476 
477         RenderingContext rcLocal = *rc;
478         rcLocal.modelViewMatrix = modelViewMatrix;
479         rcLocal.normalMatrix = rcLocal.modelViewMatrix.inverse.transposed;
480 
481         if (useMotionBlur)
482             rcLocal.prevModelViewProjMatrix = rcLocal.projectionMatrix * (rcLocal.prevViewMatrix * prevTrans);
483         else
484             rcLocal.prevModelViewProjMatrix = rcLocal.projectionMatrix * (rcLocal.viewMatrix * trans);
485 
486         rcLocal.blurModelViewProjMatrix = rcLocal.projectionMatrix * (rcLocal.viewMatrix * trans);
487 
488         if (e.material)
489         {
490             e.material.particleColor = p.color;
491             e.material.bind(&rcLocal);
492         }
493 
494         glBindVertexArray(vao);
495         glDrawElements(GL_TRIANGLES, cast(uint)indices.length * 3, GL_UNSIGNED_INT, cast(void*)0);
496         glBindVertexArray(0);
497 
498         if (e.material)
499             e.material.unbind(&rcLocal);
500     }
501 }