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.math.vector; 35 import dlib.math.matrix; 36 import dlib.math.transformation; 37 import dlib.math.interpolation; 38 import dlib.math.utils; 39 import dlib.image.color; 40 import dlib.container.array; 41 42 import derelict.opengl; 43 import dagon.logics.behaviour; 44 import dagon.logics.entity; 45 import dagon.graphics.texture; 46 import dagon.graphics.view; 47 import dagon.graphics.rc; 48 import dagon.graphics.material; 49 import dagon.graphics.materials.generic; 50 import dagon.graphics.mesh; 51 52 struct Particle 53 { 54 Color4f startColor; 55 Color4f color; 56 Vector3f position; 57 Vector3f acceleration; 58 Vector3f velocity; 59 Vector3f gravityVector; 60 Vector2f scale; 61 float lifetime; 62 float time; 63 bool move; 64 bool active; 65 } 66 67 abstract class ForceField: Behaviour 68 { 69 this(Entity e, ParticleSystem psys) 70 { 71 super(e); 72 psys.addForceField(this); 73 } 74 75 void upadte(double dt) 76 { 77 } 78 79 void affect(ref Particle p); 80 } 81 82 class Attractor: ForceField 83 { 84 float g; 85 86 this(Entity e, ParticleSystem psys, float magnitude) 87 { 88 super(e, psys); 89 g = magnitude; 90 } 91 92 override void affect(ref Particle p) 93 { 94 Vector3f r = p.position - entity.position; 95 float d = max(EPSILON, r.length); 96 p.acceleration += r * -g / (d * d); 97 } 98 } 99 100 class Deflector: ForceField 101 { 102 float g; 103 104 this(Entity e, ParticleSystem psys, float magnitude) 105 { 106 super(e, psys); 107 g = magnitude; 108 } 109 110 override void affect(ref Particle p) 111 { 112 Vector3f r = p.position - entity.position; 113 float d = max(EPSILON, r.length); 114 p.acceleration += r * g / (d * d); 115 } 116 } 117 118 class Vortex: ForceField 119 { 120 float g1; 121 float g2; 122 123 this(Entity e, ParticleSystem psys, float tangentMagnitude, float normalMagnitude) 124 { 125 super(e, psys); 126 g1 = tangentMagnitude; 127 g2 = normalMagnitude; 128 } 129 130 override void affect(ref Particle p) 131 { 132 Vector3f direction = entity.transformation.forward; 133 float proj = dot(p.position, direction); 134 Vector3f pos = entity.position + direction * proj; 135 Vector3f r = p.position - pos; 136 float d = max(EPSILON, r.length); 137 Vector3f t = lerp(r, cross(r, direction), 0.25f); 138 p.acceleration += direction * g2 - t * g1 / (d * d); 139 } 140 } 141 142 class BlackHole: ForceField 143 { 144 float g; 145 146 this(Entity e, ParticleSystem psys, float magnitude) 147 { 148 super(e, psys); 149 g = magnitude; 150 } 151 152 override void affect(ref Particle p) 153 { 154 Vector3f r = p.position - entity.position; 155 float d = r.length; 156 if (d <= 0.001f) 157 p.time = p.lifetime; 158 else 159 p.acceleration += r * -g / (d * d); 160 } 161 } 162 163 class ColorChanger: ForceField 164 { 165 Color4f color; 166 float outerRadius; 167 float innerRadius; 168 169 this(Entity e, ParticleSystem psys, Color4f color, float outerRadius, float innerRadius) 170 { 171 super(e, psys); 172 this.color = color; 173 this.outerRadius = outerRadius; 174 this.innerRadius = innerRadius; 175 } 176 177 override void affect(ref Particle p) 178 { 179 Vector3f r = p.position - entity.position; 180 float t = clamp((r.length - innerRadius) / outerRadius, 0.0f, 1.0f); 181 p.color = lerp(color, p.color, t); 182 } 183 } 184 185 class ParticleSystem: Behaviour 186 { 187 Particle[] particles; 188 DynamicArray!ForceField forceFields; 189 190 float airFrictionDamping = 0.98f; 191 192 float minLifetime = 1.0f; 193 float maxLifetime = 3.0f; 194 195 float minSize = 0.25f; 196 float maxSize = 1.0f; 197 Vector2f scaleStep = Vector2f(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, 0, 1); 208 Color4f endColor = Color4f(1, 0, 0, 0); 209 210 bool emitting = true; 211 212 bool haveParticlesToDraw = false; 213 214 Vector3f[4] vertices; 215 Vector2f[4] texcoords; 216 uint[3][2] indices; 217 218 GLuint vao = 0; 219 GLuint vbo = 0; 220 GLuint tbo = 0; 221 GLuint eao = 0; 222 223 Matrix4x4f invViewMatRot; 224 225 GenericMaterial material; 226 227 this(Entity e, uint numParticles) 228 { 229 super(e); 230 231 particles = New!(Particle[])(numParticles); 232 foreach(ref p; particles) 233 { 234 resetParticle(p); 235 } 236 237 vertices[0] = Vector3f(-0.5f, 0.5f, 0); 238 vertices[1] = Vector3f(-0.5f, -0.5f, 0); 239 vertices[2] = Vector3f(0.5f, -0.5f, 0); 240 vertices[3] = Vector3f(0.5f, 0.5f, 0); 241 242 texcoords[0] = Vector2f(0, 0); 243 texcoords[1] = Vector2f(0, 1); 244 texcoords[2] = Vector2f(1, 1); 245 texcoords[3] = Vector2f(1, 0); 246 247 indices[0][0] = 0; 248 indices[0][1] = 1; 249 indices[0][2] = 2; 250 251 indices[1][0] = 0; 252 indices[1][1] = 2; 253 indices[1][2] = 3; 254 255 glGenBuffers(1, &vbo); 256 glBindBuffer(GL_ARRAY_BUFFER, vbo); 257 glBufferData(GL_ARRAY_BUFFER, vertices.length * float.sizeof * 3, vertices.ptr, GL_STATIC_DRAW); 258 259 glGenBuffers(1, &tbo); 260 glBindBuffer(GL_ARRAY_BUFFER, tbo); 261 glBufferData(GL_ARRAY_BUFFER, texcoords.length * float.sizeof * 2, texcoords.ptr, GL_STATIC_DRAW); 262 263 glGenBuffers(1, &eao); 264 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eao); 265 glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.length * uint.sizeof * 3, indices.ptr, GL_STATIC_DRAW); 266 267 glGenVertexArrays(1, &vao); 268 glBindVertexArray(vao); 269 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eao); 270 271 glEnableVertexAttribArray(VertexAttrib.Vertices); 272 glBindBuffer(GL_ARRAY_BUFFER, vbo); 273 glVertexAttribPointer(VertexAttrib.Vertices, 3, GL_FLOAT, GL_FALSE, 0, null); 274 275 glEnableVertexAttribArray(VertexAttrib.Texcoords); 276 glBindBuffer(GL_ARRAY_BUFFER, tbo); 277 glVertexAttribPointer(VertexAttrib.Texcoords, 2, GL_FLOAT, GL_FALSE, 0, null); 278 279 glBindVertexArray(0); 280 } 281 282 ~this() 283 { 284 Delete(particles); 285 forceFields.free(); 286 } 287 288 void addForceField(ForceField ff) 289 { 290 forceFields.append(ff); 291 } 292 293 void resetParticle(ref Particle p) 294 { 295 if (initialPositionRandomRadius > 0.0f) 296 { 297 float randomDist = uniform(0.0f, initialPositionRandomRadius); 298 p.position = entity.absolutePosition + randomUnitVector3!float * randomDist; 299 } 300 else 301 p.position = entity.absolutePosition; 302 Vector3f r = randomUnitVector3!float; 303 304 float initialSpeed; 305 if (maxInitialSpeed > minInitialSpeed) 306 initialSpeed = uniform(minInitialSpeed, maxInitialSpeed); 307 else 308 initialSpeed = maxInitialSpeed; 309 p.velocity = lerp(initialDirection, r, initialDirectionRandomFactor) * initialSpeed; 310 311 if (maxLifetime > minLifetime) 312 p.lifetime = uniform(minLifetime, maxLifetime); 313 else 314 p.lifetime = maxLifetime; 315 p.gravityVector = Vector3f(0, -1, 0); 316 317 float s; 318 if (maxSize > maxSize) 319 s = uniform(maxSize, maxSize); 320 else 321 s = maxSize; 322 323 p.scale = Vector2f(s, s); 324 p.time = 0.0f; 325 p.move = true; 326 p.startColor = startColor; 327 p.color = p.startColor; 328 } 329 330 void updateParticle(ref Particle p, double dt) 331 { 332 p.time += dt; 333 334 float t = p.time / p.lifetime; 335 p.color = lerp(startColor, endColor, t); 336 p.scale = p.scale + scaleStep * dt; 337 338 if (p.move) 339 { 340 p.acceleration = Vector3f(0, 0, 0); 341 342 foreach(ref ff; forceFields) 343 { 344 ff.affect(p); 345 } 346 347 p.velocity += p.acceleration * dt; 348 p.velocity = p.velocity * airFrictionDamping; 349 350 p.position += p.velocity * dt; 351 } 352 353 p.color.a = lerp(startColor.a, endColor.a, t); 354 } 355 356 override void update(double dt) 357 { 358 haveParticlesToDraw = false; 359 foreach(ref p; particles) 360 { 361 if (p.active) 362 { 363 if (p.time < p.lifetime) 364 { 365 updateParticle(p, dt); 366 haveParticlesToDraw = true; 367 } 368 else 369 p.active = false; 370 } 371 else if (emitting) 372 { 373 resetParticle(p); 374 p.active = true; 375 } 376 } 377 } 378 379 override void render(RenderingContext* rc) 380 { 381 if (haveParticlesToDraw) 382 { 383 foreach(ref p; particles) 384 if (p.time < p.lifetime) 385 { 386 Matrix4x4f modelViewMatrix = 387 rc.viewMatrix * 388 translationMatrix(p.position) * 389 rc.invViewRotationMatrix * 390 scaleMatrix(Vector3f(p.scale.x, p.scale.y, 1.0f)); 391 392 RenderingContext rcLocal = *rc; 393 rcLocal.modelViewMatrix = modelViewMatrix; 394 395 if (material) 396 { 397 material.particleColor = p.color; 398 material.bind(&rcLocal); 399 } 400 401 glBindVertexArray(vao); 402 glDrawElements(GL_TRIANGLES, cast(uint)indices.length * 3, GL_UNSIGNED_INT, cast(void*)0); 403 glBindVertexArray(0); 404 405 if (material) 406 material.unbind(&rcLocal); 407 } 408 } 409 } 410 }