From 53c66a5f4c4e7ce640891992e86fc40843c847fd Mon Sep 17 00:00:00 2001 From: Viktor Lidholt Date: Fri, 24 Jul 2015 12:14:22 -0700 Subject: [PATCH] Optimizations to particle systems. Uses single instance of Random and faster atan2 function. --- .../example/game/lib/particle_system.dart | 35 ++++---- packages/flutter/example/game/lib/util.dart | 84 +++++++++++++++++++ 2 files changed, 100 insertions(+), 19 deletions(-) diff --git a/packages/flutter/example/game/lib/particle_system.dart b/packages/flutter/example/game/lib/particle_system.dart index db64a699722..f250a1280f3 100644 --- a/packages/flutter/example/game/lib/particle_system.dart +++ b/packages/flutter/example/game/lib/particle_system.dart @@ -80,8 +80,6 @@ class ParticleSystem extends Node { // double _elapsedTime; int _numEmittedParticles = 0; - math.Random _rand; - ParticleSystem(this.texture, {this.life: 1.5, this.lifeVar: 1.0, @@ -116,7 +114,6 @@ class ParticleSystem extends Node { this.numParticlesToEmit: 0, this.autoRemoveOnFinish: true}) { _particles = new List<_Particle>(); - _rand = new math.Random(); _emitCounter = 0.0; // _elapsedTime = 0.0; if (gravity == null) gravity = new Vector2.zero(); @@ -124,6 +121,8 @@ class ParticleSystem extends Node { } 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; @@ -196,34 +195,34 @@ class ParticleSystem extends Node { _Particle particle = new _Particle(); // Time to live - particle.timeToLive = math.max(life + lifeVar * randMinus1To1(), 0.0); + particle.timeToLive = math.max(life + lifeVar * randomSignedDouble(), 0.0); // Position Point srcPos = Point.origin; - particle.pos = new Vector2(srcPos.x + posVar.x * randMinus1To1(), - srcPos.y + posVar.y * randMinus1To1()); + particle.pos = new Vector2(srcPos.x + posVar.x * randomSignedDouble(), + srcPos.y + posVar.y * randomSignedDouble()); // Size - particle.size = math.max(startSize + startSizeVar * randMinus1To1(), 0.0); - double endSizeFinal = math.max(endSize + endSizeVar * randMinus1To1(), 0.0); + 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 * randMinus1To1(); - double endRotationFinal = endRotation + endRotationVar * randMinus1To1(); + particle.rotation = startRotation + startRotationVar * randomSignedDouble(); + double endRotationFinal = endRotation + endRotationVar * randomSignedDouble(); particle.deltaRotation = (endRotationFinal - particle.rotation) / particle.timeToLive; // Direction - double dirRadians = convertDegrees2Radians(direction + directionVar * randMinus1To1()); + double dirRadians = convertDegrees2Radians(direction + directionVar * randomSignedDouble()); Vector2 dirVector = new Vector2(math.cos(dirRadians), math.sin(dirRadians)); - double speedFinal = speed + speedVar * randMinus1To1(); + double speedFinal = speed + speedVar * randomSignedDouble(); particle.dir = dirVector.scale(speedFinal); // Radial acceleration - particle.radialAccel = radialAcceleration + radialAccelerationVar * randMinus1To1(); + particle.radialAccel = radialAcceleration + radialAccelerationVar * randomSignedDouble(); // Tangential acceleration - particle.tangentialAccel = tangentialAcceleration + tangentialAccelerationVar * randMinus1To1(); + particle.tangentialAccel = tangentialAcceleration + tangentialAccelerationVar * randomSignedDouble(); // Color particle.colorPos = 0.0; @@ -248,7 +247,7 @@ class ParticleSystem extends Node { double scos; double ssin; if (rotateToMovement) { - double extraRotation = math.atan2(particle.dir[1], particle.dir[0]); + 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 { @@ -276,14 +275,12 @@ class ParticleSystem extends Node { Paint paint = new Paint()..setTransferMode(transferMode) ..setFilterQuality(FilterQuality.low) // All Skia examples do this. ..isAntiAlias = false; // Antialiasing breaks SkCanvas.drawAtlas? - // return canvas.drawAtlas(texture.image, transforms, rects, colors, - // TransferMode.modulate, null, paint); + return canvas.drawAtlas(texture.image, transforms, rects, colors, + TransferMode.modulate, null, paint); dartDrawAtlas(canvas, texture.image, transforms, rects, colors, TransferMode.modulate, paint); } - - double randMinus1To1() => _rand.nextDouble() * 2.0 - 1.0; } void dartDrawAtlas(Canvas canvas, Image image, List transforms, diff --git a/packages/flutter/example/game/lib/util.dart b/packages/flutter/example/game/lib/util.dart index 66568a5cb87..31f79ca1783 100644 --- a/packages/flutter/example/game/lib/util.dart +++ b/packages/flutter/example/game/lib/util.dart @@ -1,2 +1,86 @@ part of sprites; + +math.Random _random = new math.Random(); + +// Random methods + +double randomDouble() { + return _random.nextDouble(); +} + +double randomSignedDouble() { + return _random.nextDouble() * 2.0 - 1.0; +} + +int randomInt(int max) { + return _random.nextInt(max); +} + +// atan2 + +class GameMath { + static bool _inited = false; + + static final int size = 1024; + static final double stretch = math.PI; + + static final int ezis = -size; + + static Float64List atan2_table_ppy = new Float64List(size + 1); + static Float64List atan2_table_ppx = new Float64List(size + 1); + static Float64List atan2_table_pny = new Float64List(size + 1); + static Float64List atan2_table_pnx = new Float64List(size + 1); + static Float64List atan2_table_npy = new Float64List(size + 1); + static Float64List atan2_table_npx = new Float64List(size + 1); + static Float64List atan2_table_nny = new Float64List(size + 1); + static Float64List atan2_table_nnx = new Float64List(size + 1); + + static void init() { + if (_inited) return; + + for (int i = 0; i <= size; i++) { + double f = i.toDouble() / size.toDouble(); + atan2_table_ppy[i] = math.atan(f) * stretch / math.PI; + atan2_table_ppx[i] = stretch * 0.5 - atan2_table_ppy[i]; + atan2_table_pny[i] = -atan2_table_ppy[i]; + atan2_table_pnx[i] = atan2_table_ppy[i] - stretch * 0.5; + atan2_table_npy[i] = stretch - atan2_table_ppy[i]; + atan2_table_npx[i] = atan2_table_ppy[i] + stretch * 0.5; + atan2_table_nny[i] = atan2_table_ppy[i] - stretch; + atan2_table_nnx[i] = -stretch * 0.5 - atan2_table_ppy[i]; + } + _inited = true; + } + + static double atan2(double y, double x) { + if (!_inited) + init(); + + if (x >= 0) { + if (y >= 0) { + if (x >= y) + return atan2_table_ppy[(size * y / x + 0.5).toInt()]; + else + return atan2_table_ppx[(size * x / y + 0.5).toInt()]; + } else { + if (x >= -y) + return atan2_table_pny[(ezis * y / x + 0.5).toInt()]; + else + return atan2_table_pnx[(ezis * x / y + 0.5).toInt()]; + } + } else { + if (y >= 0) { + if (-x >= y) + return atan2_table_npy[(ezis * y / x + 0.5).toInt()]; + else + return atan2_table_npx[(ezis * x / y + 0.5).toInt()]; + } else { + if (x <= y) + return atan2_table_nny[(size * y / x + 0.5).toInt()]; + else + return atan2_table_nnx[(size * x / y + 0.5).toInt()]; + } + } + } +}