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 }