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