├── src
├── assets
│ ├── fire_diffuse.jpg
│ └── fire_opacity.atf
├── Main.as
├── Scene.as
├── starling
│ └── extensions
│ │ └── sap
│ │ ├── ParticleTextureAtlas.as
│ │ ├── Particle.as
│ │ ├── ParticleEffect.as
│ │ ├── ParticlePrototype.as
│ │ └── ParticleSystem.as
└── FireTest.as
└── README.md
/src/assets/fire_diffuse.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonchar/SAP/HEAD/src/assets/fire_diffuse.jpg
--------------------------------------------------------------------------------
/src/assets/fire_opacity.atf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gonchar/SAP/HEAD/src/assets/fire_opacity.atf
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## SAP - Starling Alternativa3D Particles
2 | ### Description
3 | SAP is a port of [Alternativa3D Particle System](https://github.com/AlternativaPlatform/Alternativa3D/tree/master/src/alternativa/engine3d/effects) to Starling Framework.
4 | This particle system is really well optimized for rendering on mobile. All particle data goes to GPU through vertexconstants. There are no any vertexbuffer uploads each frame.
5 |
6 | ## Demo
7 | [](http://bit.ly/1BLd9w3)
8 |
9 | ### Additional Resources
10 | [Follow me](https://twitter.com/UnknownFlasher) on twitter and join [Stage3D Facebook Community](https://www.facebook.com/groups/stage3d/)
11 |
12 | ### Developed with pleasure using IntelliJ IDEA
13 |
14 |
--------------------------------------------------------------------------------
/src/Main.as:
--------------------------------------------------------------------------------
1 | package {
2 | import flash.display.Sprite;
3 | import flash.display.StageAlign;
4 | import flash.display.StageScaleMode;
5 | import flash.display3D.Context3DProfile;
6 | import flash.events.Event;
7 |
8 | import starling.core.Starling;
9 |
10 | [SWF(width="550", height="400")]
11 | public class Main extends Sprite {
12 | private var starling:Starling;
13 |
14 | public function Main() {
15 | addEventListener(Event.ADDED_TO_STAGE, onAdded);
16 | }
17 |
18 | private function onAdded(event:Event):void {
19 | stage.align = StageAlign.TOP_LEFT;
20 | stage.scaleMode = StageScaleMode.NO_SCALE;
21 | stage.frameRate = 60;
22 |
23 | removeEventListener(Event.ADDED_TO_STAGE, onAdded);
24 |
25 | Starling.handleLostContext = true;
26 |
27 | starling = new Starling(Scene, stage, null, null, "auto", Context3DProfile.BASELINE_CONSTRAINED);
28 |
29 | starling.supportHighResolutions = true;
30 | starling.start();
31 | starling.stage.color = 0x444444;
32 | stage.addEventListener(Event.RESIZE, onResize);
33 | onResize(null);
34 | }
35 |
36 | private function onResize(event:Event):void {
37 | starling.stage.stageWidth = stage.stageWidth;
38 | starling.stage.stageHeight = stage.stageHeight;
39 | starling.viewPort.width = stage.stageWidth;
40 | starling.viewPort.height = stage.stageHeight;
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/src/Scene.as:
--------------------------------------------------------------------------------
1 | package {
2 |
3 | import starling.extensions.sap.ParticleSystem;
4 | import starling.extensions.sap.ParticleTextureAtlas;
5 |
6 | import flash.geom.Vector3D;
7 |
8 | import starling.display.Sprite;
9 | import starling.textures.Texture;
10 |
11 | public class Scene extends Sprite {
12 | [Embed("assets/fire_diffuse.jpg")]
13 | static private const EmbedFireDiffuse:Class;
14 | [Embed("assets/fire_opacity.atf", mimeType="application/octet-stream")]
15 | static private const EmbedFireOpacity:Class;
16 | private var particleSystem:ParticleSystem = new ParticleSystem();
17 |
18 | public function Scene() {
19 |
20 | var fireDiffuse:Texture = Texture.fromEmbeddedAsset(EmbedFireDiffuse);
21 | var fireOpacity:Texture = Texture.fromEmbeddedAsset(EmbedFireOpacity);
22 |
23 | var fireSmokeAtlas:ParticleTextureAtlas = new ParticleTextureAtlas(fireDiffuse, fireOpacity, 8, 8, 0, 16, 30, true);
24 | var fireFireAtlas:ParticleTextureAtlas = new ParticleTextureAtlas(fireDiffuse, fireOpacity, 8, 8, 16, 16, 30, true);
25 | var fireFlameAtlas:ParticleTextureAtlas = new ParticleTextureAtlas(fireDiffuse, fireOpacity, 8, 8, 32, 32, 45, true, 0.5, 0.5);
26 |
27 | var fire:FireTest = new FireTest(fireSmokeAtlas, fireFireAtlas, fireFlameAtlas, 100, false);
28 | particleSystem.gravity = new Vector3D(0, -1, 0);
29 | particleSystem.wind = new Vector3D(1, 10, 0);
30 | addChild(particleSystem);
31 | particleSystem.addEffect(fire);
32 | particleSystem.play();
33 |
34 | particleSystem.x = 125;
35 | particleSystem.y = 170;
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/starling/extensions/sap/ParticleTextureAtlas.as:
--------------------------------------------------------------------------------
1 | /**
2 | * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
3 | * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice.
4 | * You may add additional accurate notices of copyright ownership.
5 | *
6 | * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/
7 | * */
8 | package starling.extensions.sap {
9 |
10 | import starling.textures.Texture;
11 |
12 | public class ParticleTextureAtlas {
13 |
14 | public var diffuse:Texture;
15 | public var opacity:Texture;
16 | public var columnsCount:int;
17 | public var rowsCount:int;
18 | public var rangeBegin:int;
19 | public var rangeLength:int;
20 | public var fps:int;
21 | public var loop:Boolean;
22 | public var originX:Number;
23 | public var originY:Number;
24 |
25 | public function ParticleTextureAtlas(diffuse:Texture, opacity:Texture = null, columnsCount:int = 1, rowsCount:int = 1, rangeBegin:int = 0, rangeLength:int = 1, fps:int = 30, loop:Boolean = false, originX:Number = 0.5, originY:Number = 0.5) {
26 | this.diffuse = diffuse;
27 | this.opacity = opacity;
28 | this.columnsCount = columnsCount;
29 | this.rowsCount = rowsCount;
30 | this.rangeBegin = rangeBegin;
31 | this.rangeLength = rangeLength;
32 | this.fps = fps;
33 | this.loop = loop;
34 | this.originX = originX;
35 | this.originY = originY;
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/src/starling/extensions/sap/Particle.as:
--------------------------------------------------------------------------------
1 | /**
2 | * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
3 | * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice.
4 | * You may add additional accurate notices of copyright ownership.
5 | *
6 | * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/
7 | * */
8 | package starling.extensions.sap {
9 |
10 | import flash.display3D.textures.TextureBase;
11 |
12 | public class Particle {
13 |
14 | public var diffuse:TextureBase;
15 | public var opacity:TextureBase;
16 | public var blendSource:String;
17 | public var blendDestination:String;
18 |
19 | public var x:Number;
20 | public var y:Number;
21 | public var z:Number;
22 | public var rotation:Number;
23 |
24 | public var width:Number;
25 | public var height:Number;
26 | public var originX:Number;
27 | public var originY:Number;
28 |
29 | public var uvScaleX:Number;
30 | public var uvScaleY:Number;
31 | public var uvOffsetX:Number;
32 | public var uvOffsetY:Number;
33 |
34 | public var red:Number;
35 | public var green:Number;
36 | public var blue:Number;
37 | public var alpha:Number;
38 |
39 | public var next:Particle;
40 |
41 | static public var collector:Particle;
42 |
43 | static public function create():Particle {
44 | var res:Particle;
45 | if (collector != null) {
46 | res = collector;
47 | collector = collector.next;
48 | res.next = null;
49 | } else {
50 | //trace("new Particle");
51 | res = new Particle();
52 | }
53 | return res;
54 | }
55 |
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/FireTest.as:
--------------------------------------------------------------------------------
1 | package {
2 |
3 | import starling.extensions.sap.ParticleEffect;
4 | import starling.extensions.sap.ParticlePrototype;
5 | import starling.extensions.sap.ParticleTextureAtlas;
6 |
7 | import flash.display3D.Context3DBlendFactor;
8 | import flash.geom.Vector3D;
9 | import flash.utils.setTimeout;
10 |
11 | public class FireTest extends ParticleEffect {
12 |
13 | static private var smokePrototype:ParticlePrototype;
14 | static private var firePrototype:ParticlePrototype;
15 | static private var flamePrototype:ParticlePrototype;
16 |
17 | static private var liftSpeed:Number = 60;
18 | static private var windSpeed:Number = 10;
19 |
20 | static private var pos:Vector3D = new Vector3D();
21 |
22 | public function FireTest(smoke:ParticleTextureAtlas, fire:ParticleTextureAtlas, flame:ParticleTextureAtlas, live:Number = 1, repeat:Boolean = false) {
23 |
24 | var ft:Number = 1 / 30;
25 |
26 | if (smokePrototype == null) {
27 | smokePrototype = new ParticlePrototype(128, 128, smoke, false, Context3DBlendFactor.SOURCE_ALPHA, Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA);
28 | smokePrototype.addKey(0 * ft, 0, 0.40, 0.40, 0.65, 0.25, 0.00, 0.00);
29 | smokePrototype.addKey(9 * ft, 0, 0.58, 0.58, 0.65, 0.45, 0.23, 0.30);
30 | smokePrototype.addKey(19 * ft, 0, 0.78, 0.78, 0.65, 0.55, 0.50, 0.66);
31 | smokePrototype.addKey(40 * ft, 0, 1.21, 1.21, 0.40, 0.40, 0.40, 0.27);
32 | smokePrototype.addKey(54 * ft, 0, 1.50, 1.50, 0.00, 0.00, 0.00, 0.00);
33 | }
34 | if (firePrototype == null) {
35 | firePrototype = new ParticlePrototype(128, 128, fire, false, Context3DBlendFactor.SOURCE_ALPHA, Context3DBlendFactor.ONE);
36 | firePrototype.addKey(0 * ft, 1, 0.30, 0.30, 1.00, 1.00, 1.00, 0.00);
37 | firePrototype.addKey(8 * ft, 2, 0.40, 0.40, 1.00, 1.00, 1.00, 0.85);
38 | firePrototype.addKey(17 * ft, 3, 0.51, 0.51, 1.00, 0.56, 0.48, 0.10);
39 | firePrototype.addKey(24 * ft, 4, 0.60, 0.60, 1.00, 0.56, 0.48, 0.00);
40 | }
41 | if (flamePrototype == null) {
42 | flamePrototype = new ParticlePrototype(128, 128, flame, true, Context3DBlendFactor.SOURCE_ALPHA, Context3DBlendFactor.ONE);
43 | flamePrototype.addKey(0 * ft, 0, 1.00, 1.00, 1.00, 1.00, 1.00, 0.00);
44 | flamePrototype.addKey(10 * ft, 0, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00);
45 | flamePrototype.addKey(live - 10 * ft, 0, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00);
46 | flamePrototype.addKey(live, 0, 1.00, 1.00, 1.00, 1.00, 1.00, 0.00);
47 |
48 | }
49 |
50 | addKey(0, keyFrame1);
51 |
52 | var i:int = 0;
53 | while (true) {
54 | var keyTime:Number = ft + i * 3 * ft;
55 | if (keyTime < live) {
56 | addKey(keyTime, keyFrame);
57 | } else break;
58 | i++;
59 | }
60 |
61 | if (repeat) {
62 | setTimeout(function ():void {
63 | var newFire:FireTest = new FireTest(smoke, fire, flame, live, repeat);
64 | newFire.name = name;
65 | newFire.scale = scale;
66 | newFire.position = position;
67 | newFire.direction = direction;
68 | particleSystem.addEffect(newFire);
69 | }, (live - 5 * ft) * 1000);
70 | }
71 |
72 | setLife(timeKeys[keysCount - 1] + smokePrototype.lifeTime);
73 | }
74 |
75 | private function keyFrame1(keyTime:Number, time:Number):void {
76 | var area:Number = 10;
77 | pos.x = 20;
78 | pos.y = -random() * area * 0.5;
79 | var scale:Number = 0.6;
80 | flamePrototype.createParticle(this, time, pos, 0, scale, scale, 1, 0);
81 | pos.y += 20;
82 | flamePrototype.createParticle(this, time, pos, 0, scale, scale, 1, 0.5 * flamePrototype.atlas.rangeLength);
83 | }
84 |
85 | private function keyFrame(keyTime:Number, time:Number):void {
86 | var ft:Number = 1 / 30;
87 | var area:Number = 10;
88 | for (var i:int = 0; i < 1; i++) {
89 | pos.x = 20;
90 | pos.y = -10 - random() * area * 0.5;
91 | displacePosition(time, 0.7 + random() * 0.5, pos);
92 | smokePrototype.createParticle(this, time, pos, random() - 0.5, 1.00, 1.00, 1, random() * smokePrototype.atlas.rangeLength);
93 | pos.y += 20;
94 | firePrototype.createParticle(this, time, pos, random() - 0.5, 1, 1, 1, random() * firePrototype.atlas.rangeLength);
95 | firePrototype.createParticle(this, time, pos, random() - 0.5, 1.00, 1.00, 0.70, random() * firePrototype.atlas.rangeLength);
96 | }
97 | }
98 |
99 | private function displacePosition(time:Number, factor:Number, result:Vector3D):void {
100 | result.y -= time * windSpeed * particleSystem.wind.y + time * liftSpeed * factor;
101 | }
102 |
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/starling/extensions/sap/ParticleEffect.as:
--------------------------------------------------------------------------------
1 | /**
2 | * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
3 | * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice.
4 | * You may add additional accurate notices of copyright ownership.
5 | *
6 | * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/
7 | * */
8 | package starling.extensions.sap {
9 | import flash.geom.Vector3D;
10 |
11 | public class ParticleEffect {
12 |
13 | public var name:String;
14 |
15 | public var scale:Number = 1;
16 |
17 | public var next:ParticleEffect;
18 | public var nextInSystem:ParticleEffect;
19 | public var system:ParticleSystem;
20 | public var startTime:Number;
21 | public var lifeTime:Number = Number.MAX_VALUE;
22 | public var particleList:Particle;
23 | public var keyPosition:Vector3D;
24 |
25 | protected var keyDirection:Vector3D;
26 |
27 | protected var timeKeys:Vector. = new Vector.();
28 | protected var positionKeys:Vector. = new Vector.();
29 | protected var directionKeys:Vector. = new Vector.();
30 | protected var scriptKeys:Vector. = new Vector.();
31 | protected var keysCount:int = 0;
32 |
33 | private static var randomNumbers:Vector.;
34 | private static const randomNumbersCount:int = 1000;
35 |
36 | private static const vector:Vector3D = new Vector3D();
37 |
38 | private var randomOffset:int;
39 | private var randomCounter:int;
40 |
41 | private var _position:Vector3D = new Vector3D(0, 0, 0);
42 | private var _direction:Vector3D = new Vector3D(0, -1, 0);
43 |
44 | public function ParticleEffect() {
45 | if (randomNumbers == null) {
46 | randomNumbers = new Vector.();
47 | for (var i:int = 0; i < randomNumbersCount; i++) randomNumbers[i] = Math.random();
48 | }
49 | randomOffset = Math.random() * randomNumbersCount;
50 | }
51 |
52 | public function get position():Vector3D {
53 | return _position.clone();
54 | }
55 |
56 | public function set position(value:Vector3D):void {
57 | _position.x = value.x;
58 | _position.y = value.y;
59 | _position.z = value.z;
60 | _position.w = value.w;
61 | if (system != null) setPositionKeys(system.getTime() - startTime);
62 | }
63 |
64 | public function get direction():Vector3D {
65 | return _direction.clone();
66 | }
67 |
68 | public function set direction(value:Vector3D):void {
69 | _direction.x = value.x;
70 | _direction.y = value.y;
71 | _direction.z = value.z;
72 | _direction.w = value.w;
73 | if (system != null) setDirectionKeys(system.getTime() - startTime);
74 | }
75 |
76 | public function stop():void {
77 | var time:Number = system.getTime() - startTime;
78 | for (var i:int = 0; i < keysCount; i++) {
79 | if (time < timeKeys[i]) break;
80 | }
81 | keysCount = i;
82 | }
83 |
84 | protected function get particleSystem():ParticleSystem {
85 | return system;
86 | }
87 |
88 | protected function random():Number {
89 | var res:Number = randomNumbers[randomCounter];
90 | randomCounter++;
91 | if (randomCounter == randomNumbersCount) randomCounter = 0;
92 | return res;
93 | }
94 |
95 | protected function addKey(time:Number, script:Function):void {
96 | timeKeys[keysCount] = time;
97 | positionKeys[keysCount] = new Vector3D();
98 | directionKeys[keysCount] = new Vector3D();
99 | scriptKeys[keysCount] = script;
100 | keysCount++;
101 | }
102 |
103 | protected function setLife(time:Number):void {
104 | lifeTime = time;
105 | }
106 |
107 | public function setPositionKeys(time:Number):void {
108 | for (var i:int = 0; i < keysCount; i++) {
109 | if (time <= timeKeys[i]) {
110 | var pos:Vector3D = positionKeys[i];
111 | pos.x = _position.x;
112 | pos.y = _position.y;
113 | pos.z = _position.z;
114 | }
115 | }
116 | }
117 |
118 | public function setDirectionKeys(time:Number):void {
119 | vector.x = _direction.x;
120 | vector.y = _direction.y;
121 | vector.z = _direction.z;
122 | vector.normalize();
123 | for (var i:int = 0; i < keysCount; i++) {
124 | if (time <= timeKeys[i]) {
125 | var dir:Vector3D = directionKeys[i];
126 | dir.x = vector.x;
127 | dir.y = vector.y;
128 | dir.z = vector.z;
129 | }
130 | }
131 | }
132 |
133 | public function calculate(time:Number):Boolean {
134 | randomCounter = randomOffset;
135 | for (var i:int = 0; i < keysCount; i++) {
136 | var keyTime:Number = timeKeys[i];
137 | if (time >= keyTime) {
138 | keyPosition = positionKeys[i];
139 | keyDirection = directionKeys[i];
140 | var script:Function = scriptKeys[i];
141 | script.call(this, keyTime, time - keyTime);
142 | } else break;
143 | }
144 | return i < keysCount || particleList != null;
145 | }
146 |
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/src/starling/extensions/sap/ParticlePrototype.as:
--------------------------------------------------------------------------------
1 | /**
2 | * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
3 | * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice.
4 | * You may add additional accurate notices of copyright ownership.
5 | *
6 | * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/
7 | * */
8 | package starling.extensions.sap {
9 | import flash.geom.Matrix3D;
10 | import flash.geom.Vector3D;
11 |
12 | public class ParticlePrototype {
13 |
14 | // Atlas
15 | public var atlas:ParticleTextureAtlas;
16 |
17 | // Blend
18 | private var blendSource:String;
19 | private var blendDestination:String;
20 |
21 | // If true, then play animation
22 | private var animated:Boolean;
23 |
24 | // Size
25 | private var width:Number;
26 | private var height:Number;
27 |
28 | // Key frames of animation.
29 | private var timeKeys:Vector. = new Vector.();
30 | private var rotationKeys:Vector. = new Vector.();
31 | private var scaleXKeys:Vector. = new Vector.();
32 | private var scaleYKeys:Vector. = new Vector.();
33 | private var redKeys:Vector. = new Vector.();
34 | private var greenKeys:Vector. = new Vector.();
35 | private var blueKeys:Vector. = new Vector.();
36 | private var alphaKeys:Vector. = new Vector.();
37 | private var keysCount:int = 0;
38 |
39 | public function ParticlePrototype(width:Number, height:Number, atlas:ParticleTextureAtlas, animated:Boolean = false, blendSource:String = "sourceAlpha", blendDestination:String = "oneMinusSourceAlpha") {
40 | this.width = width;
41 | this.height = height;
42 | this.atlas = atlas;
43 | this.animated = animated;
44 | this.blendSource = blendSource;
45 | this.blendDestination = blendDestination;
46 | }
47 |
48 | public function addKey(time:Number, rotation:Number = 0, scaleX:Number = 1, scaleY:Number = 1, red:Number = 1, green:Number = 1, blue:Number = 1, alpha:Number = 1):void {
49 | var lastIndex:int = keysCount - 1;
50 | if (keysCount > 0 && time <= timeKeys[lastIndex]) throw new Error("Keys must be successively.");
51 | timeKeys[keysCount] = time;
52 | rotationKeys[keysCount] = rotation;
53 | scaleXKeys[keysCount] = scaleX;
54 | scaleYKeys[keysCount] = scaleY;
55 | redKeys[keysCount] = red;
56 | greenKeys[keysCount] = green;
57 | blueKeys[keysCount] = blue;
58 | alphaKeys[keysCount] = alpha;
59 | keysCount++;
60 | }
61 |
62 | public function createParticle(effect:ParticleEffect, time:Number, position:Vector3D, rotation:Number = 0, scaleX:Number = 1, scaleY:Number = 1, alpha:Number = 1, firstFrame:int = 0):void {
63 | var b:int = keysCount - 1;
64 | if (atlas.diffuse.base != null && keysCount > 1 && time >= timeKeys[0] && time < timeKeys[b]) {
65 |
66 | for (b = 1; b < keysCount; b++) {
67 | if (time < timeKeys[b]) {
68 | var systemScale:Number = effect.system.scale;
69 | var effectScale:Number = effect.scale;
70 |
71 | //localToCameraTransform;
72 | var wind:Vector3D = effect.system.wind;
73 | var gravity:Vector3D = effect.system.gravity;
74 | // Interpolation
75 | var a:int = b - 1;
76 | var t:Number = (time - timeKeys[a]) / (timeKeys[b] - timeKeys[a]);
77 | // Frame calculation
78 | var pos:int = firstFrame + (animated ? time * atlas.fps : 0);
79 | if (atlas.loop) {
80 | pos = pos % atlas.rangeLength;
81 | if (pos < 0) pos += atlas.rangeLength;
82 | } else {
83 | if (pos < 0) pos = 0;
84 | if (pos >= atlas.rangeLength) pos = atlas.rangeLength - 1;
85 | }
86 | pos += atlas.rangeBegin;
87 | var col:int = pos % atlas.columnsCount;
88 | var row:int = pos / atlas.columnsCount;
89 | // Particle creation
90 | var particle:Particle = Particle.create();
91 | particle.diffuse = atlas.diffuse.base;
92 | particle.opacity = (atlas.opacity != null) ? atlas.opacity.base : null;
93 | particle.blendSource = blendSource;
94 | particle.blendDestination = blendDestination;
95 | var cx:Number = effect.keyPosition.x + position.x * effectScale;
96 | var cy:Number = effect.keyPosition.y + position.y * effectScale;
97 | var cz:Number = effect.keyPosition.z + position.z * effectScale;
98 |
99 | var transform:Matrix3D = effect.system.transformationMatrix3D;
100 | transformVector(transform, cx, cy, cz, TEMP_VECTOR);
101 |
102 | particle.x = TEMP_VECTOR.x;
103 | particle.y = TEMP_VECTOR.y;
104 | particle.z = TEMP_VECTOR.z;
105 |
106 | var rot:Number = rotationKeys[a] + (rotationKeys[b] - rotationKeys[a]) * t;
107 | particle.rotation = (scaleX * scaleY > 0) ? (rotation + rot) : (rotation - rot);
108 | var systemScaleX:Number = systemScale * effectScale * scaleX * (scaleXKeys[a] + (scaleXKeys[b] - scaleXKeys[a]) * t);
109 | var systemScaleY:Number = systemScale * effectScale * scaleY * (scaleYKeys[a] + (scaleYKeys[b] - scaleYKeys[a]) * t);
110 | particle.width = width * systemScaleX;
111 | particle.height = height * systemScaleY;
112 | particle.originX = atlas.originX;
113 | particle.originY = atlas.originY;
114 | particle.uvScaleX = 1 / atlas.columnsCount;
115 | particle.uvScaleY = 1 / atlas.rowsCount;
116 | particle.uvOffsetX = col / atlas.columnsCount;
117 | particle.uvOffsetY = row / atlas.rowsCount;
118 | particle.red = redKeys[a] + (redKeys[b] - redKeys[a]) * t;
119 | particle.green = greenKeys[a] + (greenKeys[b] - greenKeys[a]) * t;
120 | particle.blue = blueKeys[a] + (blueKeys[b] - blueKeys[a]) * t;
121 | particle.alpha = alpha * (alphaKeys[a] + (alphaKeys[b] - alphaKeys[a]) * t);
122 | particle.next = effect.particleList;
123 | effect.particleList = particle;
124 | break;
125 | }
126 | }
127 | }
128 | }
129 |
130 | public static const RAW_DATA_CONTAINER:Vector. = new Vector.(16);
131 | private static const TEMP_VECTOR:Vector3D = new Vector3D();
132 |
133 | public static function transformVector(matrix:Matrix3D, vx:Number, vy:Number, vz:Number, result:Vector3D = null):Vector3D {
134 | if (!result) result = new Vector3D();
135 | var raw:Vector. = RAW_DATA_CONTAINER;
136 | matrix.copyRawDataTo(raw);
137 | var a:Number = raw[0];
138 | var e:Number = raw[1];
139 | var i:Number = raw[2];
140 | var m:Number = raw[3];
141 | var b:Number = raw[4];
142 | var f:Number = raw[5];
143 | var j:Number = raw[6];
144 | var n:Number = raw[7];
145 | var c:Number = raw[8];
146 | var g:Number = raw[9];
147 | var k:Number = raw[10];
148 | var o:Number = raw[11];
149 | var d:Number = raw[12];
150 | var h:Number = raw[13];
151 | var l:Number = raw[14];
152 | var p:Number = raw[15];
153 |
154 | var x:Number = vx;
155 | var y:Number = vy;
156 | var z:Number = vz;
157 | result.x = a * x + b * y + c * z + d;
158 | result.y = e * x + f * y + g * z + h;
159 | result.z = i * x + j * y + k * z + l;
160 | result.w = m * x + n * y + o * z + p;
161 | return result;
162 | }
163 |
164 | public function get lifeTime():Number {
165 | var lastIndex:int = keysCount - 1;
166 | return timeKeys[lastIndex];
167 | }
168 |
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/src/starling/extensions/sap/ParticleSystem.as:
--------------------------------------------------------------------------------
1 | /**
2 | * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
3 | * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice.
4 | * You may add additional accurate notices of copyright ownership.
5 | *
6 | * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/
7 | * */
8 | package starling.extensions.sap {
9 | import com.adobe.utils.AGALMiniAssembler;
10 |
11 | import flash.display3D.Context3D;
12 | import flash.display3D.Context3DBlendFactor;
13 | import flash.display3D.Context3DProgramType;
14 | import flash.display3D.Context3DVertexBufferFormat;
15 | import flash.display3D.IndexBuffer3D;
16 | import flash.display3D.Program3D;
17 | import flash.display3D.VertexBuffer3D;
18 | import flash.display3D.textures.TextureBase;
19 | import flash.geom.Matrix3D;
20 | import flash.geom.Vector3D;
21 | import flash.utils.getTimer;
22 |
23 | import starling.core.RenderSupport;
24 | import starling.core.Starling;
25 | import starling.display.Sprite;
26 |
27 | public class ParticleSystem extends Sprite {
28 |
29 | static private const limit:int = 31;
30 | static private var vertexBuffer:VertexBuffer3D;
31 | static private var indexBuffer:IndexBuffer3D;
32 | static private var diffuseProgram:Program3D;
33 | static private var opacityProgram:Program3D;
34 | static private var diffuseBlendProgram:Program3D;
35 | static private var opacityBlendProgram:Program3D;
36 | private var sAssembler:AGALMiniAssembler = new AGALMiniAssembler();
37 | private var mvp:Matrix3D = new Matrix3D();
38 |
39 | private var vertexConstants:Vector. = new Vector.();
40 | private var vertexConstantsRegistersCount:int = 0;
41 |
42 | public var gravity:Vector3D = new Vector3D(0, 0, -1);
43 | public var wind:Vector3D = new Vector3D();
44 | public var sortParticlesByZ:Boolean = false;
45 |
46 | public var scale:Number = 1;
47 | public var effectList:ParticleEffect;
48 |
49 | private var diffuse:TextureBase = null;
50 | private var opacity:TextureBase = null;
51 | private var blendSource:String = null;
52 | private var blendDestination:String = null;
53 | private var counter:int;
54 |
55 | public function ParticleSystem() {
56 | super();
57 | }
58 |
59 | private var pause:Boolean = false;
60 | private var stopTime:Number;
61 | private var subtractiveTime:Number = 0;
62 |
63 | public function stop():void {
64 | if (!pause) {
65 | stopTime = getTimer() * 0.001;
66 | pause = true;
67 | }
68 | }
69 |
70 | public function play():void {
71 | if (pause) {
72 | subtractiveTime += getTimer() * 0.001 - stopTime;
73 | pause = false;
74 | }
75 | }
76 |
77 | public function prevFrame():void {
78 | stopTime -= 0.001;
79 | }
80 |
81 | public function nextFrame():void {
82 | stopTime += 0.001;
83 | }
84 |
85 | public function addEffect(effect:ParticleEffect):ParticleEffect {
86 | // Checking on belonging
87 | if (effect.system != null) throw new Error("Cannot add the same effect twice.");
88 | // Set parameters
89 | effect.startTime = getTime();
90 | effect.system = this;
91 | effect.setPositionKeys(0);
92 | effect.setDirectionKeys(0);
93 | // Add
94 | effect.nextInSystem = effectList;
95 | effectList = effect;
96 | return effect;
97 | }
98 |
99 | public function getEffectByName(name:String):ParticleEffect {
100 | for (var effect:ParticleEffect = effectList; effect != null; effect = effect.nextInSystem) {
101 | if (effect.name == name) return effect;
102 | }
103 | return null;
104 | }
105 |
106 | public function getTime():Number {
107 | return pause ? (stopTime - subtractiveTime) : (getTimer() * 0.001 - subtractiveTime);
108 | }
109 |
110 |
111 | override public function render(support:RenderSupport, parentAlpha:Number):void {
112 | support.finishQuadBatch();
113 |
114 | // Create geometry and program
115 | if (vertexBuffer == null) createAndUpload(Starling.context);
116 | // Loop items
117 | var visibleEffectList:ParticleEffect;
118 | var time:Number = getTime();
119 | for (var effect:ParticleEffect = effectList, prev:ParticleEffect = null; effect != null;) {
120 | // Check if actual
121 | var effectTime:Number = time - effect.startTime;
122 | if (effectTime <= effect.lifeTime) {
123 | if (effect.calculate(effectTime)) {
124 | // Add
125 | if (effect.particleList != null) {
126 | effect.next = visibleEffectList;
127 | visibleEffectList = effect;
128 | }
129 | prev = effect;
130 | effect = effect.nextInSystem;
131 | } else {
132 | // Removing
133 | if (prev != null) {
134 | prev.nextInSystem = effect.nextInSystem;
135 | effect = prev.nextInSystem;
136 | } else {
137 | effectList = effect.nextInSystem;
138 | effect = effectList;
139 | }
140 | }
141 | } else {
142 | // Removing
143 | if (prev != null) {
144 | prev.nextInSystem = effect.nextInSystem;
145 | effect = prev.nextInSystem;
146 | } else {
147 | effectList = effect.nextInSystem;
148 | effect = effectList;
149 | }
150 | }
151 | }
152 | // Gather draws
153 | if (visibleEffectList != null) {
154 | if (visibleEffectList.next != null) {
155 | drawConflictEffects(support, visibleEffectList);
156 | } else {
157 | drawParticleList(support, visibleEffectList.particleList);
158 | visibleEffectList.particleList = null;
159 | }
160 | flush(support);
161 | diffuse = null;
162 | opacity = null;
163 | blendSource = null;
164 | blendDestination = null;
165 | }
166 |
167 | var context:Context3D = Starling.context;
168 | context.setTextureAt(0, null);
169 | context.setTextureAt(1, null);
170 | }
171 |
172 | private function createAndUpload(context:Context3D):void {
173 | var vertices:Vector. = new Vector.();
174 | var indices:Vector. = new Vector.();
175 | for (var i:int = 0; i < limit; i++) {
176 | vertices.push(
177 | 0, 0, i * 4,
178 | 0, 1, i * 4,
179 | 1, 0, i * 4,
180 | 1, 1, i * 4);
181 | indices.push(i * 4, i * 4 + 1, i * 4 + 2, i * 4 + 1, i * 4 + 3, i * 4 + 2);
182 | }
183 | vertexBuffer = context.createVertexBuffer(limit * 4, 3);
184 | vertexBuffer.uploadFromVector(vertices, 0, limit * 4);
185 | indexBuffer = context.createIndexBuffer(limit * 6);
186 | indexBuffer.uploadFromVector(indices, 0, limit * 6);
187 | var vertexProgram:String =
188 | // Pivot
189 | "mov vt2, vc[va0.z]\n" + // originX, originY, width, height
190 | "sub vt0.z, va0.x, vt2.x\n" +
191 | "sub vt0.w, va0.y, vt2.y\n" +
192 | // Width and height
193 | "mul vt0.z, vt0.z, vt2.z\n" +
194 | "mul vt0.w, vt0.w, vt2.w\n" +
195 | // Rotation
196 | "mov vt2, vc[va0.z+1]\n" + // x, y, sin, cos
197 | "mul vt1.z, vt0.z, vt2.w\n" + // x*cos
198 | "mul vt1.w, vt0.w, vt2.z\n" + // y*sin
199 | "sub vt0.x, vt1.z, vt1.w\n" + // X
200 | "mul vt1.z, vt0.z, vt2.z\n" + // x*sin
201 | "mul vt1.w, vt0.w, vt2.w\n" + // y*cos
202 | "add vt0.y, vt1.z, vt1.w\n" + // Y
203 | // Translation
204 | "add vt0.x, vt0.x, vt2.x\n" +
205 | "add vt0.y, vt0.y, vt2.y\n" +
206 | "mov vt0.zw, va0.ww\n" +
207 | // Projection
208 | // "m44 op, vt0, vc124\n" +
209 | "dp4 op.x, vt0, vc124\n" +
210 | "dp4 op.y, vt0, vc125\n" +
211 | "dp4 op.z, vt0, vc126\n" +
212 | "dp4 op.w, vt0, vc127\n" +
213 | // UV correction and passing out
214 | "mov vt2, vc[va0.z+2]\n" + // uvScaleX, uvScaleY, uvOffsetX, uvOffsetY
215 | "mul vt1.x, va0.x, vt2.x\n" +
216 | "mul vt1.y, va0.y, vt2.y\n" +
217 | "add vt1.x, vt1.x, vt2.z\n" +
218 | "add vt1.y, vt1.y, vt2.w\n" +
219 | "mov v0, vt1.xy\n" +
220 | // Passing color
221 | "mov v1, vc[va0.z+3]\n";// red, green, blue, alpha
222 |
223 | var fragmentDiffuseProgram:String =
224 | "tex ft0, v0, fs0 <2d,clamp,linear,miplinear>\n" +
225 | "mul ft0, ft0, v1\n" +
226 |
227 | "mov oc, ft0\n";
228 |
229 | var fragmentOpacityProgram:String =
230 | "tex ft0, v0, fs0 <2d,clamp,linear,miplinear>\n" +
231 | "tex ft1, v0, fs1 <2d,clamp,linear,miplinear>,dxt1\n" +
232 | "mov ft0.w, ft1.x\n" +
233 | "mul ft0, ft0, v1\n" +
234 |
235 | "mov oc, ft0\n";
236 |
237 | var fragmentDiffuseBlendProgram:String =
238 | "tex ft0, v0, fs0 <2d,clamp,linear,miplinear>\n" +
239 | "mul ft0, ft0, v1\n" +
240 |
241 | "mov oc, ft0\n";
242 |
243 | var fragmentOpacityBlendProgram:String =
244 | "tex ft0, v0, fs0 <2d,clamp,linear,miplinear>\n" +
245 | "tex ft1, v0, fs1 <2d,clamp,linear,miplinear,dxt1>\n" +
246 | "mov ft0.w, ft1.x\n" +
247 | "mul ft0, ft0, v1\n" +
248 |
249 | "mov oc, ft0";
250 | diffuseProgram = context.createProgram();
251 | opacityProgram = context.createProgram();
252 | diffuseBlendProgram = context.createProgram();
253 | opacityBlendProgram = context.createProgram();
254 |
255 | diffuseProgram.upload(
256 | sAssembler.assemble(Context3DProgramType.VERTEX, vertexProgram),
257 | sAssembler.assemble(Context3DProgramType.FRAGMENT, fragmentDiffuseProgram));
258 |
259 | opacityProgram.upload(
260 | sAssembler.assemble(Context3DProgramType.VERTEX, vertexProgram),
261 | sAssembler.assemble(Context3DProgramType.FRAGMENT, fragmentOpacityProgram));
262 |
263 | diffuseBlendProgram.upload(
264 | sAssembler.assemble(Context3DProgramType.VERTEX, vertexProgram),
265 | sAssembler.assemble(Context3DProgramType.FRAGMENT, fragmentDiffuseBlendProgram));
266 |
267 | opacityBlendProgram.upload(
268 | sAssembler.assemble(Context3DProgramType.VERTEX, vertexProgram),
269 | sAssembler.assemble(Context3DProgramType.FRAGMENT, fragmentOpacityBlendProgram));
270 | }
271 |
272 |
273 | private function flush(support:RenderSupport):void {
274 | var context:Context3D = Starling.context;
275 |
276 | var numTriangles:Number = counter << 1;
277 | var program:Program3D;
278 | if (blendDestination == Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA) {
279 | program = (opacity != null) ? opacityProgram : diffuseProgram;
280 | } else {
281 | program = (opacity != null) ? opacityBlendProgram : diffuseBlendProgram;
282 | }
283 | context.setProgram(program);
284 |
285 | // Set streams
286 | context.setVertexBufferAt(0, vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3);
287 | context.setBlendFactors(blendSource, blendDestination);
288 |
289 | context.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 0, vertexConstants, vertexConstantsRegistersCount);
290 | // Set constants
291 | mvp.copyFrom(support.mvpMatrix3D);
292 | context.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 124, mvp, true);
293 |
294 | context.setTextureAt(0, diffuse);
295 | if (opacity != null) context.setTextureAt(1, opacity);
296 |
297 | support.raiseDrawCount();
298 |
299 | context.drawTriangles(indexBuffer, 0, numTriangles);
300 | counter = 0;
301 | }
302 |
303 | private function drawParticleList(support:RenderSupport, list:Particle):void {
304 | if (sortParticlesByZ && list.next != null) list = sortParticleList(list);
305 | // Gather draws
306 | var last:Particle;
307 | for (var particle:Particle = list; particle != null; particle = particle.next) {
308 | if (counter >= limit || particle.diffuse != diffuse || particle.opacity != opacity || particle.blendSource != blendSource || particle.blendDestination != blendDestination) {
309 | if (counter > 0) {
310 | flush(support);
311 | }
312 |
313 | diffuse = particle.diffuse;
314 | opacity = particle.opacity;
315 | blendSource = particle.blendSource;
316 | blendDestination = particle.blendDestination;
317 | counter = 0;
318 | vertexConstantsRegistersCount = 0;
319 | vertexConstants.length = 0;
320 | }
321 | // Write constants
322 | var offset:int = counter << 2;
323 |
324 | setVertexConstantsFromNumbers(offset++, particle.originX, particle.originY, particle.width, particle.height);
325 | setVertexConstantsFromNumbers(offset++, particle.x, particle.y, Math.sin(particle.rotation), Math.cos(particle.rotation));
326 | setVertexConstantsFromNumbers(offset++, particle.uvScaleX, particle.uvScaleY, particle.uvOffsetX, particle.uvOffsetY);
327 | setVertexConstantsFromNumbers(offset++, particle.red, particle.green, particle.blue, particle.alpha);
328 |
329 | counter++;
330 | last = particle;
331 | }
332 | // Send to the collector
333 | last.next = Particle.collector;
334 | Particle.collector = list;
335 | }
336 |
337 | private function setVertexConstantsFromNumbers(firstRegister:int, x:Number, y:Number, z:Number, w:Number = 1):void {
338 | if (uint(firstRegister) > 127) throw new Error("Register index " + firstRegister + " is out of bounds.");
339 | var offset:int = firstRegister << 2;
340 | if (firstRegister + 1 > vertexConstantsRegistersCount) {
341 | vertexConstantsRegistersCount = firstRegister + 1;
342 | vertexConstants.length = vertexConstantsRegistersCount << 2;
343 | }
344 | vertexConstants[offset] = x;
345 | offset++;
346 | vertexConstants[offset] = y;
347 | offset++;
348 | vertexConstants[offset] = z;
349 | offset++;
350 | vertexConstants[offset] = w;
351 | }
352 |
353 | private static function sortParticleList(list:Particle):Particle {
354 | var left:Particle = list;
355 | var right:Particle = list.next;
356 | while (right != null && right.next != null) {
357 | list = list.next;
358 | right = right.next.next;
359 | }
360 | right = list.next;
361 | list.next = null;
362 | if (left.next != null) {
363 | left = sortParticleList(left);
364 | }
365 | if (right.next != null) {
366 | right = sortParticleList(right);
367 | }
368 | var flag:Boolean = left.z > right.z;
369 | if (flag) {
370 | list = left;
371 | left = left.next;
372 | } else {
373 | list = right;
374 | right = right.next;
375 | }
376 | var last:Particle = list;
377 | while (true) {
378 | if (left == null) {
379 | last.next = right;
380 | return list;
381 | } else if (right == null) {
382 | last.next = left;
383 | return list;
384 | }
385 | if (flag) {
386 | if (left.z > right.z) {
387 | last = left;
388 | left = left.next;
389 | } else {
390 | last.next = right;
391 | last = right;
392 | right = right.next;
393 | flag = false;
394 | }
395 | } else {
396 | if (right.z > left.z) {
397 | last = right;
398 | right = right.next;
399 | } else {
400 | last.next = left;
401 | last = left;
402 | left = left.next;
403 | flag = true;
404 | }
405 | }
406 | }
407 | return null;
408 | }
409 |
410 | private function drawConflictEffects(support:RenderSupport, effectList:ParticleEffect):void {
411 | var particleList:Particle;
412 | for (var effect:ParticleEffect = effectList; effect != null; effect = next) {
413 | var next:ParticleEffect = effect.next;
414 | effect.next = null;
415 | var last:Particle = effect.particleList;
416 | while (last.next != null) last = last.next;
417 | last.next = particleList;
418 | particleList = effect.particleList;
419 | effect.particleList = null;
420 | }
421 | drawParticleList(support, particleList);
422 | }
423 | }
424 | }
425 |
--------------------------------------------------------------------------------