1/**
2 * Copyright (c) 2006-2023 LOVE Development Team
3 *
4 * This software is provided 'as-is', without any express or implied
5 * warranty. In no event will the authors be held liable for any damages
6 * arising from the use of this software.
7 *
8 * Permission is granted to anyone to use this software for any purpose,
9 * including commercial applications, and to alter it and redistribute it
10 * freely, subject to the following restrictions:
11 *
12 * 1. The origin of this software must not be misrepresented; you must not
13 * claim that you wrote the original software. If you use this software
14 * in a product, an acknowledgment in the product documentation would be
15 * appreciated but is not required.
16 * 2. Altered source versions must be plainly marked as such, and must not be
17 * misrepresented as being the original software.
18 * 3. This notice may not be removed or altered from any source distribution.
19 **/
20
21//LOVE
22#include "common/config.h"
23#include "ParticleSystem.h"
24#include "Graphics.h"
25
26#include "common/math.h"
27#include "modules/math/RandomGenerator.h"
28
29// STD
30#include <algorithm>
31#include <cmath>
32#include <cstdlib>
33
34namespace love
35{
36namespace graphics
37{
38
39namespace
40{
41
42love::math::RandomGenerator rng;
43
44float calculate_variation(float inner, float outer, float var)
45{
46 float low = inner - (outer/2.0f)*var;
47 float high = inner + (outer/2.0f)*var;
48 float r = (float) rng.random();
49 return low*(1-r)+high*r;
50}
51
52} // anonymous namespace
53
54love::Type ParticleSystem::type("ParticleSystem", &Drawable::type);
55
56ParticleSystem::ParticleSystem(Texture *texture, uint32 size)
57 : pMem(nullptr)
58 , pFree(nullptr)
59 , pHead(nullptr)
60 , pTail(nullptr)
61 , texture(texture)
62 , active(true)
63 , insertMode(INSERT_MODE_TOP)
64 , maxParticles(0)
65 , activeParticles(0)
66 , emissionRate(0)
67 , emitCounter(0)
68 , emissionAreaDistribution(DISTRIBUTION_NONE)
69 , emissionAreaAngle(0)
70 , directionRelativeToEmissionCenter(false)
71 , lifetime(-1)
72 , life(0)
73 , particleLifeMin(0)
74 , particleLifeMax(0)
75 , direction(0)
76 , spread(0)
77 , speedMin(0)
78 , speedMax(0)
79 , linearAccelerationMin(0, 0)
80 , linearAccelerationMax(0, 0)
81 , radialAccelerationMin(0)
82 , radialAccelerationMax(0)
83 , tangentialAccelerationMin(0)
84 , tangentialAccelerationMax(0)
85 , linearDampingMin(0.0f)
86 , linearDampingMax(0.0f)
87 , sizeVariation(0)
88 , rotationMin(0)
89 , rotationMax(0)
90 , spinStart(0)
91 , spinEnd(0)
92 , spinVariation(0)
93 , offset(float(texture->getWidth())*0.5f, float(texture->getHeight())*0.5f)
94 , defaultOffset(true)
95 , relativeRotation(false)
96 , vertexAttributes(vertex::CommonFormat::XYf_STf_RGBAub, 0)
97 , buffer(nullptr)
98{
99 if (size == 0 || size > MAX_PARTICLES)
100 throw love::Exception("Invalid ParticleSystem size.");
101
102 if (texture->getTextureType() != TEXTURE_2D)
103 throw love::Exception("Only 2D textures can be used with ParticleSystems.");
104
105 sizes.push_back(1.0f);
106 colors.push_back(Colorf(1.0f, 1.0f, 1.0f, 1.0f));
107
108 setBufferSize(size);
109}
110
111ParticleSystem::ParticleSystem(const ParticleSystem &p)
112 : pMem(nullptr)
113 , pFree(nullptr)
114 , pHead(nullptr)
115 , pTail(nullptr)
116 , texture(p.texture)
117 , active(p.active)
118 , insertMode(p.insertMode)
119 , maxParticles(p.maxParticles)
120 , activeParticles(0)
121 , emissionRate(p.emissionRate)
122 , emitCounter(0.0f)
123 , position(p.position)
124 , prevPosition(p.prevPosition)
125 , emissionAreaDistribution(p.emissionAreaDistribution)
126 , emissionArea(p.emissionArea)
127 , emissionAreaAngle(p.emissionAreaAngle)
128 , directionRelativeToEmissionCenter(p.directionRelativeToEmissionCenter)
129 , lifetime(p.lifetime)
130 , life(p.lifetime) // Initialize with the maximum life time.
131 , particleLifeMin(p.particleLifeMin)
132 , particleLifeMax(p.particleLifeMax)
133 , direction(p.direction)
134 , spread(p.spread)
135 , speedMin(p.speedMin)
136 , speedMax(p.speedMax)
137 , linearAccelerationMin(p.linearAccelerationMin)
138 , linearAccelerationMax(p.linearAccelerationMax)
139 , radialAccelerationMin(p.radialAccelerationMin)
140 , radialAccelerationMax(p.radialAccelerationMax)
141 , tangentialAccelerationMin(p.tangentialAccelerationMin)
142 , tangentialAccelerationMax(p.tangentialAccelerationMax)
143 , linearDampingMin(p.linearDampingMin)
144 , linearDampingMax(p.linearDampingMax)
145 , sizes(p.sizes)
146 , sizeVariation(p.sizeVariation)
147 , rotationMin(p.rotationMin)
148 , rotationMax(p.rotationMax)
149 , spinStart(p.spinStart)
150 , spinEnd(p.spinEnd)
151 , spinVariation(p.spinVariation)
152 , offset(p.offset)
153 , defaultOffset(p.defaultOffset)
154 , colors(p.colors)
155 , quads(p.quads)
156 , relativeRotation(p.relativeRotation)
157 , vertexAttributes(p.vertexAttributes)
158 , buffer(nullptr)
159{
160 setBufferSize(maxParticles);
161}
162
163ParticleSystem::~ParticleSystem()
164{
165 deleteBuffers();
166}
167
168ParticleSystem *ParticleSystem::clone()
169{
170 return new ParticleSystem(*this);
171}
172
173void ParticleSystem::resetOffset()
174{
175 if (quads.empty())
176 offset = love::Vector2(float(texture->getWidth())*0.5f, float(texture->getHeight())*0.5f);
177 else
178 {
179 Quad::Viewport v = quads[0]->getViewport();
180 offset = love::Vector2(v.w*0.5f, v.h*0.5f);
181 }
182}
183
184void ParticleSystem::createBuffers(size_t size)
185{
186 try
187 {
188 pFree = pMem = new Particle[size];
189 maxParticles = (uint32) size;
190
191 auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
192
193 size_t bytes = sizeof(Vertex) * size * 4;
194 buffer = gfx->newBuffer(bytes, nullptr, BUFFER_VERTEX, vertex::USAGE_STREAM, 0);
195 }
196 catch (std::bad_alloc &)
197 {
198 deleteBuffers();
199 throw love::Exception("Out of memory");
200 }
201}
202
203void ParticleSystem::deleteBuffers()
204{
205 delete[] pMem;
206 delete buffer;
207
208 pMem = nullptr;
209 buffer = nullptr;
210 maxParticles = 0;
211 activeParticles = 0;
212}
213
214void ParticleSystem::setBufferSize(uint32 size)
215{
216 if (size == 0 || size > MAX_PARTICLES)
217 throw love::Exception("Invalid buffer size");
218 deleteBuffers();
219 createBuffers(size);
220 reset();
221}
222
223uint32 ParticleSystem::getBufferSize() const
224{
225 return maxParticles;
226}
227
228void ParticleSystem::addParticle(float t)
229{
230 if (isFull())
231 return;
232
233 // Gets a free particle and updates the allocation pointer.
234 Particle *p = pFree++;
235 initParticle(p, t);
236
237 switch (insertMode)
238 {
239 default:
240 case INSERT_MODE_TOP:
241 insertTop(p);
242 break;
243 case INSERT_MODE_BOTTOM:
244 insertBottom(p);
245 break;
246 case INSERT_MODE_RANDOM:
247 insertRandom(p);
248 break;
249 }
250
251 activeParticles++;
252}
253
254void ParticleSystem::initParticle(Particle *p, float t)
255{
256 float min,max;
257
258 // Linearly interpolate between the previous and current emitter position.
259 love::Vector2 pos = prevPosition + (position - prevPosition) * t;
260
261 min = particleLifeMin;
262 max = particleLifeMax;
263 if (min == max)
264 p->life = min;
265 else
266 p->life = (float) rng.random(min, max);
267 p->lifetime = p->life;
268
269 p->position = pos;
270
271 min = direction - spread/2.0f;
272 max = direction + spread/2.0f;
273 float dir = (float) rng.random(min, max);
274
275 // In this switch statement, variables 'rand_y', 'min', and 'max'
276 // are sometimes reused as data stores for performance reasons
277 float rand_x, rand_y;
278 float c, s;
279 switch (emissionAreaDistribution)
280 {
281 case DISTRIBUTION_UNIFORM:
282 c = cosf(emissionAreaAngle); s = sinf(emissionAreaAngle);
283 rand_x = (float) rng.random(-emissionArea.x, emissionArea.x);
284 rand_y = (float) rng.random(-emissionArea.y, emissionArea.y);
285 p->position.x += c * rand_x - s * rand_y;
286 p->position.y += s * rand_x + c * rand_y;
287 break;
288 case DISTRIBUTION_NORMAL:
289 c = cosf(emissionAreaAngle); s = sinf(emissionAreaAngle);
290 rand_x = (float) rng.randomNormal(emissionArea.x);
291 rand_y = (float) rng.randomNormal(emissionArea.y);
292 p->position.x += c * rand_x - s * rand_y;
293 p->position.y += s * rand_x + c * rand_y;
294 break;
295 case DISTRIBUTION_ELLIPSE:
296 c = cosf(emissionAreaAngle); s = sinf(emissionAreaAngle);
297 rand_x = (float) rng.random(-1, 1);
298 rand_y = (float) rng.random(-1, 1);
299 min = emissionArea.x * (rand_x * sqrt(1 - 0.5f*pow(rand_y, 2)));
300 max = emissionArea.y * (rand_y * sqrt(1 - 0.5f*pow(rand_x, 2)));
301 p->position.x += c * min - s * max;
302 p->position.y += s * min + c * max;
303 break;
304 case DISTRIBUTION_BORDER_ELLIPSE:
305 c = cosf(emissionAreaAngle); s = sinf(emissionAreaAngle);
306 rand_x = (float) rng.random(0, LOVE_M_PI * 2);
307 min = cosf(rand_x) * emissionArea.x;
308 max = sinf(rand_x) * emissionArea.y;
309 p->position.x += c * min - s * max;
310 p->position.y += s * min + c * max;
311 break;
312 case DISTRIBUTION_BORDER_RECTANGLE:
313 c = cosf(emissionAreaAngle); s = sinf(emissionAreaAngle);
314 rand_x = (float) rng.random((emissionArea.x + emissionArea.y) * -2, (emissionArea.x + emissionArea.y) * 2);
315 rand_y = emissionArea.y * 2;
316 if (rand_x < -rand_y)
317 {
318 min = rand_x + rand_y + emissionArea.x;
319 p->position.x += c * min - s * -emissionArea.y;
320 p->position.y += s * min + c * -emissionArea.y;
321 }
322 else if (rand_x < 0)
323 {
324 max = rand_x + emissionArea.y;
325 p->position.x += c * -emissionArea.x - s * max;
326 p->position.y += s * -emissionArea.x + c * max;
327 }
328 else if (rand_x < rand_y)
329 {
330 max = rand_x - emissionArea.y;
331 p->position.x += c * emissionArea.x - s * max;
332 p->position.y += s * emissionArea.x + c * max;
333 }
334 else
335 {
336 min = rand_x - rand_y - emissionArea.x;
337 p->position.x += c * min - s * emissionArea.y;
338 p->position.y += s * min + c * emissionArea.y;
339 }
340 break;
341 case DISTRIBUTION_NONE:
342 default:
343 break;
344 }
345
346 // Determine if the origin of each particle is the center of the area
347 if (directionRelativeToEmissionCenter)
348 dir += atan2(p->position.y - pos.y, p->position.x - pos.x);
349
350 p->origin = pos;
351
352 min = speedMin;
353 max = speedMax;
354 float speed = (float) rng.random(min, max);
355
356 p->velocity = love::Vector2(cosf(dir), sinf(dir)) * speed;
357
358 p->linearAcceleration.x = (float) rng.random(linearAccelerationMin.x, linearAccelerationMax.x);
359 p->linearAcceleration.y = (float) rng.random(linearAccelerationMin.y, linearAccelerationMax.y);
360
361 min = radialAccelerationMin;
362 max = radialAccelerationMax;
363 p->radialAcceleration = (float) rng.random(min, max);
364
365 min = tangentialAccelerationMin;
366 max = tangentialAccelerationMax;
367 p->tangentialAcceleration = (float) rng.random(min, max);
368
369 min = linearDampingMin;
370 max = linearDampingMax;
371 p->linearDamping = (float) rng.random(min, max);
372
373 p->sizeOffset = (float) rng.random(sizeVariation); // time offset for size change
374 p->sizeIntervalSize = (1.0f - (float) rng.random(sizeVariation)) - p->sizeOffset;
375 p->size = sizes[(size_t)(p->sizeOffset - .5f) * (sizes.size() - 1)];
376
377 min = rotationMin;
378 max = rotationMax;
379 p->spinStart = calculate_variation(spinStart, spinEnd, spinVariation);
380 p->spinEnd = calculate_variation(spinEnd, spinStart, spinVariation);
381 p->rotation = (float) rng.random(min, max);
382
383 p->angle = p->rotation;
384 if (relativeRotation)
385 p->angle += atan2f(p->velocity.y, p->velocity.x);
386
387 p->color = colors[0];
388
389 p->quadIndex = 0;
390}
391
392void ParticleSystem::insertTop(Particle *p)
393{
394 if (pHead == nullptr)
395 {
396 pHead = p;
397 p->prev = nullptr;
398 }
399 else
400 {
401 pTail->next = p;
402 p->prev = pTail;
403 }
404 p->next = nullptr;
405 pTail = p;
406}
407
408void ParticleSystem::insertBottom(Particle *p)
409{
410 if (pTail == nullptr)
411 {
412 pTail = p;
413 p->next = nullptr;
414 }
415 else
416 {
417 pHead->prev = p;
418 p->next = pHead;
419 }
420 p->prev = nullptr;
421 pHead = p;
422}
423
424void ParticleSystem::insertRandom(Particle *p)
425{
426 // Nonuniform, but 64-bit is so large nobody will notice. Hopefully.
427 uint64 pos = rng.rand() % ((int64) activeParticles + 1);
428
429 // Special case where the particle gets inserted before the head.
430 if (pos == activeParticles)
431 {
432 Particle *pA = pHead;
433 if (pA)
434 pA->prev = p;
435 p->prev = nullptr;
436 p->next = pA;
437 pHead = p;
438 return;
439 }
440
441 // Inserts the particle after the randomly selected particle.
442 Particle *pA = pMem + pos;
443 Particle *pB = pA->next;
444 pA->next = p;
445 if (pB)
446 pB->prev = p;
447 else
448 pTail = p;
449 p->prev = pA;
450 p->next = pB;
451}
452
453ParticleSystem::Particle *ParticleSystem::removeParticle(Particle *p)
454{
455 // The linked list is updated in this function and old pointers may be
456 // invalidated. The returned pointer will inform the caller of the new
457 // pointer to the next particle.
458 Particle *pNext = nullptr;
459
460 // Removes the particle from the linked list.
461 if (p->prev)
462 p->prev->next = p->next;
463 else
464 pHead = p->next;
465
466 if (p->next)
467 {
468 p->next->prev = p->prev;
469 pNext = p->next;
470 }
471 else
472 pTail = p->prev;
473
474 // The (in memory) last particle can now be moved into the free slot.
475 // It will skip the moving if it happens to be the removed particle.
476 pFree--;
477 if (p != pFree)
478 {
479 *p = *pFree;
480 if (pNext == pFree)
481 pNext = p;
482
483 if (p->prev)
484 p->prev->next = p;
485 else
486 pHead = p;
487
488 if (p->next)
489 p->next->prev = p;
490 else
491 pTail = p;
492 }
493
494 activeParticles--;
495 return pNext;
496}
497
498void ParticleSystem::setTexture(Texture *tex)
499{
500 if (texture->getTextureType() != TEXTURE_2D)
501 throw love::Exception("Only 2D textures can be used with ParticleSystems.");
502
503 texture.set(tex);
504
505 if (defaultOffset)
506 resetOffset();
507}
508
509Texture *ParticleSystem::getTexture() const
510{
511 return texture.get();
512}
513
514void ParticleSystem::setInsertMode(InsertMode mode)
515{
516 insertMode = mode;
517}
518
519ParticleSystem::InsertMode ParticleSystem::getInsertMode() const
520{
521 return insertMode;
522}
523
524void ParticleSystem::setEmissionRate(float rate)
525{
526 if (rate < 0.0f)
527 throw love::Exception("Invalid emission rate");
528 emissionRate = rate;
529
530 // Prevent an explosion when dramatically increasing the rate
531 emitCounter = std::min(emitCounter, 1.0f/rate);
532}
533
534float ParticleSystem::getEmissionRate() const
535{
536 return emissionRate;
537}
538
539void ParticleSystem::setEmitterLifetime(float life)
540{
541 this->life = lifetime = life;
542}
543
544float ParticleSystem::getEmitterLifetime() const
545{
546 return lifetime;
547}
548
549void ParticleSystem::setParticleLifetime(float min, float max)
550{
551 particleLifeMin = min;
552 if (max == 0)
553 particleLifeMax = min;
554 else
555 particleLifeMax = max;
556}
557
558void ParticleSystem::getParticleLifetime(float &min, float &max) const
559{
560 min = particleLifeMin;
561 max = particleLifeMax;
562}
563
564void ParticleSystem::setPosition(float x, float y)
565{
566 position = love::Vector2(x, y);
567 prevPosition = position;
568}
569
570const love::Vector2 &ParticleSystem::getPosition() const
571{
572 return position;
573}
574
575void ParticleSystem::moveTo(float x, float y)
576{
577 position = love::Vector2(x, y);
578}
579
580void ParticleSystem::setEmissionArea(AreaSpreadDistribution distribution, float x, float y, float angle, bool directionRelativeToCenter)
581{
582 emissionArea = love::Vector2(x, y);
583 emissionAreaDistribution = distribution;
584 emissionAreaAngle = angle;
585 directionRelativeToEmissionCenter = directionRelativeToCenter;
586}
587
588ParticleSystem::AreaSpreadDistribution ParticleSystem::getEmissionArea(love::Vector2 &params, float &angle, bool &directionRelativeToCenter) const
589{
590 params = emissionArea;
591 angle = emissionAreaAngle;
592 directionRelativeToCenter = directionRelativeToEmissionCenter;
593 return emissionAreaDistribution;
594}
595
596void ParticleSystem::setDirection(float direction)
597{
598 this->direction = direction;
599}
600
601float ParticleSystem::getDirection() const
602{
603 return direction;
604}
605
606void ParticleSystem::setSpread(float spread)
607{
608 this->spread = spread;
609}
610
611float ParticleSystem::getSpread() const
612{
613 return spread;
614}
615
616void ParticleSystem::setSpeed(float speed)
617{
618 speedMin = speedMax = speed;
619}
620
621void ParticleSystem::setSpeed(float min, float max)
622{
623 speedMin = min;
624 speedMax = max;
625}
626
627void ParticleSystem::getSpeed(float &min, float &max) const
628{
629 min = speedMin;
630 max = speedMax;
631}
632
633void ParticleSystem::setLinearAcceleration(float x, float y)
634{
635 linearAccelerationMin.x = linearAccelerationMax.x = x;
636 linearAccelerationMin.y = linearAccelerationMax.y = y;
637}
638
639void ParticleSystem::setLinearAcceleration(float xmin, float ymin, float xmax, float ymax)
640{
641 linearAccelerationMin = love::Vector2(xmin, ymin);
642 linearAccelerationMax = love::Vector2(xmax, ymax);
643}
644
645void ParticleSystem::getLinearAcceleration(love::Vector2 &min, love::Vector2 &max) const
646{
647 min = linearAccelerationMin;
648 max = linearAccelerationMax;
649}
650
651void ParticleSystem::setRadialAcceleration(float acceleration)
652{
653 radialAccelerationMin = radialAccelerationMax = acceleration;
654}
655
656void ParticleSystem::setRadialAcceleration(float min, float max)
657{
658 radialAccelerationMin = min;
659 radialAccelerationMax = max;
660}
661
662void ParticleSystem::getRadialAcceleration(float &min, float &max) const
663{
664 min = radialAccelerationMin;
665 max = radialAccelerationMax;
666}
667
668void ParticleSystem::setTangentialAcceleration(float acceleration)
669{
670 tangentialAccelerationMin = tangentialAccelerationMax = acceleration;
671}
672
673void ParticleSystem::setTangentialAcceleration(float min, float max)
674{
675 tangentialAccelerationMin = min;
676 tangentialAccelerationMax = max;
677}
678
679void ParticleSystem::getTangentialAcceleration(float &min, float &max) const
680{
681 min = tangentialAccelerationMin;
682 max = tangentialAccelerationMax;
683}
684
685void ParticleSystem::setLinearDamping(float min, float max)
686{
687 linearDampingMin = min;
688 linearDampingMax = max;
689}
690
691void ParticleSystem::getLinearDamping(float &min, float &max) const
692{
693 min = linearDampingMin;
694 max = linearDampingMax;
695}
696
697void ParticleSystem::setSize(float size)
698{
699 sizes.resize(1);
700 sizes[0] = size;
701}
702
703void ParticleSystem::setSizes(const std::vector<float> &newSizes)
704{
705 sizes = newSizes;
706}
707
708const std::vector<float> &ParticleSystem::getSizes() const
709{
710 return sizes;
711}
712
713void ParticleSystem::setSizeVariation(float variation)
714{
715 sizeVariation = variation;
716}
717
718float ParticleSystem::getSizeVariation() const
719{
720 return sizeVariation;
721}
722
723void ParticleSystem::setRotation(float rotation)
724{
725 rotationMin = rotationMax = rotation;
726}
727
728void ParticleSystem::setRotation(float min, float max)
729{
730 rotationMin = min;
731 rotationMax = max;
732}
733
734void ParticleSystem::getRotation(float &min, float &max) const
735{
736 min = rotationMin;
737 max = rotationMax;
738}
739
740void ParticleSystem::setSpin(float spin)
741{
742 spinStart = spin;
743 spinEnd = spin;
744}
745
746void ParticleSystem::setSpin(float start, float end)
747{
748 spinStart = start;
749 spinEnd = end;
750}
751
752void ParticleSystem::getSpin(float &start, float &end) const
753{
754 start = spinStart;
755 end = spinEnd;
756}
757
758void ParticleSystem::setSpinVariation(float variation)
759{
760 spinVariation = variation;
761}
762
763float ParticleSystem::getSpinVariation() const
764{
765 return spinVariation;
766}
767
768void ParticleSystem::setOffset(float x, float y)
769{
770 offset = love::Vector2(x, y);
771 defaultOffset = false;
772}
773
774love::Vector2 ParticleSystem::getOffset() const
775{
776 return offset;
777}
778
779void ParticleSystem::setColor(const std::vector<Colorf> &newColors)
780{
781 colors = newColors;
782
783 // We don't support colors outside of [0,1] when drawing the ParticleSystem.
784 for (auto &c : colors)
785 {
786 c.r = std::min(std::max(c.r, 0.0f), 1.0f);
787 c.g = std::min(std::max(c.g, 0.0f), 1.0f);
788 c.b = std::min(std::max(c.b, 0.0f), 1.0f);
789 c.a = std::min(std::max(c.a, 0.0f), 1.0f);
790 }
791}
792
793std::vector<Colorf> ParticleSystem::getColor() const
794{
795 return colors;
796}
797
798void ParticleSystem::setQuads(const std::vector<Quad *> &newQuads)
799{
800 std::vector<StrongRef<Quad>> quadlist;
801 quadlist.reserve(newQuads.size());
802
803 for (Quad *q : newQuads)
804 quadlist.push_back(q);
805
806 quads = quadlist;
807
808 if (defaultOffset)
809 resetOffset();
810}
811
812void ParticleSystem::setQuads()
813{
814 quads.clear();
815}
816
817std::vector<Quad *> ParticleSystem::getQuads() const
818{
819 std::vector<Quad *> quadlist;
820 quadlist.reserve(quads.size());
821
822 for (const StrongRef<Quad> &q : quads)
823 quadlist.push_back(q.get());
824
825 return quadlist;
826}
827
828void ParticleSystem::setRelativeRotation(bool enable)
829{
830 relativeRotation = enable;
831}
832
833bool ParticleSystem::hasRelativeRotation() const
834{
835 return relativeRotation;
836}
837
838uint32 ParticleSystem::getCount() const
839{
840 return activeParticles;
841}
842
843void ParticleSystem::start()
844{
845 active = true;
846}
847
848void ParticleSystem::stop()
849{
850 active = false;
851 life = lifetime;
852 emitCounter = 0;
853}
854
855void ParticleSystem::pause()
856{
857 active = false;
858}
859
860void ParticleSystem::reset()
861{
862 if (pMem == nullptr)
863 return;
864
865 pFree = pMem;
866 pHead = nullptr;
867 pTail = nullptr;
868 activeParticles = 0;
869 life = lifetime;
870 emitCounter = 0;
871}
872
873void ParticleSystem::emit(uint32 num)
874{
875 if (!active)
876 return;
877
878 num = std::min(num, maxParticles - activeParticles);
879
880 while (num--)
881 addParticle(1.0f);
882}
883
884bool ParticleSystem::isActive() const
885{
886 return active;
887}
888
889bool ParticleSystem::isPaused() const
890{
891 return !active && life < lifetime;
892}
893
894bool ParticleSystem::isStopped() const
895{
896 return !active && life >= lifetime;
897}
898
899bool ParticleSystem::isEmpty() const
900{
901 return activeParticles == 0;
902}
903
904bool ParticleSystem::isFull() const
905{
906 return activeParticles == maxParticles;
907}
908
909void ParticleSystem::update(float dt)
910{
911 if (pMem == nullptr || dt == 0.0f)
912 return;
913
914 // Traverse all particles and update.
915 Particle *p = pHead;
916
917 while (p)
918 {
919 // Decrease lifespan.
920 p->life -= dt;
921
922 if (p->life <= 0)
923 p = removeParticle(p);
924 else
925 {
926 // Temp variables.
927 love::Vector2 radial, tangential;
928 love::Vector2 ppos = p->position;
929
930 // Get vector from particle center to particle.
931 radial = ppos - p->origin;
932 radial.normalize();
933 tangential = radial;
934
935 // Resize radial acceleration.
936 radial *= p->radialAcceleration;
937
938 // Calculate tangential acceleration.
939 {
940 float a = tangential.x;
941 tangential.x = -tangential.y;
942 tangential.y = a;
943 }
944
945 // Resize tangential.
946 tangential *= p->tangentialAcceleration;
947
948 // Update velocity.
949 p->velocity += (radial + tangential + p->linearAcceleration) * dt;
950
951 // Apply damping.
952 p->velocity *= 1.0f / (1.0f + p->linearDamping * dt);
953
954 // Modify position.
955 ppos += p->velocity * dt;
956
957 p->position = ppos;
958
959 const float t = 1.0f - p->life / p->lifetime;
960
961 // Rotate.
962 p->rotation += (p->spinStart * (1.0f - t) + p->spinEnd * t) * dt;
963
964 p->angle = p->rotation;
965
966 if (relativeRotation)
967 p->angle += atan2f(p->velocity.y, p->velocity.x);
968
969 // Change size according to given intervals:
970 // i = 0 1 2 3 n-1
971 // |-------|-------|------|--- ... ---|
972 // t = 0 1/(n-1) 3/(n-1) 1
973 //
974 // `s' is the interpolation variable scaled to the current
975 // interval width, e.g. if n = 5 and t = 0.3, then the current
976 // indices are 1,2 and s = 0.3 - 0.25 = 0.05
977 float s = p->sizeOffset + t * p->sizeIntervalSize; // size variation
978 s *= (float)(sizes.size() - 1); // 0 <= s < sizes.size()
979 size_t i = (size_t)s;
980 size_t k = (i == sizes.size() - 1) ? i : i + 1; // boundary check (prevents failing on t = 1.0f)
981 s -= (float)i; // transpose s to be in interval [0:1]: i <= s < i + 1 ~> 0 <= s < 1
982 p->size = sizes[i] * (1.0f - s) + sizes[k] * s;
983
984 // Update color according to given intervals (as above)
985 s = t * (float)(colors.size() - 1);
986 i = (size_t)s;
987 k = (i == colors.size() - 1) ? i : i + 1;
988 s -= (float)i; // 0 <= s <= 1
989 p->color = colors[i] * (1.0f - s) + colors[k] * s;
990
991 // Update the quad index.
992 k = quads.size();
993 if (k > 0)
994 {
995 s = t * (float) k; // [0:numquads-1] (clamped below)
996 i = (s > 0.0f) ? (size_t) s : 0;
997 p->quadIndex = (int) ((i < k) ? i : k - 1);
998 }
999
1000 // Next particle.
1001 p = p->next;
1002 }
1003 }
1004
1005 // Make some more particles.
1006 if (active)
1007 {
1008 float rate = 1.0f / emissionRate; // the amount of time between each particle emit
1009 emitCounter += dt;
1010 float total = emitCounter - rate;
1011 while (emitCounter > rate)
1012 {
1013 addParticle(1.0f - (emitCounter - rate) / total);
1014 emitCounter -= rate;
1015 }
1016
1017 life -= dt;
1018 if (lifetime != -1 && life < 0)
1019 stop();
1020 }
1021
1022 prevPosition = position;
1023}
1024
1025void ParticleSystem::draw(Graphics *gfx, const Matrix4 &m)
1026{
1027 uint32 pCount = getCount();
1028
1029 if (pCount == 0 || texture.get() == nullptr || pMem == nullptr || buffer == nullptr)
1030 return;
1031
1032 gfx->flushStreamDraws();
1033
1034 if (Shader::isDefaultActive())
1035 Shader::attachDefault(Shader::STANDARD_DEFAULT);
1036
1037 if (Shader::current && texture.get())
1038 Shader::current->checkMainTexture(texture);
1039
1040 const Vector2 *positions = texture->getQuad()->getVertexPositions();
1041 const Vector2 *texcoords = texture->getQuad()->getVertexTexCoords();
1042
1043 Vertex *pVerts = (Vertex *) buffer->map();
1044 Particle *p = pHead;
1045
1046 bool useQuads = !quads.empty();
1047
1048 Matrix3 t;
1049
1050 // set the vertex data for each particle (transformation, texcoords, color)
1051 while (p)
1052 {
1053 if (useQuads)
1054 {
1055 positions = quads[p->quadIndex]->getVertexPositions();
1056 texcoords = quads[p->quadIndex]->getVertexTexCoords();
1057 }
1058
1059 // particle vertices are image vertices transformed by particle info
1060 t.setTransformation(p->position.x, p->position.y, p->angle, p->size, p->size, offset.x, offset.y, 0.0f, 0.0f);
1061 t.transformXY(pVerts, positions, 4);
1062
1063 // Particle colors are stored as floats (0-1) but vertex colors are
1064 // unsigned bytes (0-255).
1065 Color32 c = toColor32(p->color);
1066
1067 // set the texture coordinate and color data for particle vertices
1068 for (int v = 0; v < 4; v++)
1069 {
1070 pVerts[v].s = texcoords[v].x;
1071 pVerts[v].t = texcoords[v].y;
1072 pVerts[v].color = c;
1073 }
1074
1075 pVerts += 4;
1076 p = p->next;
1077 }
1078
1079 buffer->unmap();
1080
1081 Graphics::TempTransform transform(gfx, m);
1082
1083 vertex::BufferBindings vertexbuffers;
1084 vertexbuffers.set(0, buffer, 0);
1085
1086 gfx->drawQuads(0, pCount, vertexAttributes, vertexbuffers, texture);
1087}
1088
1089bool ParticleSystem::getConstant(const char *in, AreaSpreadDistribution &out)
1090{
1091 return distributions.find(in, out);
1092}
1093
1094bool ParticleSystem::getConstant(AreaSpreadDistribution in, const char *&out)
1095{
1096 return distributions.find(in, out);
1097}
1098
1099std::vector<std::string> ParticleSystem::getConstants(AreaSpreadDistribution)
1100{
1101 return distributions.getNames();
1102}
1103
1104bool ParticleSystem::getConstant(const char *in, InsertMode &out)
1105{
1106 return insertModes.find(in, out);
1107}
1108
1109bool ParticleSystem::getConstant(InsertMode in, const char *&out)
1110{
1111 return insertModes.find(in, out);
1112}
1113
1114std::vector<std::string> ParticleSystem::getConstants(InsertMode)
1115{
1116 return insertModes.getNames();
1117}
1118
1119StringMap<ParticleSystem::AreaSpreadDistribution, ParticleSystem::DISTRIBUTION_MAX_ENUM>::Entry ParticleSystem::distributionsEntries[] =
1120{
1121 { "none", DISTRIBUTION_NONE },
1122 { "uniform", DISTRIBUTION_UNIFORM },
1123 { "normal", DISTRIBUTION_NORMAL },
1124 { "ellipse", DISTRIBUTION_ELLIPSE },
1125 { "borderellipse", DISTRIBUTION_BORDER_ELLIPSE },
1126 { "borderrectangle", DISTRIBUTION_BORDER_RECTANGLE }
1127};
1128
1129StringMap<ParticleSystem::AreaSpreadDistribution, ParticleSystem::DISTRIBUTION_MAX_ENUM> ParticleSystem::distributions(ParticleSystem::distributionsEntries, sizeof(ParticleSystem::distributionsEntries));
1130
1131StringMap<ParticleSystem::InsertMode, ParticleSystem::INSERT_MODE_MAX_ENUM>::Entry ParticleSystem::insertModesEntries[] =
1132{
1133 { "top", INSERT_MODE_TOP },
1134 { "bottom", INSERT_MODE_BOTTOM },
1135 { "random", INSERT_MODE_RANDOM },
1136};
1137
1138StringMap<ParticleSystem::InsertMode, ParticleSystem::INSERT_MODE_MAX_ENUM> ParticleSystem::insertModes(ParticleSystem::insertModesEntries, sizeof(ParticleSystem::insertModesEntries));
1139
1140} // graphics
1141} // love
1142