Selaa lähdekoodia

Merged master

Nicolas Cannasse 11 vuotta sitten
vanhempi
commit
fdaa48a038

+ 0 - 1
h2d/BlendMode.hx

@@ -7,5 +7,4 @@ enum BlendMode {
 	SoftAdd;
 	Multiply;
 	Erase;
-	Hide;
 }

+ 7 - 3
h2d/Drawable.hx

@@ -140,13 +140,19 @@ private class DrawableShader extends h3d.impl.Shader {
 		varying lowp vec4 tcolor;
 		#end
 
+        #if hasSize
 		uniform vec3 size;
+		#end
 		uniform vec3 matA;
 		uniform vec3 matB;
 		uniform lowp float zValue;
 		
+        #if hasUVPos
 		uniform vec2 uvPos;
+		#end
+        #if hasUVScale
 		uniform vec2 uvScale;
+		#end
 		
 		varying lowp vec2 tuv;
 
@@ -407,8 +413,6 @@ class Drawable extends Sprite {
 			mat.blend(DstColor, OneMinusSrcAlpha);
 		case Erase:
 			mat.blend(Zero, OneMinusSrcAlpha);
-		case Hide:
-			mat.blend(Zero, One);
 		}
 
 		if( options & HAS_SIZE != 0 ) {
@@ -458,4 +462,4 @@ class Drawable extends Sprite {
 		engine.selectMaterial(mat);
 	}
 	
-}
+}

+ 1 - 1
h2d/Interactive.hx

@@ -66,7 +66,7 @@ class Interactive extends Drawable {
 				return;
 			}
 		}
-		e.propagate = propagateEvents;
+		if( propagateEvents ) e.propagate = true;
 		if( !blockEvents ) e.cancel = true;
 		switch( e.kind ) {
 		case EMove:

+ 7 - 1
h2d/Scene.hx

@@ -101,8 +101,14 @@ class Scene extends Layers implements h3d.IDrawable {
 		case EKeyUp, EKeyDown, EWheel:
 			if( currentFocus != null )
 				currentFocus.handleEvent(event);
-			else
+			else {
+				if( currentOver != null ) {
+					event.propagate = true;
+					currentOver.handleEvent(event);
+					if( !event.propagate ) return;
+				}
 				dispatchListeners(event);
+			}
 			return;
 		default:
 		}

+ 2 - 0
h2d/Tile.hx

@@ -69,6 +69,8 @@ class Tile {
 		if( tex != null ) {
 			u = (x + EPSILON_PIXEL) / tex.width;
 			v = (y + EPSILON_PIXEL) / tex.height;
+			u2 = (width + x - EPSILON_PIXEL) / tex.width;
+			v2 = (height + y - EPSILON_PIXEL) / tex.height;
 		}
 	}
 	

+ 1 - 1
h2d/css/Parser.hx

@@ -553,7 +553,7 @@ class Parser {
 			url = url.substr(22);
 			if( StringTools.endsWith(url, "=") ) url = url.substr(0, -1);
 			var bytes = haxe.crypto.Base64.decode(url);
-			return hxd.res.Any.fromBytes("icon",bytes).getTexture().getPixels();
+			return hxd.res.Any.fromBytes("icon",bytes).toImage().getPixels();
 		default:
 			return null;
 		}

+ 14 - 0
h3d/Camera.hx

@@ -32,6 +32,10 @@ class Camera {
 	public var pos : Vector;
 	public var up : Vector;
 	public var target : Vector;
+	
+	public var viewX : Float = 0.;
+	public var viewY : Float = 0.;
+	
 	var minv : Matrix;
 	var needInv : Bool;
 
@@ -189,6 +193,16 @@ class Camera {
 			m._43 = -(zNear * zFar) / (zFar - zNear);
 			
 		}
+
+		m._11 += viewX * m._14;
+		m._21 += viewX * m._24;
+		m._31 += viewX * m._34;
+		m._41 += viewX * m._44;
+
+		m._12 += viewY * m._14;
+		m._22 += viewY * m._24;
+		m._32 += viewY * m._34;
+		m._42 += viewY * m._44;
 		
 		// our z is negative in that case
 		if( rightHanded ) {

+ 2 - 2
h3d/Engine.hx

@@ -77,8 +77,8 @@ class Engine {
 		return driver.getDriverName(details);
 	}
 	
-	public function selectShader(vertex, fragment) {
-		driver.selectShader(vertex, fragment);
+	public function setCapture( bmp : hxd.BitmapData, callb : Void -> Void ) {
+		driver.setCapture(bmp,callb);
 	}
 
 	function selectBuffer( buf : h3d.impl.MemoryManager.BigBuffer ) {

+ 2 - 1
h3d/fbx/Library.hx

@@ -767,6 +767,8 @@ class Library {
 			var jModel = ids.get(j.index);
 			var subDef = getParent(jModel, "Deformer", true);
 			var defMat = defaultModelMatrixes.get(jModel.getName());
+			j.defMat = defMat.toMatrix(leftHand);
+			
 			if( subDef == null ) {
 				// if we have skinned subs, we need to keep in joint hierarchy
 				if( j.subs.length > 0 || keepJoint(j) )
@@ -795,7 +797,6 @@ class Library {
 				hskins.set(def.getId(), skin);
 			}
 			j.transPos = h3d.Matrix.L(subDef.get("Transform").getFloats());
-			j.defMat = defMat.toMatrix(leftHand);
 			if( leftHand ) DefaultMatrixes.rightHandToLeft(j.transPos);
 			
 			var weights = subDef.getAll("Weights");

+ 3 - 0
h3d/impl/Driver.hx

@@ -36,6 +36,9 @@ class Driver {
 	public function clear( r : Float, g : Float, b : Float, a : Float ) {
 	}
 	
+	public function setCapture( bmp : hxd.BitmapData, callb : Void -> Void ) {
+	}
+	
 	public function reset() {
 	}
 	

+ 22 - 7
h3d/impl/Stage3dDriver.hx

@@ -49,6 +49,8 @@ class Stage3dDriver extends Driver {
 	var antiAlias : Int;
 	var width : Int;
 	var height : Int;
+	var enableDraw : Bool;
+	var capture : { bmp : hxd.BitmapData, callb : Void -> Void };
 
 	@:allow(h3d.impl.VertexWrapper)
 	var empty : flash.utils.ByteArray;
@@ -65,6 +67,7 @@ class Stage3dDriver extends Driver {
 	}
 	
 	override function reset() {
+		enableDraw = true;
 		curMatBits = -1;
 		curShader = null;
 		curBuffer = null;
@@ -112,6 +115,10 @@ class Stage3dDriver extends Driver {
 		ctx.clear(r, g, b, a);
 	}
 	
+	override function setCapture( bmp : hxd.BitmapData, onCapture : Void -> Void ) {
+		capture = { bmp : bmp, callb : onCapture };
+	}
+	
 	override function dispose() {
 		s3d.removeEventListener(flash.events.Event.CONTEXT3D_CREATE, onCreate);
 		if( ctx != null ) ctx.dispose();
@@ -123,6 +130,14 @@ class Stage3dDriver extends Driver {
 	}
 	
 	override function present() {
+		if( capture != null ) {
+			ctx.drawToBitmapData(capture.bmp.toNative());
+			ctx.present();
+			var callb = capture.callb;
+			capture = null;
+			callb();
+			return;
+		}
 		ctx.present();
 	}
 	
@@ -361,13 +376,14 @@ class Stage3dDriver extends Driver {
 	}
 	
 	override function draw( ibuf : IndexBuffer, startIndex : Int, ntriangles : Int ) {
-		ctx.drawTriangles(ibuf, startIndex, ntriangles);
+		if( enableDraw ) ctx.drawTriangles(ibuf, startIndex, ntriangles);
 	}
 
 	override function setRenderZone( x : Int, y : Int, width : Int, height : Int ) {
-		if( x == 0 && y == 0 && width < 0 && height < 0 )
+		if( x == 0 && y == 0 && width < 0 && height < 0 ) {
+			enableDraw = true;
 			ctx.setScissorRectangle(null);
-		else {
+		} else {
 			if( x < 0 ) {
 				width += x;
 				x = 0;
@@ -380,10 +396,9 @@ class Stage3dDriver extends Driver {
 			var th = inTarget == null ? this.height : 9999;
 			if( x + width > tw ) width = tw - x;
 			if( y + height > th ) height = th - y;
-			// for flash, width=0 means no scissor...
-			if( width <= 0 ) { x = tw; width = 1; };
-			if( height <= 0 ) { y = th; height = 1; };
-			ctx.setScissorRectangle(new flash.geom.Rectangle(x, y, width, height));
+			enableDraw = width > 0 && height > 0;
+			if( enableDraw )
+				ctx.setScissorRectangle(new flash.geom.Rectangle(x, y, width, height));
 		}
 	}
 

+ 5 - 0
h3d/mat/Texture.hx

@@ -159,5 +159,10 @@ class Texture {
 		COLOR_CACHE.set(color, t);
 		return t;
 	}
+	
+	public static function alloc( width : Int, height : Int, isTarget = false, ?allocPos : h3d.impl.AllocPos ) {
+		var engine = h3d.Engine.getCurrent();
+		return isTarget ? engine.mem.allocTargetTexture(width, height, allocPos) : engine.mem.allocTexture(width, height, null, allocPos);
+	}
 
 }

+ 4 - 1
h3d/parts/Data.hx

@@ -84,6 +84,8 @@ class State {
 	public var maxParts : Int;
 	public var shape : Shape;
 	public var emitFromShell : Bool;
+	public var emitLocal : Bool;
+	public var emitTrail : Bool;
 	public var randomDir : Bool;
 
 	// system globals
@@ -132,6 +134,7 @@ class State {
 		maxParts = 1000;
 		shape = SCone(VConst(1),VConst(Math.PI/4));
 		emitFromShell = false;
+		emitLocal = false;
 		randomDir = false;
 		// system globals
 		globalLife = 1;
@@ -157,7 +160,7 @@ class State {
 		delay = 0.;
 	}
 	
-	public /*inline*/ function eval( v : Value, time : Float, rand : Void -> Float ) : Float {
+	public inline function eval( v : Value, time : Float, rand : Void -> Float ) : Float {
 		return switch( v ) {
 		case VConst(c): c;
 		case VRandom(s, l, c): s + (switch( c ) { case No: l; case Start: l * time; case End: l * (1 - time); }) * rand();

+ 55 - 13
h3d/parts/Editor.hx

@@ -49,8 +49,9 @@ class Editor extends h2d.Sprite {
 	var cedit : h2d.Interactive;
 	var undo : Array<History>;
 	var redo : Array<History>;
-	var currentFilePath : String;
+	public var currentFilePath : String;
 	public var autoLoop : Bool = true;
+	public var moveEmitter(default,set) : Bool = false;
 	
 	static var CURVES : Array<{ name : String, f : Curve -> Data.Value }> = [
 		{ name : "Const", f : function(c) return VConst(c.min) },
@@ -92,13 +93,7 @@ class Editor extends h2d.Sprite {
 	public dynamic function onTextureSelect() {
 		hxd.File.browse(function(sel) {
 			sel.load(function(bytes) {
-				var bmp = hxd.res.Any.fromBytes(sel.fileName, bytes).toBitmap();
-				var t = h2d.Tile.fromBitmap(bmp);
-				bmp.dispose();
-				state.textureName = sel.fileName;
-				curState = null; // force reload (if texture was changed)
-				setTexture(t);
-				buildUI();
+				changeTexture(sel.fileName, hxd.res.Any.fromBytes(sel.fileName, bytes).toTile());
 			});
 		},{
 			defaultPath : currentFilePath,
@@ -108,6 +103,19 @@ class Editor extends h2d.Sprite {
 		});
 	}
 	
+	public function changeTexture( name : String, t : h2d.Tile ) {
+		state.textureName = name;
+		curState = null; // force reload (if texture was changed)
+		setTexture(t);
+		buildUI();
+	}
+	
+	function set_moveEmitter(v) {
+		this.moveEmitter = v;
+		buildUI();
+		return v;
+	}
+	
 	public dynamic function loadTexture( textureName : String ) : h2d.Tile {
 		var bytes = null;
 		try {
@@ -145,6 +153,12 @@ class Editor extends h2d.Sprite {
 	}
 	
 	public dynamic function onSave( saveData ) {
+		if( currentFilePath != null )
+			try {
+				hxd.File.saveBytes(currentFilePath, saveData);
+				return;
+			} catch( e : Dynamic ) {
+			}
 		if( currentFilePath == null ) currentFilePath = "default.p";
 		hxd.File.saveAs(saveData, {
 			defaultPath : currentFilePath,
@@ -181,6 +195,15 @@ class Editor extends h2d.Sprite {
 		getScene().addEventListener(onEvent);
 	}
 	
+	var time : Float = 0.;
+	public dynamic function onMoveEmitter(dt:Float) {
+		time += dt * 0.03 * 60;
+		var r = 1;
+		emit.x = Math.cos(time) * r;
+		emit.y = Math.sin(time * 0.5) * r;
+		emit.z = (Math.cos(time * 1.3) * Math.sin(time * 1.5) + 1) * r;
+	}
+	
 	function onEvent( e : hxd.Event ) {
 		function loadHistory( h : History ) {
 			curState = h.state;
@@ -430,12 +453,24 @@ class Editor extends h2d.Sprite {
 							</div>
 						</div>
 						<div class="line">
-							<checkbox checked="${state.emitFromShell}" onchange="api.s.emitFromShell = this.checked"/> <span>Emit from Shell</span>
+							<div class="box">
+								<checkbox checked="${state.emitTrail}" onchange="api.s.emitTrail = this.checked"/> <span>Trail</span>
+							</div>
+							<div class="box">
+								<checkbox checked="${state.randomDir}" onchange="api.s.randomDir = this.checked"/> <span>Rand. Dir</span>
+							</div>
+						</div>
+						<div class="line">
+							<div class="box">
+								<checkbox checked="${state.emitLocal}" onchange="api.s.emitLocal = this.checked"/> <span>Local</span>
+							</div>
+							<div class="box">
+								<checkbox checked="${state.emitFromShell}" onchange="api.s.emitFromShell = this.checked"/> <span>From Shell</span>
+							</div>
 						</div>
 						<div class="line">
-							<checkbox checked="${state.randomDir}" onchange="api.s.randomDir = this.checked"/> <span>Random Dir</span>
+							<checkbox checked="${this.moveEmitter}" onchange="api.setMove(this.checked)"/> <span>Move Emitter</span>
 						</div>
-						<!-- TODO : Bursts edition -->
 					</div>
 					
 					<h1>Particle</h1>
@@ -569,6 +604,7 @@ class Editor extends h2d.Sprite {
 			selectTexture : function() onTextureSelect(),
 			load : function() onLoad(),
 			save : function() onSave(haxe.io.Bytes.ofString(curState)),
+			setMove : function(b) moveEmitter = b,
 		});
 		addChildAt(ui,0);
 		stats = cast ui.getElementById("stats");
@@ -884,7 +920,7 @@ class Editor extends h2d.Sprite {
 		rebuildCurve();
 	}
 
-	public function setTexture( t : h2d.Tile ) {
+	function setTexture( t : h2d.Tile ) {
 		state.frames = [t];
 		curTile = t;
 		state.initFrames();
@@ -915,8 +951,14 @@ class Editor extends h2d.Sprite {
 			redo = [];
 		}
 		emit.speed = props.pause ? 0 : (props.slow ? 0.1 : 1);
+		if( moveEmitter ) onMoveEmitter(ctx.elapsedTime);
 		var pcount = emit.count;
-		if( stats != null ) stats.text = hxd.Math.fmt(emit.time * state.globalLife) + " s\n" + pcount + " p\n" + hxd.Math.fmt(ctx.engine.fps) + " fps" + ("\n"+getScene().getSpritesCount());
+		if( stats != null )
+			stats.text = [
+				hxd.Math.fmt(emit.time * state.globalLife) + " s",
+				pcount + " parts",
+				hxd.Math.fmt(ctx.engine.fps) + " fps",
+			].join("\n");
 		if( autoLoop && !emit.isActive() ) {
 			if( lastPartSeen == null )
 				lastPartSeen = emit.time;

+ 196 - 80
h3d/parts/Emitter.hx

@@ -156,6 +156,19 @@ class Emitter extends h3d.scene.Object {
 	
 	function initPart(p:Particle) {
 		initPosDir(p);
+		if( !state.emitLocal ) {
+			var pos = new h3d.Vector(p.x, p.y, p.z);
+			pos.transform3x4(absPos);
+			p.x = pos.x;
+			p.y = pos.y;
+			p.z = pos.z;
+			var v = new h3d.Vector(p.dx, p.dy, p.dz);
+			v.transform3x3(absPos);
+			p.dx = v.x;
+			p.dy = v.y;
+			p.dz = v.z;
+		}
+		p.fx = p.fy = p.fz = 0;
 		p.time = 0;
 		p.lifeTimeFactor = 1 / eval(state.life, time, rand);
 	}
@@ -212,17 +225,17 @@ class Emitter extends h3d.scene.Object {
 	
 		// apply forces
 		if( state.force != null ) {
-			p.dx += eval(state.force.vx, time, rand) * dt;
-			p.dy += eval(state.force.vy, time, rand) * dt;
-			p.dz += eval(state.force.vz, time, rand) * dt;
+			p.fx += eval(state.force.vx, time, rand) * dt;
+			p.fy += eval(state.force.vy, time, rand) * dt;
+			p.fz += eval(state.force.vz, time, rand) * dt;
 		}
-		p.dz -= eval(state.gravity, time, rand) * dt;
+		p.fz -= eval(state.gravity, time, rand) * dt;
 		// calc speed and update position
 		var speed = eval(state.speed, p.time, rand);
 		var ds = speed * dt;
-		p.x += p.dx * ds;
-		p.y += p.dy * ds;
-		p.z += p.dz * ds;
+		p.x += p.dx * ds + p.fx * dt;
+		p.y += p.dy * ds + p.fy * dt;
+		p.z += p.dz * ds + p.fz * dt;
 		p.size = eval(state.size, p.time, rand);
 		p.ratio = eval(state.ratio, p.time, rand);
 		p.rotation = eval(state.rotation, p.time, rand);
@@ -333,82 +346,185 @@ class Emitter extends h3d.scene.Object {
 			t.v = 0; t.v2 = 1;
 			frames = [t];
 		}
-		while( p != null ) {
-			var f = frames[p.frame];
-			if( f == null ) f = frames[0];
-			var ratio = p.size * p.ratio * (f.height / f.width);
-			tmp[pos++] = p.x;
-			tmp[pos++] = p.y;
-			tmp[pos++] = p.z;
-			// delta
-			tmp[pos++] = 0;
-			tmp[pos++] = 0;
-			tmp[pos++] = p.rotation;
-			tmp[pos++] = p.size;
-			tmp[pos++] = ratio;
-			// UV
-			tmp[pos++] = f.u;
-			tmp[pos++] = f.v;
-			// RBGA
-			if( hasColor ) {
-				tmp[pos++] = p.cr;
-				tmp[pos++] = p.cg;
-				tmp[pos++] = p.cb;
-				tmp[pos++] = p.ca;
-			}
-			
-			tmp[pos++] = p.x;
-			tmp[pos++] = p.y;
-			tmp[pos++] = p.z;
-			tmp[pos++] = 0;
-			tmp[pos++] = 1;
-			tmp[pos++] = p.rotation;
-			tmp[pos++] = p.size;
-			tmp[pos++] = ratio;
-			tmp[pos++] = f.u;
-			tmp[pos++] = f.v2;
-			if( hasColor ) {
-				tmp[pos++] = p.cr;
-				tmp[pos++] = p.cg;
-				tmp[pos++] = p.cb;
-				tmp[pos++] = p.ca;
-			}
+		if( state.emitTrail ) {
+			var prev = p;
+			var prevX1 = p.x, prevY1 = p.y, prevZ1 = p.z;
+			var prevX2 = p.x, prevY2 = p.y, prevZ2 = p.z;
+			if( p != null ) p = p.next;
+			while( p != null ) {
+				var f = frames[p.frame];
+				if( f == null ) f = frames[0];
+				var ratio = p.size * p.ratio * (f.height / f.width);
+				
+				tmp[pos++] = prevX1;
+				tmp[pos++] = prevY1;
+				tmp[pos++] = prevZ1;
+				// delta
+				tmp[pos++] = 0;
+				tmp[pos++] = 0;
+				tmp[pos++] = p.rotation;
+				tmp[pos++] = p.size;
+				tmp[pos++] = ratio;
+				// UV
+				tmp[pos++] = f.u;
+				tmp[pos++] = f.v;
+				// RBGA
+				if( hasColor ) {
+					tmp[pos++] = p.cr;
+					tmp[pos++] = p.cg;
+					tmp[pos++] = p.cb;
+					tmp[pos++] = p.ca;
+				}
+				
+				tmp[pos++] = prevX2;
+				tmp[pos++] = prevY2;
+				tmp[pos++] = prevZ2;
+				tmp[pos++] = 0;
+				tmp[pos++] = 0;
+				tmp[pos++] = p.rotation;
+				tmp[pos++] = p.size;
+				tmp[pos++] = ratio;
+				tmp[pos++] = f.u;
+				tmp[pos++] = f.v2;
+				if( hasColor ) {
+					tmp[pos++] = p.cr;
+					tmp[pos++] = p.cg;
+					tmp[pos++] = p.cb;
+					tmp[pos++] = p.ca;
+				}
+
+				var dx = p.x - prev.x;
+				var dy = p.y - prev.y;
+				var dz = p.z - prev.z;
+				var d = hxd.Math.invSqrt(dx * dx + dy * dy + dz * dz);
+				dx *= d;
+				dy *= d;
+				dz *= d;
+				var dir = new h3d.Vector(Math.sin(p.rotation), 0, Math.cos(p.rotation)).cross(new h3d.Vector(dx, dy, dz));
+				
+				prevX1 = p.x + dir.x * p.size;
+				prevY1 = p.y + dir.y * p.size;
+				prevZ1 = p.z + dir.z * p.size;
+				
+				prevX2 = p.x - dir.x * p.size;
+				prevY2 = p.y - dir.y * p.size;
+				prevZ2 = p.z - dir.z * p.size;
+
+				tmp[pos++] = prevX1;
+				tmp[pos++] = prevY1;
+				tmp[pos++] = prevZ1;
+				tmp[pos++] = 0;
+				tmp[pos++] = 0;
+				tmp[pos++] = p.rotation;
+				tmp[pos++] = p.size;
+				tmp[pos++] = ratio;
+				tmp[pos++] = f.u2;
+				tmp[pos++] = f.v;
+				if( hasColor ) {
+					tmp[pos++] = p.cr;
+					tmp[pos++] = p.cg;
+					tmp[pos++] = p.cb;
+					tmp[pos++] = p.ca;
+				}
 
-			tmp[pos++] = p.x;
-			tmp[pos++] = p.y;
-			tmp[pos++] = p.z;
-			tmp[pos++] = 1;
-			tmp[pos++] = 0;
-			tmp[pos++] = p.rotation;
-			tmp[pos++] = p.size;
-			tmp[pos++] = ratio;
-			tmp[pos++] = f.u2;
-			tmp[pos++] = f.v;
-			if( hasColor ) {
-				tmp[pos++] = p.cr;
-				tmp[pos++] = p.cg;
-				tmp[pos++] = p.cb;
-				tmp[pos++] = p.ca;
+				tmp[pos++] = prevX2;
+				tmp[pos++] = prevY2;
+				tmp[pos++] = prevZ2;
+				tmp[pos++] = 0;
+				tmp[pos++] = 0;
+				tmp[pos++] = p.rotation;
+				tmp[pos++] = p.size;
+				tmp[pos++] = ratio;
+				tmp[pos++] = f.u2;
+				tmp[pos++] = f.v2;
+				if( hasColor ) {
+					tmp[pos++] = p.cr;
+					tmp[pos++] = p.cg;
+					tmp[pos++] = p.cb;
+					tmp[pos++] = p.ca;
+				}
+				
+				prev = p;
+				p = p.next;
 			}
+		} else {
+			while( p != null ) {
+				var f = frames[p.frame];
+				if( f == null ) f = frames[0];
+				var ratio = p.size * p.ratio * (f.height / f.width);
+				tmp[pos++] = p.x;
+				tmp[pos++] = p.y;
+				tmp[pos++] = p.z;
+				// delta
+				tmp[pos++] = -0.5;
+				tmp[pos++] = -0.5;
+				tmp[pos++] = p.rotation;
+				tmp[pos++] = p.size;
+				tmp[pos++] = ratio;
+				// UV
+				tmp[pos++] = f.u;
+				tmp[pos++] = f.v;
+				// RBGA
+				if( hasColor ) {
+					tmp[pos++] = p.cr;
+					tmp[pos++] = p.cg;
+					tmp[pos++] = p.cb;
+					tmp[pos++] = p.ca;
+				}
+				
+				tmp[pos++] = p.x;
+				tmp[pos++] = p.y;
+				tmp[pos++] = p.z;
+				tmp[pos++] = -0.5;
+				tmp[pos++] = 0.5;
+				tmp[pos++] = p.rotation;
+				tmp[pos++] = p.size;
+				tmp[pos++] = ratio;
+				tmp[pos++] = f.u;
+				tmp[pos++] = f.v2;
+				if( hasColor ) {
+					tmp[pos++] = p.cr;
+					tmp[pos++] = p.cg;
+					tmp[pos++] = p.cb;
+					tmp[pos++] = p.ca;
+				}
 
-			tmp[pos++] = p.x;
-			tmp[pos++] = p.y;
-			tmp[pos++] = p.z;
-			tmp[pos++] = 1;
-			tmp[pos++] = 1;
-			tmp[pos++] = p.rotation;
-			tmp[pos++] = p.size;
-			tmp[pos++] = ratio;
-			tmp[pos++] = f.u2;
-			tmp[pos++] = f.v2;
-			if( hasColor ) {
-				tmp[pos++] = p.cr;
-				tmp[pos++] = p.cg;
-				tmp[pos++] = p.cb;
-				tmp[pos++] = p.ca;
+				tmp[pos++] = p.x;
+				tmp[pos++] = p.y;
+				tmp[pos++] = p.z;
+				tmp[pos++] = 0.5;
+				tmp[pos++] = -0.5;
+				tmp[pos++] = p.rotation;
+				tmp[pos++] = p.size;
+				tmp[pos++] = ratio;
+				tmp[pos++] = f.u2;
+				tmp[pos++] = f.v;
+				if( hasColor ) {
+					tmp[pos++] = p.cr;
+					tmp[pos++] = p.cg;
+					tmp[pos++] = p.cb;
+					tmp[pos++] = p.ca;
+				}
+
+				tmp[pos++] = p.x;
+				tmp[pos++] = p.y;
+				tmp[pos++] = p.z;
+				tmp[pos++] = 0.5;
+				tmp[pos++] = 0.5;
+				tmp[pos++] = p.rotation;
+				tmp[pos++] = p.size;
+				tmp[pos++] = ratio;
+				tmp[pos++] = f.u2;
+				tmp[pos++] = f.v2;
+				if( hasColor ) {
+					tmp[pos++] = p.cr;
+					tmp[pos++] = p.cg;
+					tmp[pos++] = p.cb;
+					tmp[pos++] = p.ca;
+				}
+				
+				p = p.next;
 			}
-			
-			p = p.next;
 		}
 		var stride = 10;
 		if( hasColor ) stride += 4;
@@ -417,7 +533,7 @@ class Emitter extends h3d.scene.Object {
 		buffer.uploadVector(tmpBuf, 0, nverts);
 		var size = eval(state.globalSize, time, rand);
 		
-		material.pshader.mpos = this.absPos;
+		material.pshader.mpos = state.emitLocal ? this.absPos : h3d.Matrix.I();
 		material.pshader.mproj = ctx.camera.m;
 		material.pshader.partSize = new h3d.Vector(size, size * ctx.engine.width / ctx.engine.height);
 		material.pshader.hasColor = hasColor;

+ 1 - 1
h3d/parts/Material.hx

@@ -24,7 +24,7 @@ private class PartShader extends h3d.impl.Shader {
 			var tpos = input.pos.xyzw;
 			tpos.xyz = input.pos.xyzw * mpos;
 			var tmp = tpos * mproj;
-			var rpos = input.delta - 0.5;
+			var rpos = input.delta;
 			var cr = input.rotation.cos();
 			var sr = input.rotation.sin();
 			var rtmp = rpos.x * cr + rpos.y * sr;

+ 4 - 0
h3d/parts/Particle.hx

@@ -14,6 +14,10 @@ class Particle {
 	public var dy : Float;
 	public var dz : Float;
 
+	public var fx : Float;
+	public var fy : Float;
+	public var fz : Float;
+
 	public var cr : Float;
 	public var cg : Float;
 	public var cb : Float;

+ 5 - 8
h3d/scene/Skin.hx

@@ -78,18 +78,18 @@ class Skin extends Mesh {
 		var tmp = primitive.getBounds().clone();
 		var b0 = skinData.allJoints[0];
 		// not sure if that's the good joint
-		if( b0 != null && b0.defMat != null && b0.parent == null ) {
+		if( b0 != null && b0.parent == null ) {
 			var mtmp = absPos.clone();
 			var r = currentRelPose[b0.index];
 			if( r != null )
 				mtmp.multiply3x4(r, mtmp);
 			else
 				mtmp.multiply3x4(b0.defMat, mtmp);
-			mtmp.multiply3x4(b0.transPos, mtmp);
+			if( b0.transPos != null )
+				mtmp.multiply3x4(b0.transPos, mtmp);
 			tmp.transform3x4(mtmp);
-		} else {
+		} else
 			tmp.transform3x4(absPos);
-		}
 		b.add(tmp);
 		return b;
 	}
@@ -142,10 +142,7 @@ class Skin extends Mesh {
 				var id = j.index;
 				var m = currentAbsPose[id];
 				var r = currentRelPose[id];
-				if( r == null ) {
-					var bid = j.bindIndex;
-					if( bid >= 0 ) r = j.defMat else continue;
-				}
+				if( r == null ) r = j.defMat;
 				if( j.parent == null )
 					m.multiply3x4(r, absPos);
 				else

+ 62 - 2
hxd/BitmapData.hx

@@ -4,6 +4,12 @@ private typedef InnerData = #if flash flash.display.BitmapData #elseif js js.htm
 
 abstract BitmapData(InnerData) {
 
+	#if flash
+	static var tmpRect = new flash.geom.Rectangle();
+	static var tmpPoint = new flash.geom.Point();
+	static var tmpMatrix = new flash.geom.Matrix();
+	#end
+	
 	public var width(get, never) : Int;
 	public var height(get, never) : Int;
 	
@@ -23,9 +29,63 @@ abstract BitmapData(InnerData) {
 		#end
 	}
 	
-	public inline function fill( rect : h2d.col.Bounds, color : Int ) {
+	public function fill( x : Int, y : Int, width : Int, height : Int, color : Int ) {
+		#if flash
+		var r = tmpRect;
+		r.x = x;
+		r.y = y;
+		r.width = width;
+		r.height = height;
+		this.fillRect(r, color);
+		#else
+		throw "TODO";
+		#end
+	}
+	
+	public function draw( x : Int, y : Int, src : BitmapData, srcX : Int, srcY : Int, width : Int, height : Int, ?blendMode : h2d.BlendMode ) {
 		#if flash
-		this.fillRect(new flash.geom.Rectangle(Std.int(rect.xMin), Std.int(rect.yMin), Math.ceil(rect.xMax - rect.xMin), Math.ceil(rect.yMax - rect.yMin)), color);
+		if( blendMode == null ) blendMode = Normal;
+		var r = tmpRect;
+		r.x = srcX;
+		r.y = srcY;
+		r.width = width;
+		r.height = height;
+		switch( blendMode ) {
+		case None:
+			var p = tmpPoint;
+			p.x = x;
+			p.y = y;
+			this.copyPixels(src.toNative(), r, p);
+		case Normal:
+			var p = tmpPoint;
+			p.x = x;
+			p.y = y;
+			trace(r, p);
+			this.copyPixels(src.toNative(), r, p, null, null, false);
+		case Add:
+			var m = tmpMatrix;
+			m.tx = x - srcX;
+			m.ty = y - srcY;
+			r.x = x;
+			r.y = y;
+			this.draw(src.toNative(), m, null, flash.display.BlendMode.ADD, r, false);
+		case Erase:
+			var m = tmpMatrix;
+			m.tx = x - srcX;
+			m.ty = y - srcY;
+			r.x = x;
+			r.y = y;
+			this.draw(src.toNative(), m, null, flash.display.BlendMode.ERASE, r, false);
+		case Multiply:
+			var m = tmpMatrix;
+			m.tx = x - srcX;
+			m.ty = y - srcY;
+			r.x = x;
+			r.y = y;
+			this.draw(src.toNative(), m, null, flash.display.BlendMode.MULTIPLY, r, false);
+		case SoftAdd:
+			throw "BlendMode not supported";
+		}
 		#else
 		throw "TODO";
 		#end

+ 1 - 1
hxd/File.hx

@@ -147,7 +147,7 @@ class File {
 		#end
 	}
 
-	public function saveBytesAt( path : String, data : haxe.io.Bytes, dataPos : Int, dataSize : Int, filePos : Int ) {
+	public static function saveBytesAt( path : String, data : haxe.io.Bytes, dataPos : Int, dataSize : Int, filePos : Int ) {
 		#if air3
 		var f = new flash.filesystem.File(path);
 		var o = new flash.filesystem.FileStream();

+ 14 - 0
hxd/Res.hx

@@ -1,10 +1,24 @@
 package hxd;
 
+#if !macro
 @:build(hxd.res.FileTree.build())
+#end
 class Res {
 	
+	#if !macro
 	public static function load(name:String) {
 		return loader.load(name);
 	}
+	#end
+	
+	public static macro function initEmbed() {
+		return macro hxd.Res.loader = new hxd.res.Loader(hxd.res.EmbedFileSystem.create());
+	}
+
+	public static macro function initLocal() {
+		var dir = haxe.macro.Compiler.getDefine("resourcesPath");
+		if( dir == null ) dir = "res";
+		return macro hxd.Res.loader = new hxd.res.Loader(new hxd.res.LocalFileSystem($v{dir}));
+	}
 	
 }

+ 20 - 0
hxd/Stage.hx

@@ -45,6 +45,24 @@ class Stage {
 		js.Browser.window.addEventListener("keyup", onKeyUp);
 		js.Browser.window.addEventListener("resize", onResize);
 		#end
+		#if flash
+		if( untyped hxd.System.isAir() )
+			setupOnCloseEvent();
+		#end
+	}
+	
+	#if flash
+	function setupOnCloseEvent() {
+		var nw : flash.events.EventDispatcher = Reflect.field(stage, "nativeWindow");
+		nw.addEventListener("closing", function(e:flash.events.Event) {
+			if( !onClose() )
+				e.preventDefault();
+		});
+	}
+	#end
+	
+	public dynamic function onClose() {
+		return true;
 	}
 	
 	public function event( e : hxd.Event ) {
@@ -165,6 +183,7 @@ class Stage {
 		ev.keyCode = e.keyCode;
 		ev.charCode = getCharCode(e);
 		event(ev);
+		#if flash
 		// prevent escaping fullscreen in air
 		if( e.keyCode == flash.ui.Keyboard.ESCAPE ) e.preventDefault();
 		// prevent back exiting app in mobile
@@ -172,6 +191,7 @@ class Stage {
 			e.preventDefault();
 			e.stopImmediatePropagation();
 		}
+		#end
 	}
 	
 	function getCharCode( e : flash.events.KeyboardEvent ) {

+ 5 - 5
hxd/res/Any.hx

@@ -36,19 +36,19 @@ class Any extends Resource {
 	}
 
 	public function toTexture() {
-		return loader.loadTexture(entry.path).toTexture();
+		return loader.loadImage(entry.path).toTexture();
 	}
 	
 	public function toTile() {
-		return loader.loadTexture(entry.path).toTile();
+		return loader.loadImage(entry.path).toTile();
 	}
 	
 	public function toString() {
 		return entry.getBytes().toString();
 	}
 
-	public function getTexture() {
-		return loader.loadTexture(entry.path);
+	public function toImage() {
+		return loader.loadImage(entry.path);
 	}
 	
 	public function toSound() {
@@ -60,7 +60,7 @@ class Any extends Resource {
 	}
 
 	public function toBitmap() {
-		return loader.loadTexture(entry.path).toBitmap();
+		return loader.loadImage(entry.path).toBitmap();
 	}
 
 	public function toBitmapFont() {

+ 0 - 4
hxd/res/EmbedFileSystem.hx

@@ -241,10 +241,6 @@ class EmbedFileSystem #if !macro implements FileSystem #end {
 	
 	#end
 	
-	public static macro function init() {
-		return macro hxd.Res.loader = new hxd.res.Loader(hxd.res.EmbedFileSystem.create());
-	}
-	
 	public static macro function create( ?basePath : String, ?options : EmbedOptions ) {
 		var f = new FileTree(basePath);
 		var data = f.embed(options);

+ 3 - 12
hxd/res/FileTree.hx

@@ -10,7 +10,6 @@ class FileTree {
 	var currentModule : String;
 	var pos : Position;
 	var loaderType : ComplexType;
-	var ignoredDir : Map<String,Bool>;
 	var ignoredExt : Map<String,Bool>;
 	var pairedExt : Map<String,Array<String>>;
 	var ignoredPairedExt : Map<String,Array<String>>;
@@ -23,10 +22,6 @@ class FileTree {
 		this.path = resolvePath(dir);
 		currentModule = Std.string(Context.getLocalClass());
 		pos = Context.currentPos();
-		ignoredDir = new Map();
-		ignoredDir.set(".svn", true);
-		ignoredDir.set(".git", true);
-		ignoredDir.set(".tmp", true);
 		ignoredExt = new Map();
 		ignoredExt.set("gal", true); // graphics gale source
 		ignoredExt.set("lch", true); // labchirp source
@@ -73,9 +68,7 @@ class FileTree {
 		for( f in sys.FileSystem.readDirectory(dir) ) {
 			var path = dir + "/" + f;
 			if( sys.FileSystem.isDirectory(path) ) {
-				if( ignoredDir.exists(f.toLowerCase()) )
-					continue;
-				if( f.charCodeAt(0) == "_".code )
+				if( f.charCodeAt(0) == ".".code || f.charCodeAt(0) == "_".code )
 					continue;
 				var sub = embedDir(f, relPath + "/" + f, path);
 				if( sub != null )
@@ -219,9 +212,7 @@ class FileTree {
 			var field = null;
 			var ext = null;
 			if( sys.FileSystem.isDirectory(path) ) {
-				if( ignoredDir.exists(f.toLowerCase()) )
-					continue;
-				if( f.charCodeAt(0) == "_".code )
+				if( f.charCodeAt(0) == ".".code || f.charCodeAt(0) == "_".code )
 					continue;
 				field = handleDir(f, relPath.length == 0 ? f : relPath+"/"+f, path);
 			} else {
@@ -318,7 +309,7 @@ class FileTree {
 		var epath = { expr : EConst(CString(relPath)), pos : pos };
 		switch( ext.toLowerCase() ) {
 		case "jpg", "png":
-			return { e : macro loader.loadTexture($epath), t : macro : hxd.res.Texture };
+			return { e : macro loader.loadImage($epath), t : macro : hxd.res.Image };
 		case "fbx", "xbx":
 			return { e : macro loader.loadModel($epath), t : macro : hxd.res.Model };
 		case "ttf":

+ 1 - 1
hxd/res/Texture.hx → hxd/res/Image.hx

@@ -1,6 +1,6 @@
 package hxd.res;
 
-class Texture extends Resource {
+class Image extends Resource {
 	
 	var needResize : Bool;
 	var tex : h3d.mat.Texture;

+ 6 - 6
hxd/res/Loader.hx

@@ -4,14 +4,14 @@ class Loader {
 	
 	public var fs(default,null) : FileSystem;
 	var modelCache : Map<String,Model>;
-	var textureCache : Map<String,Texture>;
+	var imageCache : Map<String,Image>;
 	var soundCache : Map<String,Sound>;
 	var fontCache : Map<String,BitmapFont>;
 	
 	public function new(fs) {
 		this.fs = fs;
 		modelCache = new Map();
-		textureCache = new Map();
+		imageCache = new Map();
 		soundCache = new Map();
 		fontCache = new Map();
 	}
@@ -33,11 +33,11 @@ class Loader {
 		return m;
 	}
 	
-	function loadTexture( path : String ) : Texture {
-		var t = textureCache.get(path);
+	function loadImage( path : String ) : Image {
+		var t = imageCache.get(path);
 		if( t == null ) {
-			t = new Texture(fs.get(path));
-			textureCache.set(path, t);
+			t = new Image(fs.get(path));
+			imageCache.set(path, t);
 		}
 		return t;
 	}

+ 1 - 1
samples/basic/Test.hx

@@ -59,7 +59,7 @@ class Test {
 		#if flash
 		haxe.Log.setColor(0xFF0000);
 		#end
-		hxd.res.EmbedFileSystem.init();
+		hxd.Res.initEmbed();
 		new Test();
 	}
 	

+ 3 - 24
tools/parts/Main.hx

@@ -1,33 +1,18 @@
 import h3d.parts.Data;
 
-class Main implements h3d.parts.Collider {
+class Main extends hxd.App implements h3d.parts.Collider {
 	
-	var s3d : h3d.scene.Scene;
-	var s2d : h2d.Scene;
-	var engine : h3d.Engine;
 	var emit : h3d.parts.Emitter;
 	var edit : h3d.parts.Editor;
 	
-	public function new() {
-		hxd.res.Embed.embedFont("Arial.ttf");
-		engine = new h3d.Engine();
-		engine.onReady = init;
-		engine.init();
-	}
-	
 	public function collidePart( p : h3d.parts.Particle, n : h3d.Vector ) {
 		if( p.z > 0 ) return false;
 		n.set(0, 0, 1);
 		return true;
 	}
 	
-	function init() {
-		hxd.Key.initialize();
-		hxd.System.setLoop(update);
-		s3d = new h3d.scene.Scene();
-		s2d = new h2d.Scene();
-		s3d.addPass(s2d);
-		
+	override function init() {
+		hxd.res.Embed.embedFont("Arial.ttf",true);
 		var bmp = new hxd.BitmapData(2, 2);
 		bmp.setPixel(0, 0, 0xFF202020);
 		bmp.setPixel(1, 1, 0xFF202020);
@@ -50,12 +35,6 @@ class Main implements h3d.parts.Collider {
 		edit = new h3d.parts.Editor(emit, s2d);
 	}
 	
-	function update() {
-		s3d.setElapsedTime(1 / engine.fps);
-		engine.render(s3d);
-		s2d.checkEvents();
-	}
-	
 	static function main() {
 		new Main();
 	}