blob: a5167d9135f7e58a9712cdb440225dc867ac7370 [file] [log] [blame]
part of sprites;
class _Particle {
Vector2 pos;
Vector2 startPos;
double colorPos = 0.0;
double deltaColorPos = 0.0;
double size = 0.0;
double deltaSize = 0.0;
double rotation = 0.0;
double deltaRotation = 0.0;
double timeToLive = 0.0;
Vector2 dir;
_ParticleAccelerations accelerations;
Float64List simpleColorSequence;
ColorSequence colorSequence;
}
class _ParticleAccelerations {
double radialAccel = 0.0;
double tangentialAccel = 0.0;
}
class ParticleSystem extends Node {
Texture texture;
double life;
double lifeVar;
Point posVar;
double startSize;
double startSizeVar;
double endSize;
double endSizeVar;
double startRotation;
double startRotationVar;
double endRotation;
double endRotationVar;
bool rotateToMovement;
double direction;
double directionVar;
double speed;
double speedVar;
double radialAcceleration;
double radialAccelerationVar;
double tangentialAcceleration;
double tangentialAccelerationVar;
Vector2 gravity;
int maxParticles;
int numParticlesToEmit;
double emissionRate;
bool autoRemoveOnFinish;
ColorSequence colorSequence;
int alphaVar;
int redVar;
int greenVar;
int blueVar;
TransferMode colorTransferMode;
TransferMode transferMode;
List<_Particle> _particles;
double _emitCounter;
int _numEmittedParticles = 0;
static Paint _paint = new Paint()
..setFilterQuality(FilterQuality.low)
..isAntiAlias = false;
ParticleSystem(this.texture,
{this.life: 1.5,
this.lifeVar: 1.0,
this.posVar: Point.origin,
this.startSize: 2.5,
this.startSizeVar: 0.5,
this.endSize: 0.0,
this.endSizeVar: 0.0,
this.startRotation: 0.0,
this.startRotationVar: 0.0,
this.endRotation: 0.0,
this.endRotationVar: 0.0,
this.rotateToMovement : false,
this.direction: 0.0,
this.directionVar: 360.0,
this.speed: 100.0,
this.speedVar: 50.0,
this.radialAcceleration: 0.0,
this.radialAccelerationVar: 0.0,
this.tangentialAcceleration: 0.0,
this.tangentialAccelerationVar: 0.0,
this.gravity,
this.maxParticles: 100,
this.emissionRate: 50.0,
this.colorSequence,
this.alphaVar: 0,
this.redVar: 0,
this.greenVar: 0,
this.blueVar: 0,
this.colorTransferMode: TransferMode.multiply,
this.transferMode: TransferMode.plus,
this.numParticlesToEmit: 0,
this.autoRemoveOnFinish: true}) {
_particles = new List<_Particle>();
_emitCounter = 0.0;
// _elapsedTime = 0.0;
if (gravity == null) gravity = new Vector2.zero();
if (colorSequence == null) colorSequence = new ColorSequence.fromStartAndEndColor(new Color(0xffffffff), new Color(0x00ffffff));
}
void update(double dt) {
// TODO: Fix this (it's a temp fix for low framerates)
if (dt > 0.1) dt = 0.1;
// Create new particles
double rate = 1.0 / emissionRate;
if (_particles.length < maxParticles) {
_emitCounter += dt;
}
while(_particles.length < maxParticles
&& _emitCounter > rate
&& (numParticlesToEmit == 0 || _numEmittedParticles < numParticlesToEmit)) {
// Add a new particle
_addParticle();
_emitCounter -= rate;
}
// _elapsedTime += dt;
// Iterate over all particles
for (int i = _particles.length -1; i >= 0; i--) {
_Particle particle = _particles[i];
// Manage life time
particle.timeToLive -= dt;
if (particle.timeToLive <= 0) {
_particles.removeAt(i);
continue;
}
// Update the particle
if (particle.accelerations != null) {
// Radial acceleration
Vector2 radial;
if (particle.pos[0] != 0 || particle.pos[1] != 0) {
radial = new Vector2.copy(particle.pos).normalize();
} else {
radial = new Vector2.zero();
}
Vector2 tangential = new Vector2.copy(radial);
radial.scale(particle.accelerations.radialAccel);
// Tangential acceleration
double newY = tangential.x;
tangential.x = -tangential.y;
tangential.y = newY;
tangential.scale(particle.accelerations.tangentialAccel);
// (gravity + radial + tangential) * dt
Vector2 accel = (gravity + radial + tangential).scale(dt);
particle.dir += accel;
} else if (gravity[0] != 0.0 || gravity[1] != 0) {
// gravity
Vector2 accel = gravity.scale(dt);
particle.dir += accel;
}
// Update particle position
particle.pos[0] += particle.dir[0] * dt;
particle.pos[1] += particle.dir[1] * dt;
// Size
particle.size = math.max(particle.size + particle.deltaSize * dt, 0.0);
// Angle
particle.rotation += particle.deltaRotation * dt;
// Color
if (particle.simpleColorSequence != null) {
for (int i = 0; i < 4; i++) {
particle.simpleColorSequence[i] += particle.simpleColorSequence[i + 4] * dt;
}
} else {
particle.colorPos = math.min(particle.colorPos + particle.deltaColorPos * dt, 1.0);
}
}
if (autoRemoveOnFinish && _particles.length == 0 && _numEmittedParticles > 0) {
if (parent != null) removeFromParent();
}
}
void _addParticle() {
_Particle particle = new _Particle();
// Time to live
particle.timeToLive = math.max(life + lifeVar * randomSignedDouble(), 0.0);
// Position
Point srcPos = Point.origin;
particle.pos = new Vector2(srcPos.x + posVar.x * randomSignedDouble(),
srcPos.y + posVar.y * randomSignedDouble());
// Size
particle.size = math.max(startSize + startSizeVar * randomSignedDouble(), 0.0);
double endSizeFinal = math.max(endSize + endSizeVar * randomSignedDouble(), 0.0);
particle.deltaSize = (endSizeFinal - particle.size) / particle.timeToLive;
// Rotation
particle.rotation = startRotation + startRotationVar * randomSignedDouble();
double endRotationFinal = endRotation + endRotationVar * randomSignedDouble();
particle.deltaRotation = (endRotationFinal - particle.rotation) / particle.timeToLive;
// Direction
double dirRadians = convertDegrees2Radians(direction + directionVar * randomSignedDouble());
Vector2 dirVector = new Vector2(math.cos(dirRadians), math.sin(dirRadians));
double speedFinal = speed + speedVar * randomSignedDouble();
particle.dir = dirVector.scale(speedFinal);
// Accelerations
if (radialAcceleration != 0.0 || radialAccelerationVar != 0.0 ||
tangentialAcceleration != 0.0 || tangentialAccelerationVar != 0.0) {
particle.accelerations = new _ParticleAccelerations();
// Radial acceleration
particle.accelerations.radialAccel = radialAcceleration + radialAccelerationVar * randomSignedDouble();
// Tangential acceleration
particle.accelerations.tangentialAccel = tangentialAcceleration + tangentialAccelerationVar * randomSignedDouble();
}
// Color
particle.colorPos = 0.0;
particle.deltaColorPos = 1.0 / particle.timeToLive;
if (alphaVar != 0 || redVar != 0 || greenVar != 0 || blueVar != 0) {
particle.colorSequence = _ColorSequenceUtil.copyWithVariance(colorSequence, alphaVar, redVar, greenVar, blueVar);
}
// Optimizes the case where there are only two colors in the sequence
if (colorSequence.colors.length == 2) {
Color startColor;
Color endColor;
if (particle.colorSequence != null) {
startColor = particle.colorSequence.colors[0];
endColor = particle.colorSequence.colors[1];
} else {
startColor = colorSequence.colors[0];
endColor = colorSequence.colors[1];
}
// First 4 elements are start ARGB, last 4 are delta ARGB
particle.simpleColorSequence = new Float64List(8);
particle.simpleColorSequence[0] = startColor.alpha.toDouble();
particle.simpleColorSequence[1] = startColor.red.toDouble();
particle.simpleColorSequence[2] = startColor.green.toDouble();
particle.simpleColorSequence[3] = startColor.blue.toDouble();
particle.simpleColorSequence[4] = (endColor.alpha.toDouble() - startColor.alpha.toDouble()) / particle.timeToLive;
particle.simpleColorSequence[5] = (endColor.red.toDouble() - startColor.red.toDouble()) / particle.timeToLive;
particle.simpleColorSequence[6] = (endColor.green.toDouble() - startColor.green.toDouble()) / particle.timeToLive;
particle.simpleColorSequence[7] = (endColor.blue.toDouble() - startColor.blue.toDouble()) / particle.timeToLive;
}
_particles.add(particle);
_numEmittedParticles++;
}
void paint(PaintingCanvas canvas) {
List<RSTransform> transforms = [];
List<Rect> rects = [];
List<Color> colors = [];
_paint.setTransferMode(transferMode);
for (_Particle particle in _particles) {
// Rect
Rect rect = texture.frame;
rects.add(rect);
// Transform
double scos;
double ssin;
if (rotateToMovement) {
double extraRotation = GameMath.atan2(particle.dir[1], particle.dir[0]);
scos = math.cos(convertDegrees2Radians(particle.rotation) + extraRotation) * particle.size;
ssin = math.sin(convertDegrees2Radians(particle.rotation) + extraRotation) * particle.size;
} else if (particle.rotation != 0.0) {
scos = math.cos(convertDegrees2Radians(particle.rotation)) * particle.size;
ssin = math.sin(convertDegrees2Radians(particle.rotation)) * particle.size;
} else {
scos = particle.size;
ssin = 0.0;
}
double ax = rect.width / 2;
double ay = rect.height / 2;
double tx = particle.pos[0] + -scos * ax + ssin * ay;
double ty = particle.pos[1] + -ssin * ax - scos * ay;
RSTransform transform = new RSTransform(scos, ssin, tx, ty);
transforms.add(transform);
// Color
if (particle.simpleColorSequence != null) {
Color particleColor = new Color.fromARGB(
particle.simpleColorSequence[0].toInt().clamp(0, 255),
particle.simpleColorSequence[1].toInt().clamp(0, 255),
particle.simpleColorSequence[2].toInt().clamp(0, 255),
particle.simpleColorSequence[3].toInt().clamp(0, 255));
colors.add(particleColor);
} else {
Color particleColor;
if (particle.colorSequence != null) {
particleColor = particle.colorSequence.colorAtPosition(particle.colorPos);
} else {
particleColor = colorSequence.colorAtPosition(particle.colorPos);
}
colors.add(particleColor);
}
}
canvas.drawAtlas(texture.image, transforms, rects, colors,
TransferMode.modulate, null, _paint);
}
}
class _ColorSequenceUtil {
static ColorSequence copyWithVariance(
ColorSequence sequence,
int alphaVar,
int redVar,
int greenVar,
int blueVar
) {
ColorSequence copy = new ColorSequence.copy(sequence);
int i = 0;
for (Color color in sequence.colors) {
int aDelta = ((randomDouble() * 2.0 - 1.0) * alphaVar).toInt();
int rDelta = ((randomDouble() * 2.0 - 1.0) * redVar).toInt();
int gDelta = ((randomDouble() * 2.0 - 1.0) * greenVar).toInt();
int bDelta = ((randomDouble() * 2.0 - 1.0) * blueVar).toInt();
int aNew = (color.alpha + aDelta).clamp(0, 255);
int rNew = (color.red + rDelta).clamp(0, 255);
int gNew = (color.green + gDelta).clamp(0, 255);
int bNew = (color.blue + bDelta).clamp(0, 255);
copy.colors[i] = new Color.fromARGB(aNew, rNew, gNew, bNew);
i++;
}
return copy;
}
}