123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769 |
- package h2d;
- /**
- See `ParticleGroup.sortMode` - not used anywhere.
- **/
- @:dox(hide)
- enum PartSortMode {
- /**
- Particles are not sorted.
- **/
- None;
- /**
- Particles are sorted back-to-front every frame based on their current position.
- **/
- Dynamic;
- }
- /**
- The particle emission pattern modes. See `ParticleGroup.emitMode`.
- **/
- enum PartEmitMode {
- /**
- A single Point, that emits in all directions.
- **/
- Point;
- /**
- A cone, parametrized with `emitAngle` and `emitDistance`.
- **/
- Cone;
- /**
- A box, parametrized with `emitDist` and `emitDistY`.
- **/
- Box;
- /**
- A box, parametrized with `emitAngle` and `emitDistance`.
- **/
- Direction;
- }
- private class ParticleShader extends hxsl.Shader {
- static var SRC = {
- @input var input : { color : Vec4 };
- @const var hasGradient : Bool;
- @const var has2DGradient : Bool;
- @param var gradient : Sampler2D;
- var pixelColor : Vec4;
- var textureColor : Vec4;
- function fragment() {
- pixelColor = textureColor; // ignore input.color RGB
- pixelColor.a *= input.color.a;
- if( has2DGradient ) {
- var g = gradient.get(vec2(input.color.r, textureColor.r));
- pixelColor.rgb = g.rgb;
- pixelColor.a *= g.a;
- } else if( hasGradient )
- pixelColor *= gradient.get(input.color.rg);
- }
- }
- }
- @:access(h2d.ParticleGroup)
- private class Particle extends h2d.SpriteBatch.BatchElement {
- var group : ParticleGroup;
- public var vx : Float;
- public var vy : Float;
- public var vSize : Float;
- public var vr : Float;
- public var maxLife : Float;
- public var life : Float;
- public var delay : Float;
- public function new(group) {
- super(null);
- this.group = group;
- }
- override function update(et:Float) {
- if( delay > 0 ) {
- delay -= et;
- if( delay <= 0 )
- visible = true;
- else {
- visible = false;
- return true;
- }
- }
- var dv = Math.pow(1 + group.speedIncr, et);
- vx *= dv;
- vy *= dv;
- vx += group.gravity * et * group.sinGravityAngle;
- vy += group.gravity * et * group.cosGravityAngle;
- x += vx * et;
- y += vy * et;
- life += et;
- if( group.rotAuto )
- rotation = Math.atan2(vy, vx) + life * vr + group.rotInit * Math.PI;
- else
- rotation += vr * et;
- if (group.incrX)
- scaleX *= Math.pow(1 + vSize, et);
- if (group.incrY)
- scaleY *= Math.pow(1 + vSize, et);
- var t = life / maxLife;
- if( t < group.fadeIn )
- alpha = Math.pow(t / group.fadeIn, group.fadePower);
- else if( t > group.fadeOut )
- alpha = Math.pow((1 - t) / (1 - group.fadeOut), group.fadePower);
- else
- alpha = 1;
- r = t; // pass to pshader for colorGradient
- if( group.animationRepeat > 0 )
- this.t = group.tiles[Std.int(t * group.tiles.length * group.animationRepeat) % group.tiles.length];
- if( t > 1 ) {
- if( group.emitLoop ) {
- @:privateAccess group.init(this);
- delay = 0;
- } else
- return false;
- }
- return true;
- }
- }
- /**
- An emitter of a single particle group. Part of `Particles` simulation system.
- **/
- @:access(h2d.SpriteBatch)
- @:access(h2d.Object)
- class ParticleGroup {
- static var FIELDS = null;
- static function getFields( inst : ParticleGroup ) {
- if( FIELDS != null )
- return FIELDS;
- FIELDS = Type.getInstanceFields(ParticleGroup);
- for( f in ["parts", "pshader", "batch", "needRebuild", "emitMode", "sortMode", "blendMode", "texture", "colorGradient", "tiles"] )
- FIELDS.remove(f);
- for( f in FIELDS.copy() )
- if( Reflect.isFunction(Reflect.field(inst, f)) )
- FIELDS.remove(f);
- FIELDS.sort(Reflect.compare);
- return FIELDS;
- }
- var parts : Particles;
- var batch : SpriteBatch;
- var needRebuild = true;
- var tiles : Array<h2d.Tile>;
- /**
- The group name.
- **/
- public var name : String;
- /**
- Disabling the group immediately removes it from rendering and resets it's state.
- **/
- public var enable(default, set) : Bool = true;
- /**
- Does nothing.
- **/
- public var sortMode(default, set) : PartSortMode = None;
- /**
- Configures blending mode for this group.
- **/
- public var blendMode(default, set) : BlendMode = Alpha;
- /**
- Maximum number of particles alive at a time.
- **/
- public var nparts(default, set) : Int = 100;
- /**
- Initial particle X offset.
- **/
- public var dx(default, set) : Int = 0;
- /**
- Initial particle Y offset.
- **/
- public var dy(default, set) : Int = 0;
- /**
- If enabled, group will emit new particles indefinitely maintaining number of particles at `ParticleGroup.nparts`.
- **/
- public var emitLoop(default, set) : Bool = true;
- /**
- The pattern in which particles are emitted. See individual `PartEmitMode` values for more details.
- **/
- public var emitMode(default, set):PartEmitMode = Point;
- /**
- Initial particle position distance from emission point.
- **/
- public var emitStartDist(default, set) : Float = 0.;
- /**
- Additional random particle position distance from emission point.
- **/
- public var emitDist(default, set) : Float = 50.;
- /**
- Secondary random position distance modifier (used by `Box` emitMode)
- **/
- public var emitDistY(default, set) : Float = 50.;
- /**
- Normalized particle emission direction angle.
- **/
- public var emitAngle(default, set) : Float = -0.5;
- /**
- When enabled, particle rotation will match the particle movement direction angle.
- **/
- public var emitDirectionAsAngle(default, set) : Bool = false;
- /**
- Randomized synchronization delay before particle appears after being emitted.
- Usage note for non-relative mode: Particle will use configuration that was happened at time of emission, not when delay timer runs out.
- **/
- public var emitSync(default, set) : Float = 0;
- /**
- Fixed delay before particle appears after being emitted.
- Usage note for non-relative mode: Particle will use configuration that was happened at time of emission, not when delay timer runs out.
- **/
- public var emitDelay(default, set) : Float = 0;
- /**
- Initial particle size.
- **/
- public var size(default, set) : Float = 1;
- /**
- If set, particle will change it's size with time.
- **/
- public var sizeIncr(default, set) : Float = 0;
- /**
- If enabled, particle will increase on X-axis with `sizeIncr`.
- **/
- public var incrX(default, set) : Bool = true;
- /**
- If enabled, particle will increase on Y-axis with `sizeIncr`.
- **/
- public var incrY(default, set) : Bool = true;
- /**
- Additional random size increase when particle is created.
- **/
- public var sizeRand(default, set) : Float = 0;
- /**
- Initial particle lifetime.
- **/
- public var life(default, set) : Float = 1;
- /**
- Additional random lifetime increase when particle is created.
- **/
- public var lifeRand(default, set) : Float = 0;
- /**
- Initial particle velocity.
- **/
- public var speed(default, set) : Float = 50.;
- /**
- Additional random velocity increase when particle is created.
- **/
- public var speedRand(default, set) : Float = 0;
- /**
- If set, particle velocity will change over time.
- **/
- public var speedIncr(default, set) : Float = 0;
- /**
- Gravity applied to the particle.
- **/
- public var gravity(default, set) : Float = 0;
- /**
- The gravity angle in radians. `0` points down.
- **/
- public var gravityAngle(default, set) : Float = 0;
- @:noCompletion @:dox(hide) public var cosGravityAngle : Float;
- @:noCompletion @:dox(hide) public var sinGravityAngle : Float;
- /**
- Initial particle rotation.
- **/
- public var rotInit(default, set) : Float = 0;
- /**
- Initial rotation speed of the particle.
- **/
- public var rotSpeed(default, set) : Float = 0;
- /**
- Additional random rotation speed when particle is created.
- **/
- public var rotSpeedRand(default, set):Float = 0;
- /**
- If enabled, particles will be automatically rotated in the direction of particle velocity.
- **/
- public var rotAuto = false;
- /**
- The time in seconds during which particle alpha fades in after being emitted.
- **/
- public var fadeIn : Float = 0.2;
- /**
- The time in seconds at which particle will start to fade out before dying. Fade out time can be calculated with `lifetime - fadeOut`.
- **/
- public var fadeOut : Float = 0.8;
- /**
- The exponent of the alpha transition speed on fade in and fade out.
- **/
- public var fadePower : Float = 1;
- /**
- Total count of frames used by the group.
- When 0, amount of frames in a group calculated by `frameDivisionX * frameDivisionY`.
- Otherwise it's `min(frameDivisionX * frameDivisionY, frameCount)`.
- **/
- public var frameCount(default,set) : Int = 0;
- /**
- Horizontal frame divisor.
- **/
- public var frameDivisionX(default,set) : Int = 1;
- /**
- Vertical frame divisor.
- **/
- public var frameDivisionY(default,set) : Int = 1;
- /**
- The amount of times the animations will loop during lifetime.
- Settings it to 0 will stop the animation playback and each particle will have a random frame assigned at emission time.
- **/
- public var animationRepeat(default,set) : Float = 1;
- /**
- The texture used to render particles.
- **/
- public var texture(default,set) : h3d.mat.Texture;
- /**
- Optional color gradient texture for tinting.
- **/
- public var colorGradient(default,set) : h3d.mat.Texture;
- /**
- When enabled, causes particles to always render relative to the emitter position, moving along with it.
- Otherwise, once emitted, particles won't follow the emitter, and will render relative to the scene origin.
- Non-relative mode is useful for simulating something like a smoke coming from a moving object,
- while relative mode things like jet flame that have to stick to its emission source.
- **/
- public var isRelative(default, set) : Bool = true;
- /**
- Should group rebuild on parameters change.
- Note that some parameters take immediate effect on the existing particles, and some would force rebuild regardless of this setting.
- Parameters that take immediate effect:
- `speedIncr`, `gravity`, `gravityAngle`, `fadeIn`, `fadeOut`, `fadePower`, `rotAuto`, `rotInit`, `incrX`, `incrY`, `emitLoop` and `blendMode`
- Parameters that will always force rebuild:
- `enable`, `sortMode`, `isRelative`, `texture`, `frameCount`, `frameDivisionX`, `frameDivisionY` and `nparts`
- Parameters that newer cause rebuild:
- `blendMode`, `colorGradient` and `animationRepeat`
- **/
- public var rebuildOnChange : Bool = true;
- inline function set_enable(v) { enable = v; if( !v ) { batch.clear(); needRebuild = true; }; return v; }
- inline function set_sortMode(v) { needRebuild = true; return sortMode = v; }
- inline function set_blendMode(v) { batch.blendMode = v; return blendMode = v; }
- inline function set_size(v) { if (rebuildOnChange) needRebuild = true; return size = v; }
- inline function set_sizeRand(v) { if (rebuildOnChange) needRebuild = true; return sizeRand = v; }
- inline function set_sizeIncr(v) { if (rebuildOnChange) needRebuild = true; return sizeIncr = v; }
- inline function set_incrX(v) { if (rebuildOnChange) needRebuild = true; return incrX = v; }
- inline function set_incrY(v) { if (rebuildOnChange) needRebuild = true; return incrY = v; }
- inline function set_speed(v) { if (rebuildOnChange) needRebuild = true; return speed = v; }
- inline function set_speedIncr(v) { if (rebuildOnChange) needRebuild = true; return speedIncr = v; }
- inline function set_gravity(v) { if (rebuildOnChange) needRebuild = true; return gravity = v; }
- inline function set_gravityAngle(v : Float) {
- if (rebuildOnChange) needRebuild = true;
- cosGravityAngle = Math.cos(v * Math.PI * 0.5);
- sinGravityAngle = Math.sin(v * Math.PI * 0.5);
- return gravityAngle = v;
- }
- inline function set_speedRand(v) { if (rebuildOnChange) needRebuild = true; return speedRand = v; }
- inline function set_life(v) { if (rebuildOnChange) needRebuild = true; return life = v; }
- inline function set_lifeRand(v) { if (rebuildOnChange) needRebuild = true; return lifeRand = v; }
- inline function set_nparts(n) { needRebuild = true; return nparts = n; }
- inline function set_dx(v) { if (rebuildOnChange) needRebuild = true; return dx = v; }
- inline function set_dy(v) { if (rebuildOnChange) needRebuild = true; return dy = v; }
- inline function set_emitLoop(v) { if (rebuildOnChange) needRebuild = true; return emitLoop = v; }
- inline function set_emitMode(v) { if (rebuildOnChange) needRebuild = true; return emitMode = v; }
- inline function set_emitStartDist(v) { if (rebuildOnChange) needRebuild = true; return emitStartDist = v; }
- inline function set_emitDist(v) { if (rebuildOnChange) needRebuild = true; return emitDist = v; }
- inline function set_emitDistY(v) { if (rebuildOnChange) needRebuild = true; return emitDistY = v; }
- inline function set_emitAngle(v) { if (rebuildOnChange) needRebuild = true; return emitAngle = v; }
- inline function set_emitDirectionAsAngle(v) { if (rebuildOnChange) needRebuild = true; return emitDirectionAsAngle = v; }
- inline function set_emitSync(v) { if (rebuildOnChange) needRebuild = true; return emitSync = v; }
- inline function set_emitDelay(v) { if (rebuildOnChange) needRebuild = true; return emitDelay = v; }
- inline function set_rotInit(v) { if (rebuildOnChange) needRebuild = true; return rotInit = v; }
- inline function set_rotSpeed(v) { if (rebuildOnChange) needRebuild = true; return rotSpeed = v; }
- inline function set_rotSpeedRand(v) { if (rebuildOnChange) needRebuild = true; return rotSpeedRand = v; }
- inline function set_texture(t) { texture = t; makeTiles(); return t; }
- inline function set_colorGradient(t) { colorGradient = t; return t; }
- inline function set_frameCount(v) { frameCount = v; makeTiles(); return v; }
- inline function set_frameDivisionX(v) { frameDivisionX = v; makeTiles(); return v; }
- inline function set_frameDivisionY(v) { frameDivisionY = v; makeTiles(); return v; }
- inline function set_animationRepeat(v) return animationRepeat = v;
- inline function set_isRelative(v) { needRebuild = true; return isRelative = v; }
- /**
- Create a new particle group instance.
- @param p The parent Particles instance. Group does not automatically adds itself to the Particles.
- **/
- public function new(p) {
- this.parts = p;
- batch = new SpriteBatch(null, p);
- batch.visible = false;
- batch.hasRotationScale = true;
- batch.hasUpdate = true;
- this.texture = null;
- }
- function makeTiles() {
- var t : h2d.Tile;
- // hide : create default particle
- if( h3d.Engine.getCurrent() == null ) {
- tiles = [];
- needRebuild = true;
- return;
- }
- if( texture == null )
- t = h2d.Tile.fromColor(0xFFFFFF, 16, 16);
- else
- t = h2d.Tile.fromTexture(texture);
- batch.tile = t;
- var dx = Std.int(t.width / frameDivisionX);
- var dy = Std.int(t.height / frameDivisionY);
- tiles = [for( y in 0...frameDivisionY ) for( x in 0...frameDivisionX ) if( frameCount == 0 || y * frameDivisionX + x < frameCount ) t.sub(x * dx, y * dy, dx, dy, -dx >> 1, -dy >> 1)];
- needRebuild = true;
- }
- /**
- Reset current state of particle group and re-emit all particles.
- **/
- public function rebuild() {
- needRebuild = false;
- batch.clear();
- for( i in 0...nparts ) {
- var p = new Particle(this);
- batch.add(p);
- init(p);
- }
- }
- function init( p : Particle ) {
- inline function srand() return hxd.Math.srand();
- inline function rand() return hxd.Math.random();
- inline function getAngleFromNormalized(a : Float, rand : Float = 1.) : Float {
- var newAngle = a * 0.5 * Math.PI * rand;
- if (a < 0) newAngle += Math.PI;
- return newAngle;
- };
- var g = this;
- var size = g.size * (1 + srand() * g.sizeRand);
- var rot = srand() * Math.PI * g.rotInit;
- var vrot = g.rotSpeed * (1 + rand() * g.rotSpeedRand) * (srand() < 0 ? -1 : 1);
- var life = g.life * (1 + srand() * g.lifeRand);
- var delay = rand() * life * (1 - g.emitSync) + g.emitDelay;
- var speed = g.speed * (1 + srand() * g.speedRand);
- if( g.life == 0 )
- life = 1e10;
- p.x = dx;
- p.y = dy;
- switch( g.emitMode ) {
- case Point:
- p.vx = srand();
- p.vy = srand();
- speed *= 1 / hxd.Math.sqrt(p.vx * p.vx + p.vy * p.vy);
- var r = g.emitStartDist + g.emitDist * rand();
- p.x += p.vx * r;
- p.y += p.vy * r;
- case Cone:
- if (g.emitAngle == 0) {
- p.vx = 0.;
- p.vy = 0.;
- }
- else {
- var theta = rand() * Math.PI * 2;
- var phi = getAngleFromNormalized(g.emitAngle, srand());
- p.vx = Math.sin(phi) * Math.cos(theta);
- p.vy = Math.cos(phi);
- }
- var r = g.emitStartDist + g.emitDist * rand();
- p.x += p.vx * r;
- p.y += p.vy * r;
- case Box:
- p.vx = srand();
- p.vy = srand();
- p.x += g.emitDist * srand();
- p.y += g.emitDistY * srand();
- var a = getAngleFromNormalized(g.emitAngle);
- var cosA = Math.cos(a);
- var sinA = Math.sin(a);
- var xx = cosA * (p.x - dx) - sinA * (p.y - dy) + dx;
- var yy = sinA * (p.x - dx) + cosA * (p.y - dy) + dy;
- p.x = xx;
- p.y = yy;
- case Direction:
- speed = Math.abs(speed);
- p.vx = Math.cos(g.emitAngle);
- p.vy = Math.sin(g.emitAngle);
- var r = g.emitStartDist + g.emitDist * rand();
- p.x += r * Math.cos(g.emitAngle - Math.PI / 2);
- p.y += r * Math.sin(g.emitAngle - Math.PI / 2);
- }
- p.scale = size;
- p.rotation = rot;
- p.vSize = g.sizeIncr;
- p.vr = vrot;
- p.t = animationRepeat == 0 ? tiles[Std.random(tiles.length)] : tiles[0];
- p.delay = delay;
- p.vx *= speed;
- p.vy *= speed;
- p.life = 0;
- p.maxLife = life;
- var rot = emitDirectionAsAngle ? Math.atan2(p.vy, p.vx) : srand() * Math.PI * g.rotInit;
- p.rotation = rot;
- if ( !isRelative ) {
- // Less this.parts access
- var parts = this.parts;
- // calcAbsPos() was already called, because during both rebuild() and Particle.update()
- // called during sync() call which calls this function if required before any of this happens.
- //parts.syncPos();
- var px = p.x;
- p.x = px * parts.matA + p.y * parts.matC + parts.absX;
- p.y = px * parts.matB + p.y * parts.matD + parts.absY;
- p.scaleX = Math.sqrt((parts.matA * parts.matA) + (parts.matC * parts.matC)) * size;
- p.scaleY = Math.sqrt((parts.matB * parts.matB) + (parts.matD * parts.matD)) * size;
- var rot = Math.atan2(parts.matB / p.scaleY, parts.matA / p.scaleX);
- p.rotation += rot;
- // Also rotate velocity.
- var cos = Math.cos(rot);
- var sin = Math.sin(rot);
- px = p.vx;
- p.vx = px * cos - p.vy * sin;
- p.vy = px * sin + p.vy * cos;
- }
- }
- /**
- Saves the particle group configuration into a `Dynamic` object.
- **/
- public function save() {
- var o : Dynamic = {};
- for( f in getFields(this) )
- Reflect.setField(o, f, Reflect.field(this, f));
- o.emitMode = emitMode.getName();
- o.sortMode = sortMode.getName();
- o.blendMode = blendMode.getName();
- if( texture != null ) o.texture = texture.name;
- if( colorGradient != null ) o.colorGradient = colorGradient.name;
- return o;
- }
- /**
- Loads the particle group configuration from a given object.
- @param version The version of Particles that were used to save the configuration.
- @param o The previously saved configuration data to load.
- **/
- public function load( version : Int, o : Dynamic ) {
- for( f in getFields(this) )
- if( Reflect.hasField(o,f) )
- Reflect.setProperty(this, f, Reflect.field(o, f));
- emitMode = PartEmitMode.createByName(o.emitMode);
- sortMode = PartSortMode.createByName(o.sortMode);
- blendMode = BlendMode.createByName(o.blendMode);
- if( o.texture != null ) texture = @:privateAccess parts.loadTexture(o.texture);
- if( o.colorGradient != null ) colorGradient = @:privateAccess parts.loadTexture(o.colorGradient);
- }
- }
- /**
- A 2D particle system with wide range of customizability.
- The Particles instance can contain multiple `ParticleGroup` instances - each of which works independently from one another.
- To simplify designing of the particles [HIDE](https://github.com/HeapsIO/hide/) contains a dedicated 2D particle editor and
- stores the particle data in a JSON format, which then can be loaded with the `Particles.load` method:
- ```haxe
- var part = new h2d.Particles();
- part.load(haxe.Json.parse(hxd.Res.my_parts_file.entry.getText()), hxd.Res.my_parts_file.entry.path);
- ```
- **/
- @:access(h2d.ParticleGroup)
- class Particles extends Drawable {
- static inline var VERSION = 1;
- var groups : Array<ParticleGroup>;
- var resourcePath : String;
- var hideProps : Dynamic;
- var pshader : ParticleShader;
- /**
- Create a new Particles instance.
- @param parent An optional parent `h2d.Object` instance to which Particles adds itself if set.
- **/
- public function new( ?parent ) {
- super(parent);
- groups = [];
- pshader = new ParticleShader();
- addShader(pshader);
- }
- function loadTexture( path : String ) {
- return hxd.res.Loader.currentInstance.load(path).toTexture();
- }
- /**
- Sent when all particle groups stopped playback.
- Restarts all groups by default.
- **/
- public dynamic function onEnd() {
- for( g in groups )
- g.needRebuild = true;
- }
- /**
- Saves Particles settings and returns an object that can be saved into a file and then loaded with a `Particles.load` method.
- **/
- public function save() : Dynamic {
- var obj : Dynamic = { type : "particles2D", version : VERSION, groups : [for( g in groups ) g.save()] };
- if( hideProps != null ) obj.hide = hideProps;
- return obj;
- }
- /**
- Loads previously saved Particles settings.
- @param o The saved Particles settings.
- @param resourcePath An optional path of the configuration file. May be safely omitted.
- **/
- public function load( o : Dynamic, ?resourcePath : String ) {
- this.resourcePath = resourcePath;
- if( o.version == 0 || o.version > VERSION ) throw "Unsupported version " + o.version;
- for( g in (o.groups:Array<Dynamic>) )
- addGroup().load(o.version, g);
- hideProps = o.hide;
- }
- /**
- Add new particle group to the Particles.
- @param g Particle group to add. If null, will create an empty ParticleGroup.
- Note that when passing existing group, it should be created with this Particles instanceas the constructor argument,
- otherwise it may lead to undefined behavior.
- @param index Optional insertion index at which the group should be inserted.
- @returns Added ParticleGroup instance.
- **/
- public function addGroup( ?g : ParticleGroup, ?index ) {
- if( g == null )
- g = new ParticleGroup(this);
- if( g.name == null )
- g.name = "Group#" + (groups.length + 1);
- if( index == null )
- index = groups.length;
- groups.insert(index, g);
- return g;
- }
- /**
- Removes the group from the Particles.
- **/
- public function removeGroup( g : ParticleGroup ) {
- var idx = groups.indexOf(g);
- if( idx < 0 ) return;
- groups.splice(idx,1);
- }
- /**
- Returns a group with a specified name or `null` if none found.
- **/
- public function getGroup( name : String ) {
- for( g in groups )
- if( g.name == name )
- return g;
- return null;
- }
- override function sync(ctx:RenderContext) {
- super.sync(ctx);
- var hasPart = false;
- for( g in groups ) {
- if ( g.needRebuild && g.enable )
- g.rebuild();
- if( @:privateAccess g.batch.first != null )
- hasPart = true;
- }
- if( !hasPart )
- onEnd();
- }
- override function draw(ctx:RenderContext) {
- var old = blendMode;
- var realX : Float = absX;
- var realY : Float = absY;
- var realA : Float = matA;
- var realB : Float = matB;
- var realC : Float = matC;
- var realD : Float = matD;
- for( g in groups )
- if( g.enable ) {
- pshader.gradient = g.colorGradient;
- pshader.hasGradient = g.colorGradient != null && g.colorGradient.height == 1;
- pshader.has2DGradient = g.colorGradient != null && g.colorGradient.height > 1;
- blendMode = g.batch.blendMode;
- if ( g.isRelative ) {
- g.batch.drawWith(ctx, this);
- } else {
- matA = 1;
- matB = 0;
- matC = 0;
- matD = 1;
- absX = 0;
- absY = 0;
- g.batch.drawWith(ctx, this);
- matA = realA;
- matB = realB;
- matC = realC;
- matD = realD;
- absX = realX;
- absY = realY;
- }
- }
- blendMode = old;
- }
- /**
- Returns an Iterator of particle groups within Particles.
- **/
- public inline function getGroups() {
- return groups.iterator();
- }
- }
|