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 double lifetime; 64 double 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 double minLifetime = 1.0; 193 double maxLifetime = 3.0; 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 bool shouldRender = true; 422 if (rc.shadowPass) 423 shouldRender = e.entity.castShadow; 424 425 if (shouldRender) 426 { 427 if (e.material) 428 e.entity.material = e.material; 429 430 foreach(ref p; e.particles) 431 if (p.time < p.lifetime) 432 { 433 if (e.particleEntity) 434 renderEntityParticle(e, p, rc); 435 else 436 renderBillboardParticle(e, p, rc); 437 } 438 } 439 } 440 } 441 } 442 443 void renderEntityParticle(Emitter e, ref Particle p, RenderingContext* rc) 444 { 445 auto rcLocal = *rc; 446 447 Matrix4x4f trans = 448 translationMatrix(p.position); 449 450 Matrix4x4f prevTrans = 451 translationMatrix(p.positionPrev); 452 453 auto absTrans = e.particleEntity.absoluteTransformation; 454 auto invAbsTrans = e.particleEntity.invAbsoluteTransformation; 455 auto prevAbsTrans = e.particleEntity.prevAbsoluteTransformation; 456 457 e.particleEntity.absoluteTransformation = trans; 458 e.particleEntity.invAbsoluteTransformation = trans.inverse; 459 e.particleEntity.prevAbsoluteTransformation = prevTrans; 460 461 foreach(child; e.particleEntity.children) 462 { 463 child.updateTransformation(0.0); 464 } 465 466 e.particleEntity.render(&rcLocal); 467 468 e.particleEntity.absoluteTransformation = absTrans; 469 e.particleEntity.invAbsoluteTransformation = invAbsTrans; 470 e.particleEntity.prevAbsoluteTransformation = prevAbsTrans; 471 } 472 473 void renderBillboardParticle(Emitter e, ref Particle p, RenderingContext* rc) 474 { 475 Matrix4x4f trans = translationMatrix(p.position); 476 Matrix4x4f prevTrans = translationMatrix(p.positionPrev); 477 478 Matrix4x4f modelViewMatrix = 479 rc.viewMatrix * 480 translationMatrix(p.position) * 481 rc.invViewRotationMatrix * 482 scaleMatrix(Vector3f(p.scale.x, p.scale.y, 1.0f)); 483 484 RenderingContext rcLocal = *rc; 485 rcLocal.modelViewMatrix = modelViewMatrix; 486 rcLocal.normalMatrix = rcLocal.modelViewMatrix.inverse.transposed; 487 488 if (useMotionBlur) 489 rcLocal.prevModelViewProjMatrix = rcLocal.projectionMatrix * (rcLocal.prevViewMatrix * prevTrans); 490 else 491 rcLocal.prevModelViewProjMatrix = rcLocal.projectionMatrix * (rcLocal.viewMatrix * trans); 492 493 rcLocal.blurModelViewProjMatrix = rcLocal.projectionMatrix * (rcLocal.viewMatrix * trans); 494 495 if (e.material) 496 { 497 e.material.particleColor = p.color; 498 e.material.bind(&rcLocal); 499 } 500 501 glBindVertexArray(vao); 502 glDrawElements(GL_TRIANGLES, cast(uint)indices.length * 3, GL_UNSIGNED_INT, cast(void*)0); 503 glBindVertexArray(0); 504 505 if (e.material) 506 e.material.unbind(&rcLocal); 507 } 508 }