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 }