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