├── 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 | [![demo](https://dl.dropboxusercontent.com/u/123272146/sap/screen.png)](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 | --------------------------------------------------------------------------------