Przeglądaj źródła

added particles 2d

ncannasse 8 lat temu
rodzic
commit
c2ae686db9
3 zmienionych plików z 390 dodań i 1 usunięć
  1. 381 0
      h2d/Particles.hx
  2. 8 0
      h2d/SpriteBatch.hx
  3. 1 1
      h3d/parts/GpuParticles.hx

+ 381 - 0
h2d/Particles.hx

@@ -0,0 +1,381 @@
+package h2d;
+
+enum PartSortMode {
+	/**
+		Particles are not sorted.
+	**/
+	None;
+	/**
+		Particles are sorted back-to-front every frame based on their current position.
+	**/
+	Dynamic;
+}
+
+enum PartEmitMode {
+	/**
+		A single Point, emit in all directions
+	**/
+	Point;
+	/**
+		A cone, parametrized with emitAngle and emitDistance
+	**/
+	Cone;
+}
+
+private class ParticleShader extends hxsl.Shader {
+	static var SRC = {
+
+		@input var input : { color : Vec4 };
+
+		@const var hasGradient : Bool;
+		@param var gradient : Sampler2D;
+
+		var pixelColor : Vec4;
+		var textureColor : Vec4;
+
+		function fragment() {
+			pixelColor = textureColor;
+			pixelColor.a *= input.color.a;
+			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 vs : 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;
+		vy += group.gravity * et;
+
+		x += vx * et;
+		y += vy * et;
+		life += et;
+
+		rotation += vr * et;
+		scale = scaleX * Math.pow(1 + vs, 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;
+		g = 0;
+
+		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;
+	}
+
+}
+
+@:access(h2d.SpriteBatch)
+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 pshader : ParticleShader;
+	var batch : SpriteBatch;
+	var needRebuild = true;
+	var tiles : Array<h2d.Tile>;
+
+	public var name : String;
+	public var enable(default, set) : Bool = true;
+	public var sortMode(default, set) : PartSortMode = None;
+	public var blendMode(default, set) : BlendMode = Alpha;
+
+	public var nparts(default, set) : Int 		= 100;
+	public var emitLoop(default, set) : Bool 	= true;
+	public var emitMode(default, set):PartEmitMode = Point;
+	public var emitStartDist(default, set) : Float = 0.;
+	public var emitDist(default, set) : Float	= 50.;
+	public var emitAngle(default,set) : Float 	= 1.5;
+	public var emitSync(default, set) : Float	= 0;
+	public var emitDelay(default, set) : Float	= 0;
+
+	public var size(default, set) : Float		= 1;
+	public var sizeIncr(default, set) : Float	= 0;
+	public var sizeRand(default, set) : Float	= 0;
+
+	public var life(default, set) : Float		= 1;
+	public var lifeRand(default, set) : Float	= 0;
+
+	public var speed(default, set) : Float		= 50.;
+	public var speedRand(default, set) : Float	= 0;
+	public var speedIncr(default, set) : Float	= 0;
+	public var gravity(default, set) : Float	= 0;
+
+	public var rotInit(default, set) : Float	= 0;
+	public var rotSpeed(default, set) : Float	= 0;
+	public var rotSpeedRand(default, set):Float = 0;
+
+	public var fadeIn : Float					= 0.2;
+	public var fadeOut : Float					= 0.8;
+	public var fadePower : Float				= 1;
+
+	public var frameCount(default,set) : Int		= 0;
+	public var frameDivisionX(default,set) : Int	= 1;
+	public var frameDivisionY(default,set) : Int	= 1;
+	public var animationRepeat(default,set) : Float	= 1;
+	public var texture(default,set) : h3d.mat.Texture;
+	public var colorGradient(default,set) : h3d.mat.Texture;
+
+	inline function set_enable(v) { enable = v; batch.visible = 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) { needRebuild = true; return size = v; }
+	inline function set_sizeRand(v) { needRebuild = true; return sizeRand = v; }
+	inline function set_sizeIncr(v) { needRebuild = true; return sizeIncr = v; }
+	inline function set_speed(v) { needRebuild = true; return speed = v; }
+	inline function set_speedIncr(v) { needRebuild = true; return speedIncr = v; }
+	inline function set_gravity(v) { needRebuild = true; return gravity = v; }
+	inline function set_speedRand(v) { needRebuild = true; return speedRand = v; }
+	inline function set_life(v) { needRebuild = true; return life = v; }
+	inline function set_lifeRand(v) { needRebuild = true; return lifeRand = v; }
+	inline function set_nparts(n) { needRebuild = true; return nparts = n; }
+	inline function set_emitLoop(v) { needRebuild = true; return emitLoop = v; }
+	inline function set_emitMode(v) { needRebuild = true; return emitMode = v; }
+	inline function set_emitStartDist(v) { needRebuild = true; return emitStartDist = v; }
+	inline function set_emitDist(v) { needRebuild = true; return emitDist = v; }
+	inline function set_emitAngle(v) { needRebuild = true; return emitAngle = v; }
+	inline function set_emitSync(v) { needRebuild = true; return emitSync = v; }
+	inline function set_emitDelay(v) { needRebuild = true; return emitDelay = v; }
+	inline function set_rotInit(v) { needRebuild = true; return rotInit = v; }
+	inline function set_rotSpeed(v) { needRebuild = true; return rotSpeed = v; }
+	inline function set_rotSpeedRand(v) { needRebuild = true; return rotSpeedRand = v; }
+	inline function set_texture(t) { texture = t; makeTiles(); return t; }
+	inline function set_colorGradient(t) { colorGradient = t; pshader.gradient = t; pshader.hasGradient = t != null; 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;
+
+	public function new(p) {
+		this.parts = p;
+		batch = new SpriteBatch(null, p);
+		pshader = new ParticleShader();
+		batch.addShader(pshader);
+		batch.hasRotationScale = true;
+		batch.hasUpdate = true;
+		this.texture = null;
+	}
+
+	function makeTiles() {
+		var t : h2d.Tile;
+		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;
+	}
+
+	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();
+		var g = this;
+		var size = g.size * (1 + srand() * g.sizeRand);
+		var rot = srand() * Math.PI * g.rotInit;
+		var vsize = g.sizeIncr;
+		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);
+
+		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:
+			var theta = rand() * Math.PI * 2;
+			var phi = g.emitAngle * srand();
+			if( g.emitAngle > 0 ) phi += Math.PI;
+			var r = g.emitStartDist + g.emitDist * rand();
+			p.vx = Math.sin(phi) * Math.cos(theta);
+			p.vy = Math.cos(phi);
+			p.x = p.vx * r;
+			p.y = p.vy * r;
+		}
+
+		p.scale = size;
+		p.rotation = rot;
+		p.vs = vsize;
+		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;
+	}
+
+	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;
+	}
+
+	public function load( version : Int, o : Dynamic ) {
+		for( f in getFields(this) )
+			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);
+	}
+
+}
+
+@:access(h2d.ParticleGroup)
+class Particles extends Sprite {
+
+	static inline var VERSION = 1;
+
+	var groups : Array<ParticleGroup>;
+	var resourcePath : String;
+
+	public function new( ?parent ) {
+		super(parent);
+		groups = [];
+	}
+
+	function loadTexture( path : String ) {
+		return hxd.res.Loader.currentInstance.load(path).toTexture();
+	}
+
+	public dynamic function onEnd() {
+		for( g in groups )
+			g.needRebuild = true;
+	}
+
+	public function save() : Dynamic {
+		return { type : "particles2D", version : VERSION, groups : [for( g in groups ) g.save()] };
+	}
+
+	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);
+	}
+
+	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;
+	}
+
+	public function removeGroup( g : ParticleGroup ) {
+		var idx = groups.indexOf(g);
+		if( idx < 0 ) return;
+		groups.splice(idx,1);
+	}
+
+	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();
+	}
+
+	public inline function getGroups() {
+		return groups.iterator();
+	}
+
+}

+ 8 - 0
h2d/SpriteBatch.hx

@@ -120,6 +120,11 @@ class SpriteBatch extends Drawable {
 		return e;
 	}
 
+	public function clear() {
+		first = last = null;
+		flush();
+	}
+
 	public function alloc(t) {
 		return add(new BatchElement(t));
 	}
@@ -205,6 +210,9 @@ class SpriteBatch extends Drawable {
 			}
 
 			var t = e.t;
+
+			tmp.grow(pos + 8 * 4);
+
 			if( hasRotationScale ) {
 				var ca = Math.cos(e.rotation), sa = Math.sin(e.rotation);
 				var hx = t.width, hy = t.height;

+ 1 - 1
h3d/parts/GpuParticles.hx

@@ -145,7 +145,7 @@ class GpuPartGroup {
 	public var rotSpeedRand(default, set):Float = 0;
 
 	public var fadeIn : Float					= 0.2;
-	public var fadeOut : Float					= 0.2;
+	public var fadeOut : Float					= 0.8;
 	public var fadePower : Float				= 1;
 
 	public var frameCount : Int					= 0;