瀏覽代碼

merged master

Nicolas Cannasse 11 年之前
父節點
當前提交
90d04f723f

+ 9 - 3
h2d/Anim.hx

@@ -30,6 +30,12 @@ class Anim extends Drawable {
 		return curFrame;
 	}
 	
+	override function getBoundsRec( relativeTo, out ) {
+		super.getBoundsRec(relativeTo, out);
+		var tile = getFrame();
+		if( tile != null ) addBounds(relativeTo, out, tile.dx, tile.dy, tile.width, tile.height);
+	}
+
 	override function sync( ctx : RenderContext ) {
 		var prev = curFrame;
 		curFrame += speed * ctx.elapsedTime;
@@ -48,14 +54,14 @@ class Anim extends Drawable {
 	
 	public dynamic function onAnimEnd() {
 	}
-	
+
 	public function getFrame() {
 		return frames[Std.int(curFrame)];
 	}
-	
+
 	override function draw( ctx : RenderContext ) {
 		var t = getFrame();
 		emitTile(ctx,t);
 	}
-	
+
 }

+ 9 - 4
h2d/Bitmap.hx

@@ -3,18 +3,23 @@ package h2d;
 class Bitmap extends Drawable {
 
 	public var tile : Tile;
-	
+
 	public function new( ?tile, ?parent ) {
 		super(parent);
 		this.tile = tile;
 	}
-	
+
+	override function getBoundsRec( relativeTo, out ) {
+		super.getBoundsRec(relativeTo, out);
+		if( tile != null ) addBounds(relativeTo, out, tile.dx, tile.dy, tile.width, tile.height);
+	}
+
 	override function draw( ctx : RenderContext ) {
 		emitTile(ctx,tile);
 	}
-			
+
 	public static function create( bmp : hxd.BitmapData, ?allocPos : h3d.impl.AllocPos ) {
 		return new Bitmap(Tile.fromBitmap(bmp,allocPos));
 	}
-	
+
 }

+ 1 - 1
h2d/Console.hx

@@ -93,7 +93,7 @@ class Console extends h2d.Sprite {
 	function showHelp( ?command : String ) {
 		var all;
 		if( command == null ) {
-			all = Lambda.array( { iterator : commands.keys } );
+			all = Lambda.array( { iterator : function() return commands.keys() } );
 			all.sort(Reflect.compare);
 			all.remove("help");
 			all.push("help");

+ 53 - 35
h2d/Graphics.hx

@@ -24,9 +24,9 @@ private class GraphicsContent extends h3d.prim.Primitive {
 
 	var tmp : hxd.FloatBuffer;
 	var index : hxd.IndexBuffer;
-	
+
 	var buffers : Array<{ buf : hxd.FloatBuffer, vbuf : h3d.Buffer, idx : hxd.IndexBuffer, ibuf : h3d.Indexes }>;
-	
+
 	public function new() {
 		buffers = [];
 	}
@@ -45,7 +45,7 @@ private class GraphicsContent extends h3d.prim.Primitive {
 		tmp.push(b);
 		tmp.push(a);
 	}
-	
+
 	public function next() {
 		var nvect = tmp.length >> 3;
 		if( nvect < 1 << 15 )
@@ -56,7 +56,7 @@ private class GraphicsContent extends h3d.prim.Primitive {
 		super.dispose();
 		return true;
 	}
-	
+
 	override function alloc( engine : h3d.Engine ) {
 		if (index.length <= 0) return ;
 		buffer = h3d.Buffer.ofFloats(tmp, 8);
@@ -66,7 +66,7 @@ private class GraphicsContent extends h3d.prim.Primitive {
 			if( b.ibuf == null || b.ibuf.isDisposed() ) b.ibuf = h3d.Indexes.alloc(b.idx);
 		}
 	}
-	
+
 	override function render( engine : h3d.Engine ) {
 		if (index.length <= 0) return ;
 		if( buffer == null || buffer.isDisposed() ) alloc(engine);
@@ -74,7 +74,7 @@ private class GraphicsContent extends h3d.prim.Primitive {
 			engine.renderIndexed(b.vbuf, b.ibuf);
 		super.render(engine);
 	}
-	
+
 	override function dispose() {
 		for( b in buffers ) {
 			if( b.vbuf != null ) b.vbuf.dispose();
@@ -84,15 +84,15 @@ private class GraphicsContent extends h3d.prim.Primitive {
 		}
 		super.dispose();
 	}
-	
-	
+
+
 	public function reset() {
 		dispose();
 		tmp = new hxd.FloatBuffer();
 		index = new hxd.IndexBuffer();
 		buffers = [];
 	}
-	
+
 }
 
 class Graphics extends Drawable {
@@ -112,21 +112,26 @@ class Graphics extends Drawable {
 	var lineB : Float;
 	var lineA : Float;
 	var doFill : Bool;
-	
+
+	var xMin : Float;
+	var yMin : Float;
+	var xMax : Float;
+	var yMax : Float;
+
 	public var tile : h2d.Tile;
-	
+
 	public function new(?parent) {
 		super(parent);
 		content = new GraphicsContent();
 		tile = h2d.Tile.fromColor(0xFFFFFFFF);
 		clear();
 	}
-	
+
 	override function onDelete() {
 		super.onDelete();
 		clear();
 	}
-	
+
 	public function clear() {
 		content.reset();
 		pts = [];
@@ -134,6 +139,15 @@ class Graphics extends Drawable {
 		linePts = [];
 		pindex = 0;
 		lineSize = 0;
+		xMin = Math.POSITIVE_INFINITY;
+		yMin = Math.POSITIVE_INFINITY;
+		yMax = Math.NEGATIVE_INFINITY;
+		xMax = Math.NEGATIVE_INFINITY;
+	}
+
+	override function getBoundsRec( relativeTo, out ) {
+		super.getBoundsRec(relativeTo, out);
+		if( tile != null ) addBounds(relativeTo, out, xMin, yMin, xMax - xMin, yMax - yMin);
 	}
 
 	function isConvex( points : Array<GraphicsPoint> ) {
@@ -146,7 +160,7 @@ class Graphics extends Drawable {
 		}
 		return true;
 	}
-	
+
 	function flushLine() {
 		if( linePts.length == 0 )
 			return;
@@ -162,15 +176,15 @@ class Graphics extends Drawable {
 			var nx2 = p.y - next.y;
 			var ny2 = next.x - p.x;
 			var ns2 = Math.invSqrt(nx2 * nx2 + ny2 * ny2);
-			
+
 			var nx = (nx1 * ns1 + nx2 * ns2) * lineSize * 0.5;
 			var ny = (ny1 * ns1 + ny2 * ns2) * lineSize * 0.5;
-			
+
 			content.add(p.x + nx, p.y + ny, 0, 0, p.r, p.g, p.b, p.a);
 			content.add(p.x - nx, p.y - ny, 0, 0, p.r, p.g, p.b, p.a);
-			
+
 			var pnext = i == last ? start : pindex + 2;
-			
+
 			content.addIndex(pindex);
 			content.addIndex(pindex + 1);
 			content.addIndex(pnext);
@@ -178,9 +192,9 @@ class Graphics extends Drawable {
 			content.addIndex(pindex + 1);
 			content.addIndex(pnext);
 			content.addIndex(pnext + 1);
-			
+
 			pindex += 2;
-			
+
 			prev = p;
 			p = next;
 		}
@@ -188,7 +202,7 @@ class Graphics extends Drawable {
 		if( content.next() )
 			pindex = 0;
 	}
-	
+
 	function flushFill() {
 		if( pts.length > 0 ) {
 			prev.push(pts);
@@ -196,7 +210,7 @@ class Graphics extends Drawable {
 		}
 		if( prev.length == 0 )
 			return;
-			
+
 		if( prev.length == 1 && isConvex(prev[0]) ) {
 			var p0 = prev[0][0].id;
 			for( i in 1...prev[0].length - 1 ) {
@@ -208,31 +222,31 @@ class Graphics extends Drawable {
 			var ctx = new hxd.poly2tri.SweepContext();
 			for( p in prev )
 				ctx.addPolyline(p);
-				
+
 			var p = new hxd.poly2tri.Sweep(ctx);
 			p.triangulate();
-			
+
 			for( t in ctx.triangles )
 				for( p in t.points )
 					content.addIndex(p.id);
 		}
-				
+
 		prev = [];
 		if( content.next() )
 			pindex = 0;
 	}
-	
+
 	function flush() {
 		flushFill();
 		flushLine();
 	}
-	
+
 	public function beginFill( color : Int = 0, alpha = 1.  ) {
 		flush();
 		setColor(color,alpha);
 		doFill = true;
 	}
-	
+
 	public function lineStyle( size : Float = 0, color = 0, alpha = 1. ) {
 		flush();
 		this.lineSize = size;
@@ -241,26 +255,26 @@ class Graphics extends Drawable {
 		lineG = ((color >> 8) & 0xFF) / 255.;
 		lineB = (color & 0xFF) / 255.;
 	}
-	
+
 	public function endFill() {
 		flush();
 		doFill = false;
 	}
-	
+
 	public inline function setColor( color : Int, alpha : Float = 1. ) {
 		curA = alpha;
 		curR = ((color >> 16) & 0xFF) / 255.;
 		curG = ((color >> 8) & 0xFF) / 255.;
 		curB = (color & 0xFF) / 255.;
 	}
-	
+
 	public function drawRect( x : Float, y : Float, w : Float, h : Float ) {
 		addPoint(x, y);
 		addPoint(x + w, y);
 		addPoint(x + w, y + h);
 		addPoint(x, y + h);
 	}
-	
+
 	public function drawCircle( cx : Float, cy : Float, ray : Float, nsegments = 0 ) {
 		if( nsegments == 0 )
 			nsegments = Math.ceil(ray * 3.14 * 2 / 4);
@@ -271,19 +285,23 @@ class Graphics extends Drawable {
 			addPoint(cx + Math.cos(a) * ray, cy + Math.sin(a) * ray);
 		}
 	}
-	
+
 	public function addHole() {
 		if( pts.length > 0 ) {
 			prev.push(pts);
 			pts = [];
 		}
 	}
-	
+
 	public inline function addPoint( x : Float, y : Float ) {
 		addPointFull(x, y, curR, curG, curB, curA);
 	}
 
 	public function addPointFull( x : Float, y : Float, r : Float, g : Float, b : Float, a : Float, u : Float = 0., v : Float = 0. ) {
+		if( x < xMin ) xMin = x;
+		if( y < yMin ) yMin = y;
+		if( x > xMax ) xMax = x;
+		if( y < yMax ) yMax = y;
 		if( doFill ) {
 			var p = new GraphicsPoint(x, y);
 			p.id = pindex++;
@@ -293,7 +311,7 @@ class Graphics extends Drawable {
 		if( lineSize > 0 )
 			linePts.push(new LinePoint(x, y, lineR, lineG, lineB, lineA));
 	}
-	
+
 	override function draw(ctx:RenderContext) {
 		flush();
 		ctx.beginDrawObject(this, tile.getTexture());

+ 29 - 24
h2d/Interactive.hx

@@ -18,7 +18,7 @@ class Interactive extends Drawable {
 	public var enableRightButton : Bool;
 	var scene : Scene;
 	var isMouseDown : Int;
-	
+
 	public function new(width, height, ?parent) {
 		super(parent);
 		this.width = width;
@@ -31,18 +31,23 @@ class Interactive extends Drawable {
 		if( scene != null ) scene.addEventTarget(this);
 		super.onAlloc();
 	}
-	
+
 	override function draw( ctx : RenderContext ) {
 		if( backgroundColor != null ) emitTile(ctx,h2d.Tile.fromColor(backgroundColor,Std.int(width),Std.int(height)));
 	}
-	
+
+	override function getBoundsRec( relativeTo, out ) {
+		super.getBoundsRec(relativeTo, out);
+		if( backgroundColor != null ) addBounds(relativeTo, out, 0, 0, Std.int(width), Std.int(height));
+	}
+
 	override function onParentChanged() {
 		if( scene != null ) {
 			scene.removeEventTarget(this);
 			scene.addEventTarget(this);
 		}
 	}
-	
+
 	override function calcAbsPos() {
 		super.calcAbsPos();
 		// force a move event if we update the current over interactive
@@ -52,7 +57,7 @@ class Interactive extends Drawable {
 			@:privateAccess scene.onEvent(e);
 		}
 	}
-	
+
 	override function onDelete() {
 		if( scene != null ) {
 			scene.removeEventTarget(this);
@@ -72,7 +77,7 @@ class Interactive extends Drawable {
 		default: true;
 		}
 	}
-	
+
 	@:allow(h2d.Scene)
 	function handleEvent( e : hxd.Event ) {
 		if( isEllipse && checkBounds(e) ) {
@@ -122,40 +127,40 @@ class Interactive extends Drawable {
 			onKeyDown(e);
 		}
 	}
-	
+
 	function set_cursor(c) {
 		this.cursor = c;
 		if( scene != null && scene.currentOver == this )
 			hxd.System.setCursor(cursor);
 		return c;
 	}
-	
+
 	function eventToLocal( e : hxd.Event ) {
 		// convert global event to our local space
 		var x = e.relX, y = e.relY;
 		var rx = x * scene.matA + y * scene.matB + scene.absX;
 		var ry = x * scene.matC + y * scene.matD + scene.absY;
 		var r = scene.height / scene.width;
-		
+
 		var i = this;
-		
+
 		var dx = rx - i.absX;
 		var dy = ry - i.absY;
-		
+
 		var w1 = i.width * i.matA * r;
 		var h1 = i.width * i.matC;
 		var ky = h1 * dx - w1 * dy;
-		
+
 		var w2 = i.height * i.matB * r;
 		var h2 = i.height * i.matD;
 		var kx = w2 * dy - h2 * dx;
-		
+
 		var max = h1 * w2 - w1 * h2;
-		
+
 		e.relX = (kx * r / max) * i.width;
 		e.relY = (ky / max) * i.height;
 	}
-	
+
 	public function startDrag(callb,?onCancel) {
 		scene.startDrag(function(event) {
 			var x = event.relX, y = event.relY;
@@ -165,11 +170,11 @@ class Interactive extends Drawable {
 			event.relY = y;
 		},onCancel);
 	}
-	
+
 	public function stopDrag() {
 		scene.stopDrag();
 	}
-	
+
 	public function focus() {
 		if( scene == null )
 			return;
@@ -184,7 +189,7 @@ class Interactive extends Drawable {
 		ev.kind = EFocus;
 		handleEvent(ev);
 	}
-	
+
 	public function blur() {
 		if( scene == null )
 			return;
@@ -194,17 +199,17 @@ class Interactive extends Drawable {
 			scene.currentFocus.handleEvent(ev);
 		}
 	}
-	
+
 	public function hasFocus() {
 		return scene != null && scene.currentFocus == this;
 	}
-	
+
 	public dynamic function onOver( e : hxd.Event ) {
 	}
 
 	public dynamic function onOut( e : hxd.Event ) {
 	}
-	
+
 	public dynamic function onPush( e : hxd.Event ) {
 	}
 
@@ -213,7 +218,7 @@ class Interactive extends Drawable {
 
 	public dynamic function onClick( e : hxd.Event ) {
 	}
-	
+
 	public dynamic function onMove( e : hxd.Event ) {
 	}
 
@@ -222,7 +227,7 @@ class Interactive extends Drawable {
 
 	public dynamic function onFocus( e : hxd.Event ) {
 	}
-	
+
 	public dynamic function onFocusLost( e : hxd.Event ) {
 	}
 
@@ -231,5 +236,5 @@ class Interactive extends Drawable {
 
 	public dynamic function onKeyDown( e : hxd.Event ) {
 	}
-	
+
 }

+ 16 - 5
h2d/Mask.hx

@@ -4,24 +4,35 @@ class Mask extends Sprite {
 
 	public var width : Int;
 	public var height : Int;
-	
+
 	public function new(width, height, ?parent) {
 		super(parent);
 		this.width = width;
 		this.height = height;
 	}
 
+	override function getBoundsRec( relativeTo, out ) {
+		super.getBoundsRec(relativeTo, out);
+		var xMin = out.xMin, yMin = out.yMin, xMax = out.xMax, yMax = out.yMax;
+		out.empty();
+		addBounds(relativeTo, out, 0, 0, width, height);
+		if( xMin > out.xMin ) out.xMin = xMin;
+		if( yMin > out.yMin ) out.yMin = yMin;
+		if( xMax < out.xMax ) out.xMax = xMax;
+		if( yMax < out.yMax ) out.yMax = yMax;
+	}
+
 	override function drawRec( ctx : h2d.RenderContext ) {
-				
+
 		var x1 = (absX + 1) * 0.5 * ctx.engine.width;
 		var y1 = (1 - absY) * 0.5 * ctx.engine.height;
-		
+
 		var x2 = ((width * matA + height * matC + absX) + 1) * 0.5 * ctx.engine.width;
 		var y2 = (1 - (width * matB + height * matD + absY)) * 0.5 * ctx.engine.height;
-		
+
 		ctx.engine.setRenderZone(Std.int(x1+1e-10), Std.int(y1+1e-10), Std.int(x2-x1+1e-10), Std.int(y2-y1+1e-10));
 		super.drawRec(ctx);
 		ctx.engine.setRenderZone();
 	}
-	
+
 }

+ 60 - 40
h2d/Scene.hx

@@ -5,25 +5,27 @@ class Scene extends Layers implements h3d.IDrawable {
 
 	public var width(default,null) : Int;
 	public var height(default, null) : Int;
-	
+
 	public var mouseX(get, null) : Float;
 	public var mouseY(get, null) : Float;
-	
+
+	public var zoom(get, set) : Int;
+
 	var fixedSize : Bool;
 	var interactive : Array<Interactive>;
 	var pendingEvents : Array<hxd.Event>;
 	var ctx : RenderContext;
 	var stage : hxd.Stage;
-	
+
 	@:allow(h2d.Interactive)
 	var currentOver : Interactive;
 	@:allow(h2d.Interactive)
 	var currentFocus : Interactive;
-		
+
 	var pushList : Array<Interactive>;
 	var currentDrag : { f : hxd.Event -> Void, onCancel : Void -> Void, ref : Null<Int> };
 	var eventListeners : Array< hxd.Event -> Void >;
-	
+
 	public function new() {
 		super(null);
 		var e = h3d.Engine.getCurrent();
@@ -36,7 +38,25 @@ class Scene extends Layers implements h3d.IDrawable {
 		stage = hxd.Stage.getInstance();
 		posChanged = true;
 	}
-	
+
+	function get_zoom() {
+		return Std.int(h3d.Engine.getCurrent().width / width);
+	}
+
+	function set_zoom(v:Int) {
+		var e = h3d.Engine.getCurrent();
+		var stage = hxd.Stage.getInstance();
+		var twidth = Math.ceil(stage.width / v);
+		var theight = Math.ceil(stage.height / v);
+		var totalWidth = twidth * v;
+		var totalHeight = theight * v;
+		// increase back buffer size if necessary
+		if( totalWidth != e.width || totalHeight != e.height )
+			e.resize(totalWidth, totalHeight);
+		setFixedSize(twidth, theight);
+		return v;
+	}
+
 	public function setFixedSize( w, h ) {
 		width = w;
 		height = h;
@@ -48,12 +68,12 @@ class Scene extends Layers implements h3d.IDrawable {
 		stage.addEventTarget(onEvent);
 		super.onAlloc();
 	}
-	
+
 	override function onDelete() {
 		stage.removeEventTarget(onEvent);
 		super.onDelete();
 	}
-	
+
 	function onEvent( e : hxd.Event ) {
 		if( pendingEvents != null ) {
 			e.relX = screenXToLocal(e.relX);
@@ -61,7 +81,7 @@ class Scene extends Layers implements h3d.IDrawable {
 			pendingEvents.push(e);
 		}
 	}
-	
+
 	function screenXToLocal(mx:Float) {
 		return (mx - x) * width / (stage.width * scaleX);
 	}
@@ -69,7 +89,7 @@ class Scene extends Layers implements h3d.IDrawable {
 	function screenYToLocal(my:Float) {
 		return (my - y) * height / (stage.height * scaleY);
 	}
-	
+
 	function get_mouseX() {
 		return screenXToLocal(stage.mouseX);
 	}
@@ -77,7 +97,7 @@ class Scene extends Layers implements h3d.IDrawable {
 	function get_mouseY() {
 		return screenYToLocal(stage.mouseY);
 	}
-	
+
 	function dispatchListeners( event : hxd.Event ) {
 		event.propagate = true;
 		event.cancel = false;
@@ -107,27 +127,27 @@ class Scene extends Layers implements h3d.IDrawable {
 		default:
 		}
 		for( i in interactive ) {
-			
+
 			// this is a bit tricky since we are not in the not-euclide viewport space
 			// (r = ratio correction)
 			var dx = rx - i.absX;
 			var dy = ry - i.absY;
-			
+
 			var w1 = i.width * i.matA * r;
 			var h1 = i.width * i.matC;
 			var ky = h1 * dx - w1 * dy;
 			// up line
 			if( ky < 0 )
 				continue;
-				
+
 			var w2 = i.height * i.matB * r;
 			var h2 = i.height * i.matD;
 			var kx = w2 * dy - h2 * dx;
-				
+
 			// left line
 			if( kx < 0 )
 				continue;
-			
+
 			var max = h1 * w2 - w1 * h2;
 			// bottom/right
 			if( ky >= max || kx * r >= max )
@@ -144,12 +164,12 @@ class Scene extends Layers implements h3d.IDrawable {
 				p = p.parent;
 			}
 			if( !visible ) continue;
-			
+
 			event.relX = (kx * r / max) * i.width;
 			event.relY = (ky / max) * i.height;
-			
+
 			i.handleEvent(event);
-			
+
 			if( event.cancel )
 				event.cancel = false;
 			else if( checkOver ) {
@@ -184,12 +204,12 @@ class Scene extends Layers implements h3d.IDrawable {
 				if( cancelFocus && i == currentFocus )
 					cancelFocus = false;
 			}
-				
+
 			if( event.propagate ) {
 				event.propagate = false;
 				continue;
 			}
-			
+
 			handled = true;
 			break;
 		}
@@ -210,11 +230,11 @@ class Scene extends Layers implements h3d.IDrawable {
 			dispatchListeners(event);
 		}
 	}
-	
+
 	function hasEvents() {
 		return interactive.length > 0 || eventListeners.length > 0;
 	}
-	
+
 	public function checkEvents() {
 		if( pendingEvents == null ) {
 			if( !hasEvents() )
@@ -231,12 +251,12 @@ class Scene extends Layers implements h3d.IDrawable {
 			case EKeyUp, EKeyDown: false;
 			default: true;
 			}
-			
+
 			if( hasPos ) {
 				ox = e.relX;
 				oy = e.relY;
 			}
-			
+
 			if( currentDrag != null && (currentDrag.ref == null || currentDrag.ref == e.touchId) ) {
 				currentDrag.f(e);
 				if( e.cancel )
@@ -264,7 +284,7 @@ class Scene extends Layers implements h3d.IDrawable {
 		if( hasEvents() )
 			pendingEvents = new Array();
 	}
-	
+
 	public function addEventListener( f : hxd.Event -> Void ) {
 		eventListeners.push(f);
 	}
@@ -272,21 +292,21 @@ class Scene extends Layers implements h3d.IDrawable {
 	public function removeEventListener( f : hxd.Event -> Void ) {
 		return eventListeners.remove(f);
 	}
-	
+
 	public function startDrag( f : hxd.Event -> Void, ?onCancel : Void -> Void, ?refEvent : hxd.Event ) {
 		if( currentDrag != null && currentDrag.onCancel != null )
 			currentDrag.onCancel();
 		currentDrag = { f : f, ref : refEvent == null ? null : refEvent.touchId, onCancel : onCancel };
 	}
-	
+
 	public function stopDrag() {
 		currentDrag = null;
 	}
-	
+
 	public function getFocus() {
 		return currentFocus;
 	}
-	
+
 	@:allow(h2d)
 	function addEventTarget(i:Interactive) {
 		// sort by which is over the other in the scene hierarchy
@@ -338,7 +358,7 @@ class Scene extends Layers implements h3d.IDrawable {
 		}
 		interactive.push(i);
 	}
-	
+
 	@:allow(h2d)
 	function removeEventTarget(i) {
 		for( k in 0...interactive.length )
@@ -356,7 +376,7 @@ class Scene extends Layers implements h3d.IDrawable {
 		matD = scaleY;
 		absX = x;
 		absY = y;
-		
+
 		// adds a pixels-to-viewport transform
 		var w = 2 / width;
 		var h = -2 / height;
@@ -376,7 +396,7 @@ class Scene extends Layers implements h3d.IDrawable {
 		matB *= h;
 		matC *= w;
 		matD *= h;
-		
+
 		// perform final rotation around center
 		if( rotation != 0 ) {
 			var cr = Math.cos(rotation);
@@ -395,16 +415,16 @@ class Scene extends Layers implements h3d.IDrawable {
 			absY = tmpY;
 		}
 	}
-	
+
 	public function dispose() {
 		if( allocated )
 			onDelete();
 	}
-	
+
 	public function setElapsedTime( v : Float ) {
 		ctx.elapsedTime = v;
 	}
-	
+
 	public function render( engine : h3d.Engine ) {
 		ctx.engine = engine;
 		ctx.frame++;
@@ -414,7 +434,7 @@ class Scene extends Layers implements h3d.IDrawable {
 		drawRec(ctx);
 		ctx.end();
 	}
-	
+
 	override function sync( ctx : RenderContext ) {
 		if( !allocated )
 			onAlloc();
@@ -425,7 +445,7 @@ class Scene extends Layers implements h3d.IDrawable {
 		}
 		super.sync(ctx);
 	}
-	
+
 	public function captureBitmap( ?target : Tile ) {
 		var engine = h3d.Engine.getCurrent();
 		if( target == null ) {
@@ -448,6 +468,6 @@ class Scene extends Layers implements h3d.IDrawable {
 		engine.end();
 		return new Bitmap(target);
 	}
-	
-	
+
+
 }

+ 132 - 26
h2d/Sprite.hx

@@ -7,7 +7,7 @@ class Sprite {
 	var childs : Array<Sprite>;
 	public var parent(default, null) : Sprite;
 	public var numChildren(get, never) : Int;
-	
+
 	public var x(default,set) : Float;
 	public var y(default, set) : Float;
 	public var scaleX(default,set) : Float;
@@ -21,11 +21,11 @@ class Sprite {
 	var matD : Float;
 	var absX : Float;
 	var absY : Float;
-	
+
 	var posChanged : Bool;
 	var allocated : Bool;
 	var lastFrame : Int;
-	
+
 	public function new( ?parent : Sprite ) {
 		matA = 1; matB = 0; matC = 0; matD = 1; absX = 0; absY = 0;
 		x = 0; y = 0; scaleX = 1; scaleY = 1; rotation = 0;
@@ -35,14 +35,120 @@ class Sprite {
 		if( parent != null )
 			parent.addChild(this);
 	}
-	
+
+	public function getBounds( ?relativeTo : Sprite, ?out : h2d.col.Bounds ) : h2d.col.Bounds {
+		if( out == null ) out = new h2d.col.Bounds();
+		if( relativeTo == null ) {
+			relativeTo = getScene();
+			if( relativeTo == null ) relativeTo = this;
+		}
+		syncPos();
+		getBoundsRec(relativeTo, out);
+		if( out.isEmpty() ) {
+			addBounds(relativeTo, out, 0, 0, 1, 1);
+			out.xMax = out.xMin;
+			out.yMax = out.yMin;
+		}
+		return out;
+	}
+
+	function getBoundsRec( relativeTo : Sprite, out : h2d.col.Bounds ) {
+		var n = childs.length;
+		if( n == 0 ) {
+			out.empty();
+			return;
+		}
+		if( posChanged ) {
+			calcAbsPos();
+			for( c in childs )
+				c.posChanged = true;
+			posChanged = false;
+		}
+		if( n == 1 ) {
+			childs[0].getBounds(relativeTo, out);
+			return;
+		}
+		var xmin = hxd.Math.POSITIVE_INFINITY, ymin = hxd.Math.POSITIVE_INFINITY;
+		var xmax = hxd.Math.NEGATIVE_INFINITY, ymax = hxd.Math.NEGATIVE_INFINITY;
+		for( c in childs ) {
+			c.getBoundsRec(relativeTo, out);
+			if( out.xMin < xmin ) xmin = out.xMin;
+			if( out.yMin < ymin ) ymin = out.yMin;
+			if( out.xMax > xmax ) xmax = out.xMax;
+			if( out.yMax > ymax ) ymax = out.yMax;
+		}
+		out.xMin = xmin;
+		out.yMin = ymin;
+		out.xMax = xmax;
+		out.yMax = ymax;
+	}
+
+	function addBounds( relativeTo : Sprite, out : h2d.col.Bounds, dx : Float, dy : Float, width : Float, height : Float ) {
+
+		if( width <= 0 || height <= 0 ) return;
+
+		if( relativeTo == this ) {
+			if( out.xMin > dx ) out.xMin = dx;
+			if( out.yMin > dy ) out.yMin = dy;
+			if( out.xMax < dx + width ) out.xMax = dx + width;
+			if( out.yMax < dy + height ) out.yMax = dy + height;
+			return;
+		}
+
+		var det = 1 / (relativeTo.matA * relativeTo.matD + relativeTo.matB * relativeTo.matC);
+		var rA = relativeTo.matD * det;
+		var rB = -relativeTo.matC * det;
+		var rC = -relativeTo.matB * det;
+		var rD = relativeTo.matA * det;
+		var rX = absX - relativeTo.absX;
+		var rY = absY - relativeTo.absY;
+
+		var x, y, rx, ry;
+
+		x = dx * matA + dy * matC + rX;
+		y = dx * matB + dy * matD + rY;
+		rx = x * rA + y * rC;
+		ry = x * rB + y * rD;
+		if( out.xMin > rx ) out.xMin = rx;
+		if( out.yMin > ry ) out.yMin = ry;
+		if( out.xMax < rx ) out.xMax = rx;
+		if( out.yMax < ry ) out.yMax = ry;
+
+		x = (dx + width) * matA + dy * matC + rX;
+		y = (dx + width) * matB + dy * matD + rY;
+		rx = x * rA + y * rC;
+		ry = x * rB + y * rD;
+		if( out.xMin > rx ) out.xMin = rx;
+		if( out.yMin > ry ) out.yMin = ry;
+		if( out.xMax < rx ) out.xMax = rx;
+		if( out.yMax < ry ) out.yMax = ry;
+
+		x = dx * matA + (dy + height) * matC + rX;
+		y = dx * matB + (dy + height) * matD + rY;
+		rx = x * rA + y * rC;
+		ry = x * rB + y * rD;
+		if( out.xMin > rx ) out.xMin = rx;
+		if( out.yMin > ry ) out.yMin = ry;
+		if( out.xMax < rx ) out.xMax = rx;
+		if( out.yMax < ry ) out.yMax = ry;
+
+		x = (dx + width) * matA + (dy + height) * matC + rX;
+		y = (dx + width) * matB + (dy + height) * matD + rY;
+		rx = x * rA + y * rC;
+		ry = x * rB + y * rD;
+		if( out.xMin > rx ) out.xMin = rx;
+		if( out.yMin > ry ) out.yMin = ry;
+		if( out.xMax < rx ) out.xMax = rx;
+		if( out.yMax < ry ) out.yMax = ry;
+	}
+
 	public function getSpritesCount() {
 		var k = 0;
 		for( c in childs )
 			k += c.getSpritesCount() + 1;
 		return k;
 	}
-	
+
 	public function localToGlobal( ?pt : h2d.col.Point ) {
 		syncPos();
 		if( pt == null ) pt = new h2d.col.Point();
@@ -82,17 +188,17 @@ class Sprite {
 		pt.y = py;
 		return pt;
 	}
-	
+
 	function getScene() {
 		var p = this;
 		while( p.parent != null ) p = p.parent;
 		return Std.instance(p, Scene);
 	}
-	
+
 	public function addChild( s : Sprite ) {
 		addChildAt(s, childs.length);
 	}
-	
+
 	public function addChildAt( s : Sprite, pos : Int ) {
 		if( pos < 0 ) pos = 0;
 		if( pos > childs.length ) pos = childs.length;
@@ -121,40 +227,40 @@ class Sprite {
 				s.onParentChanged();
 		}
 	}
-	
+
 	// called when we're allocated already but moved in hierarchy
 	function onParentChanged() {
 	}
-	
+
 	// kept for internal init
 	function onAlloc() {
 		allocated = true;
 		for( c in childs )
 			c.onAlloc();
 	}
-		
+
 	// kept for internal cleanup
 	function onDelete() {
 		allocated = false;
 		for( c in childs )
 			c.onDelete();
 	}
-	
+
 	public function removeChild( s : Sprite ) {
 		if( childs.remove(s) ) {
 			if( s.allocated ) s.onDelete();
 			s.parent = null;
 		}
 	}
-	
+
 	// shortcut for parent.removeChild
 	public inline function remove() {
 		if( this != null && parent != null ) parent.removeChild(this);
 	}
-	
+
 	function draw( ctx : RenderContext ) {
 	}
-	
+
 	function sync( ctx : RenderContext ) {
 		/*
 		if( currentAnimation != null ) {
@@ -172,7 +278,7 @@ class Sprite {
 			calcAbsPos();
 			posChanged = false;
 		}
-		
+
 		lastFrame = ctx.frame;
 		var p = 0, len = childs.length;
 		while( p < len ) {
@@ -192,7 +298,7 @@ class Sprite {
 				p++;
 		}
 	}
-	
+
 	function syncPos() {
 		if( parent != null ) parent.syncPos();
 		if( posChanged ) {
@@ -202,7 +308,7 @@ class Sprite {
 			posChanged = false;
 		}
 	}
-	
+
 	function calcAbsPos() {
 		if( parent == null ) {
 			var cr, sr;
@@ -272,22 +378,22 @@ class Sprite {
 		posChanged = true;
 		return y = v;
 	}
-	
+
 	inline function set_scaleX(v) {
 		posChanged = true;
 		return scaleX = v;
 	}
-	
+
 	inline function set_scaleY(v) {
 		posChanged = true;
 		return scaleY = v;
 	}
-	
+
 	inline function set_rotation(v) {
 		posChanged = true;
 		return rotation = v;
 	}
-	
+
 	public function move( dx : Float, dy : Float ) {
 		x += dx * Math.cos(rotation);
 		y += dy * Math.sin(rotation);
@@ -297,16 +403,16 @@ class Sprite {
 		this.x = x;
 		this.y = y;
 	}
-	
+
 	public inline function rotate( v : Float ) {
 		rotation += v;
 	}
-	
+
 	public inline function scale( v : Float ) {
 		scaleX *= v;
 		scaleY *= v;
 	}
-	
+
 	public inline function setScale( v : Float ) {
 		scaleX = v;
 		scaleY = v;
@@ -322,7 +428,7 @@ class Sprite {
 				return i;
 		return -1;
 	}
-	
+
 	inline function get_numChildren() {
 		return childs.length;
 	}

+ 47 - 12
h2d/SpriteBatch.hx

@@ -13,10 +13,10 @@ class BatchElement {
 	public var t : Tile;
 	public var alpha(get,set) : Float;
 	public var batch(default, null) : SpriteBatch;
-	
+
 	var prev : BatchElement;
 	var next : BatchElement;
-	
+
 	function new(t) {
 		x = 0; y = 0; r = 1; g = 1; b = 1; a = 1;
 		rotation = 0; scale = 1;
@@ -34,11 +34,11 @@ class BatchElement {
 	function update(et:Float) {
 		return true;
 	}
-	
+
 	public inline function remove() {
 		batch.delete(this);
 	}
-	
+
 }
 
 class SpriteBatch extends Drawable {
@@ -49,12 +49,12 @@ class SpriteBatch extends Drawable {
 	var first : BatchElement;
 	var last : BatchElement;
 	var tmpBuf : hxd.FloatBuffer;
-		
+
 	public function new(t,?parent) {
 		super(parent);
 		tile = t;
 	}
-	
+
 	public function add(e:BatchElement) {
 		e.batch = this;
 		if( first == null )
@@ -66,11 +66,11 @@ class SpriteBatch extends Drawable {
 		}
 		return e;
 	}
-	
+
 	public function alloc(t) {
 		return add(new BatchElement(t));
 	}
-	
+
 	@:allow(h2d.BatchElement)
 	function delete(e : BatchElement) {
 		if( e.prev == null ) {
@@ -84,7 +84,7 @@ class SpriteBatch extends Drawable {
 		} else
 			e.next.prev = e.prev;
 	}
-	
+
 	override function sync(ctx) {
 		super.sync(ctx);
 		if( hasUpdate ) {
@@ -96,7 +96,42 @@ class SpriteBatch extends Drawable {
 			}
 		}
 	}
-	
+
+	override function getBoundsRec( relativeTo, out ) {
+		super.getBoundsRec(relativeTo, out);
+		var e = first;
+		while( e != null ) {
+			var t = e.t;
+			if( hasRotationScale ) {
+				var ca = Math.cos(e.rotation), sa = Math.sin(e.rotation);
+				var hx = t.width, hy = t.height;
+				var px = t.dx, py = t.dy;
+				var x, y;
+
+				x = (px * ca - py * sa) * e.scale + e.x;
+				y = (py * ca + px * sa) * e.scale + e.y;
+				addBounds(relativeTo, out, x, y, 1e-10, 1e-10);
+
+				var px = t.dx + hx, py = t.dy;
+				x = (px * ca - py * sa) * e.scale + e.x;
+				y = (py * ca + px * sa) * e.scale + e.y;
+				addBounds(relativeTo, out, x, y, 1e-10, 1e-10);
+
+				var px = t.dx, py = t.dy + hy;
+				x = (px * ca - py * sa) * e.scale + e.x;
+				y = (py * ca + px * sa) * e.scale + e.y;
+				addBounds(relativeTo, out, x, y, 1e-10, 1e-10);
+
+				var px = t.dx + hx, py = t.dy + hy;
+				x = (px * ca - py * sa) * e.scale + e.x;
+				y = (py * ca + px * sa) * e.scale + e.y;
+				addBounds(relativeTo, out, x, y, 1e-10, 1e-10);
+			} else
+				addBounds(relativeTo, out, e.x + tile.dx, e.y + tile.dy, tile.width, tile.height);
+			e = e.next;
+		}
+	}
+
 	override function draw( ctx : RenderContext ) {
 		if( first == null )
 			return;
@@ -188,9 +223,9 @@ class SpriteBatch extends Drawable {
 		ctx.engine.renderQuadBuffer(buffer);
 		buffer.dispose();
 	}
-	
+
 	public inline function isEmpty() {
 		return first == null;
 	}
-	
+
 }

+ 17 - 0
h2d/TileGroup.hx

@@ -3,6 +3,10 @@ package h2d;
 private class TileLayerContent extends h3d.prim.Primitive {
 
 	var tmp : hxd.FloatBuffer;
+	public var xMin : Float;
+	public var yMin : Float;
+	public var xMax : Float;
+	public var yMax : Float;
 
 	public function new() {
 		reset();
@@ -12,6 +16,10 @@ private class TileLayerContent extends h3d.prim.Primitive {
 		tmp = new hxd.FloatBuffer();
 		if( buffer != null ) buffer.dispose();
 		buffer = null;
+		xMin = hxd.Math.POSITIVE_INFINITY;
+		yMin = hxd.Math.POSITIVE_INFINITY;
+		xMax = hxd.Math.NEGATIVE_INFINITY;
+		yMax = hxd.Math.NEGATIVE_INFINITY;
 	}
 	
 	public function isEmpty() {
@@ -69,6 +77,10 @@ private class TileLayerContent extends h3d.prim.Primitive {
 		tmp.push(0);
 		tmp.push(0);
 		insertColor(color);
+		if( x < xMin ) xMin = x;
+		if( y < yMin ) yMin = y;
+		if( x > xMax ) xMax = x;
+		if( y > yMax ) yMax = y;
 	}
 	
 	inline function insertColor( c : Int ) {
@@ -153,6 +165,11 @@ class TileGroup extends Drawable {
 		content = new TileLayerContent();
 	}
 
+	override function getBoundsRec( relativeTo, out ) {
+		super.getBoundsRec(relativeTo, out);
+		addBounds(relativeTo, out, content.xMin, content.yMin, content.xMax - content.xMin, content.yMax - content.yMin);
+	}
+
 	public function reset() {
 		content.reset();
 	}

+ 61 - 17
h2d/col/Bounds.hx

@@ -1,25 +1,31 @@
 package h2d.col;
 
 class Bounds {
-	
+
 	public var xMin : Float;
 	public var yMin : Float;
 
 	public var xMax : Float;
 	public var yMax : Float;
-	
+
+
+	public var x(get, set) : Float;
+	public var y(get, set) : Float;
+	public var width(get, set) : Float;
+	public var height(get, set) : Float;
+
 	public inline function new() {
 		empty();
 	}
-	
+
 	public inline function collide( b : Bounds ) {
 		return !(xMin > b.xMax || yMin > b.yMax || xMax < b.xMin || yMax < b.yMin);
 	}
-	
+
 	public inline function include( p : Point ) {
 		return p.x >= xMin && p.x < xMax && p.y >= yMin && p.y < yMax;
 	}
-	
+
 	public inline function add( b : Bounds ) {
 		if( b.xMin < xMin ) xMin = b.xMin;
 		if( b.xMax > xMax ) xMax = b.xMax;
@@ -33,7 +39,7 @@ class Bounds {
 		if( p.y < yMin ) yMin = p.y;
 		if( p.y > yMax ) yMax = p.y;
 	}
-	
+
 	public inline function setMin( p : Point ) {
 		xMin = p.x;
 		yMin = p.y;
@@ -43,14 +49,14 @@ class Bounds {
 		xMax = p.x;
 		yMax = p.y;
 	}
-	
+
 	public function load( b : Bounds ) {
 		xMin = b.xMin;
 		yMin = b.yMin;
 		xMax = b.xMax;
 		yMax = b.yMax;
 	}
-	
+
 	public function scaleCenter( v : Float ) {
 		var dx = (xMax - xMin) * 0.5 * v;
 		var dy = (yMax - yMin) * 0.5 * v;
@@ -61,18 +67,18 @@ class Bounds {
 		xMax = mx + dx * v;
 		yMax = my + dy * v;
 	}
-	
+
 	public inline function offset( dx : Float, dy : Float ) {
 		xMin += dx;
 		xMax += dx;
 		yMin += dy;
 		yMax += dy;
 	}
-	
+
 	public inline function getMin() {
 		return new Point(xMin, yMin);
 	}
-	
+
 	public inline function getCenter() {
 		return new Point((xMin + xMax) * 0.5, (yMin + yMax) * 0.5);
 	}
@@ -80,11 +86,15 @@ class Bounds {
 	public inline function getSize() {
 		return new Point(xMax - xMin, yMax - yMin);
 	}
-	
+
 	public inline function getMax() {
 		return new Point(xMax, yMax);
 	}
-	
+
+	public inline function isEmpty() {
+		return xMax <= xMin || yMax <= yMin;
+	}
+
 	public inline function empty() {
 		xMin = 1e20;
 		yMin = 1e20;
@@ -98,7 +108,7 @@ class Bounds {
 		xMax = 1e20;
 		yMax = 1e20;
 	}
-	
+
 	public inline function clone() {
 		var b = new Bounds();
 		b.xMin = xMin;
@@ -107,7 +117,41 @@ class Bounds {
 		b.yMax = yMax;
 		return b;
 	}
-	
+
+	inline function get_x() {
+		return xMin;
+	}
+
+	inline function get_y() {
+		return yMin;
+	}
+
+	inline function set_x(x) {
+		return xMin = x;
+	}
+
+	inline function set_y(y) {
+		return yMin = y;
+	}
+
+	inline function get_width() {
+		return xMax - xMin;
+	}
+
+	inline function get_height() {
+		return yMax - yMin;
+	}
+
+	inline function set_width(w) {
+		xMax = xMin + w;
+		return w;
+	}
+
+	inline function set_height(h) {
+		yMax = yMin + h;
+		return h;
+	}
+
 	public function toString() {
 		return "{" + getMin() + "," + getMax() + "}";
 	}
@@ -120,12 +164,12 @@ class Bounds {
 		b.yMax = y0 + height;
 		return b;
 	}
-	
+
 	public static inline function fromPoints( min : Point, max : Point ) {
 		var b = new Bounds();
 		b.setMin(min);
 		b.setMax(max);
 		return b;
 	}
-	
+
 }

+ 2 - 2
h3d/Buffer.hx

@@ -92,8 +92,8 @@ class Buffer {
 		}
 	}
 	
-	public static function ofFloats( v : hxd.FloatBuffer, stride : Int, ?flags ) {
-		var nvert = Std.int(v.length / stride);
+	public static function ofFloats( v : hxd.FloatBuffer, stride : Int, ?flags, ?vertices ) {
+		var nvert = vertices == null ? Std.int(v.length / stride) : vertices;
 		var b = new Buffer(nvert, stride, flags);
 		b.uploadVector(v, 0, nvert);
 		return b;

+ 10 - 0
h3d/impl/ManagedBuffer.hx

@@ -56,6 +56,16 @@ class ManagedBuffer {
 		return b;
 	}
 	
+	public function getFreeVertices() {
+		var m = 0;
+		var l = freeList;
+		while( l != null ) {
+			m += l.count;
+			l = l.next;
+		}
+		return m;
+	}
+	
 	function allocPosition( nvert : Int, align : Int ) {
 		var free = freeList;
 		while( free != null ) {

+ 37 - 35
h3d/impl/Stage3dDriver.hx

@@ -8,12 +8,12 @@ class VertexWrapper {
 	var vbuf : flash.display3D.VertexBuffer3D;
 	var written : Bool;
 	var b : ManagedBuffer;
-	
+
 	function new(vbuf, b) {
 		this.vbuf = vbuf;
 		this.b = b;
 	}
-		
+
 	function finalize( driver : Stage3dDriver ) {
 		if( written ) return;
 		written = true;
@@ -32,11 +32,13 @@ class VertexWrapper {
 }
 
 class Stage3dDriver extends Driver {
-	
+
+	public static var PROFILE = flash.display3D.Context3DProfile.BASELINE;
+
 	var s3d : flash.display.Stage3D;
 	var ctx : flash.display3D.Context3D;
 	var onCreateCallback : Bool -> Void;
-	
+
 	var curMatBits : Int;
 	var curShader : hxsl.Shader.ShaderInstance;
 	var curBuffer : VertexBuffer;
@@ -54,18 +56,18 @@ class Stage3dDriver extends Driver {
 
 	@:allow(h3d.impl.VertexWrapper)
 	var empty : flash.utils.ByteArray;
-	
+
 	public function new() {
 		empty = new flash.utils.ByteArray();
 		s3d = flash.Lib.current.stage.stage3Ds[0];
 		curTextures = [];
 		curMultiBuffer = [];
 	}
-	
+
 	override function getDriverName(details:Bool) {
 		return ctx == null ? "None" : (details ? ctx.driverInfo : ctx.driverInfo.split(" ")[0]);
 	}
-	
+
 	override function begin( frame : Int ) {
 		reset();
 		this.frame = frame;
@@ -85,13 +87,13 @@ class Stage3dDriver extends Driver {
 		curTextures = [];
 		curSamplerBits = [];
 	}
-	
+
 	override function init( onCreate, forceSoftware = false ) {
 		this.onCreateCallback = onCreate;
 		s3d.addEventListener(flash.events.Event.CONTEXT3D_CREATE, this.onCreate);
-		s3d.requestContext3D( forceSoftware ? "software" : "auto" );
+		s3d.requestContext3D( forceSoftware ? "software" : "auto", PROFILE );
 	}
-	
+
 	function onCreate(_) {
 		var old = ctx;
 		if( old != null ) {
@@ -105,35 +107,35 @@ class Stage3dDriver extends Driver {
 			onCreateCallback(false);
 		}
 	}
-	
+
 	override function isHardware() {
 		return ctx != null && ctx.driverInfo.toLowerCase().indexOf("software") == -1;
 	}
-	
+
 	override function resize(width, height) {
 		ctx.configureBackBuffer(width, height, antiAlias);
 		this.width = width;
 		this.height = height;
 	}
-	
+
 	override function clear(r, g, b, a) {
 		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();
 		ctx = null;
 	}
-	
+
 	override function isDisposed() {
 		return ctx == null || ctx.driverInfo == "Disposed";
 	}
-	
+
 	override function present() {
 		if( capture != null ) {
 			ctx.drawToBitmapData(capture.bmp.toNative());
@@ -145,11 +147,11 @@ class Stage3dDriver extends Driver {
 		}
 		ctx.present();
 	}
-	
+
 	override function disposeTexture( t : Texture ) {
 		t.dispose();
 	}
-	
+
 	override function allocVertex( buf : ManagedBuffer ) : VertexBuffer {
 		var v;
 		try {
@@ -166,7 +168,7 @@ class Stage3dDriver extends Driver {
 	override function allocIndexes( count : Int ) : IndexBuffer {
 		return ctx.createIndexBuffer(count);
 	}
-	
+
 	function getMipLevels( t : h3d.mat.Texture ) {
 		if( !t.flags.has(MipMapped) )
 			return 0;
@@ -175,7 +177,7 @@ class Stage3dDriver extends Driver {
 			levels++;
 		return levels;
 	}
-	
+
 	override function allocTexture( t : h3d.mat.Texture ) : Texture {
 		var fmt = flash.display3D.Context3DTextureFormat.BGRA;
 		t.lastFrame = frame;
@@ -232,20 +234,20 @@ class Stage3dDriver extends Driver {
 			t.uploadFromByteArray(data, 0, mipLevel);
 		}
 	}
-	
+
 	override function disposeVertex( v : VertexBuffer ) {
 		v.vbuf.dispose();
 		v.b = null;
 	}
-	
+
 	override function disposeIndexes( i : IndexBuffer ) {
 		i.dispose();
 	}
-	
+
 	override function setDebug( d : Bool ) {
 		if( ctx != null ) ctx.enableErrorChecking = d && isHardware();
 	}
-	
+
 	override function uploadVertexBuffer( v : VertexBuffer, startVertex : Int, vertexCount : Int, buf : hxd.FloatBuffer, bufPos : Int ) {
 		var data = buf.getNative();
 		v.vbuf.uploadFromVector( bufPos == 0 ? data : data.slice(bufPos, vertexCount * v.b.stride + bufPos), startVertex, vertexCount );
@@ -263,7 +265,7 @@ class Stage3dDriver extends Driver {
 	override function uploadIndexesBytes( i : IndexBuffer, startIndice : Int, indiceCount : Int, buf : haxe.io.Bytes, bufPos : Int ) {
 		i.uploadFromByteArray(buf.getData(), bufPos, startIndice, indiceCount );
 	}
-	
+
 	override function selectMaterial( mbits : Int ) {
 		var diff = curMatBits ^ mbits;
 		if( diff != 0 ) {
@@ -338,7 +340,7 @@ class Stage3dDriver extends Driver {
 		}
 		return shaderChanged;
 	}
-	
+
 	override function selectBuffer( v : VertexBuffer ) {
 		if( v == curBuffer )
 			return;
@@ -360,11 +362,11 @@ class Stage3dDriver extends Driver {
 			ctx.setVertexBufferAt(i, null);
 		curAttributes = pos;
 	}
-	
+
 	override function getShaderInputNames() {
 		return curShader.bufferNames;
 	}
-	
+
 	override function selectMultiBuffers( buffers : Buffer.BufferOffset ) {
 		// select the multiple buffers elements
 		var changed = false;
@@ -411,7 +413,7 @@ class Stage3dDriver extends Driver {
 				throw e;
 		}
 	}
-	
+
 	override function draw( ibuf : IndexBuffer, startIndex : Int, ntriangles : Int ) {
 		if( enableDraw ) {
 			if( ctx.enableErrorChecking )
@@ -460,7 +462,7 @@ class Stage3dDriver extends Driver {
 			ctx.clear( ((clearColor>>16)&0xFF)/255 , ((clearColor>>8)&0xFF)/255, (clearColor&0xFF)/255, ((clearColor>>>24)&0xFF)/255);
 		}
 	}
-	
+
 	static var BLEND = [
 		flash.display3D.Context3DBlendFactor.ONE,
 		flash.display3D.Context3DBlendFactor.ZERO,
@@ -499,22 +501,22 @@ class Stage3dDriver extends Driver {
 		flash.display3D.Context3DVertexBufferFormat.FLOAT_3,
 		flash.display3D.Context3DVertexBufferFormat.FLOAT_4,
 	];
-	
+
 	static var WRAP = [
 		flash.display3D.Context3DWrapMode.CLAMP,
 		flash.display3D.Context3DWrapMode.REPEAT,
 	];
-	
+
 	static var FILTER = [
 		flash.display3D.Context3DTextureFilter.NEAREST,
 		flash.display3D.Context3DTextureFilter.LINEAR,
 	];
-	
+
 	static var MIP = [
 		flash.display3D.Context3DMipFilter.MIPNONE,
 		flash.display3D.Context3DMipFilter.MIPNEAREST,
 		flash.display3D.Context3DMipFilter.MIPLINEAR,
 	];
-	
+
 }
 #end

+ 1 - 1
h3d/parts/Emitter.hx

@@ -558,7 +558,7 @@ class Emitter extends h3d.scene.Object implements Randomized {
 		}
 		var stride = 10;
 		if( hasColor ) stride += 4;
-		var buffer = h3d.Buffer.ofFloats(tmpBuf, stride, [Quads, Dynamic]);
+		var buffer = h3d.Buffer.ofFloats(tmp, stride, [Quads, Dynamic], Std.int(pos/stride));
 		var size = eval(state.globalSize);
 		
 		/*

+ 7 - 5
h3d/prim/MeshPrimitive.hx

@@ -1,8 +1,10 @@
 package h3d.prim;
 
+private typedef Cache = #if flash haxe.ds.UnsafeStringMap<h3d.Buffer.BufferOffset> #else Map<Int,h3d.Buffer.BufferOffset> #end
+
 class MeshPrimitive extends Primitive {
 		
-	var bufferCache : Map<Int,h3d.Buffer.BufferOffset>;
+	var bufferCache : Cache;
 	
 	function allocBuffer( engine : h3d.Engine, name : String ) {
 		return null;
@@ -18,8 +20,8 @@ class MeshPrimitive extends Primitive {
 	
 	function addBuffer( name : String, buf, offset = 0 ) {
 		if( bufferCache == null )
-			bufferCache = new Map();
-		var id = hash(name);
+			bufferCache = new Cache();
+		var id = #if flash name #else hash(name) #end;
 		var old = bufferCache.get(id);
 		if( old != null ) old.dispose();
 		bufferCache.set(id, new h3d.Buffer.BufferOffset(buf, offset));
@@ -35,10 +37,10 @@ class MeshPrimitive extends Primitive {
 
 	function getBuffers( engine : h3d.Engine ) {
 		if( bufferCache == null )
-			bufferCache = new Map();
+			bufferCache = new Cache();
 		var buffers = null, prev = null;
 		for( name in @:privateAccess engine.driver.getShaderInputNames() ) {
-			var id = hash(name);
+			var id = #if flash name #else hash(name) #end;
 			var b = bufferCache.get(id);
 			if( b == null ) {
 				b = allocBuffer(engine, name);

+ 12 - 1
h3d/scene/Box.hx

@@ -40,6 +40,17 @@ class Box extends Graphics {
 		drawLine(p.add(dy), p.add(dx).add(dy));
 		drawLine(p.add(dy), p.add(dx).add(dy));
 	}
-	
+
+	public static function ofBounds( bounds : h3d.col.Bounds, ?parent : Object ) {
+		var b = new Box();
+		if( parent != null ) parent.addChild(b);
+		b.x = (bounds.xMin + bounds.xMax) * 0.5;
+		b.y = (bounds.yMin + bounds.yMax) * 0.5;
+		b.z = (bounds.zMin + bounds.zMax) * 0.5;
+		b.scaleX = bounds.xMax - bounds.xMin;
+		b.scaleY = bounds.yMax - bounds.yMin;
+		b.scaleZ = bounds.zMax - bounds.zMin;
+		return b;
+	}
 	
 }

+ 2 - 2
h3d/scene/Mesh.hx

@@ -12,8 +12,8 @@ class Mesh extends Object {
 		this.material = mat;
 	}
 	
-	override function getBounds( ?b : h3d.col.Bounds ) {
-		b = super.getBounds(b);
+	override function getBounds( ?b : h3d.col.Bounds, rec = false ) {
+		b = super.getBounds(b, rec);
 		var tmp = primitive.getBounds().clone();
 		tmp.transform3x4(absPos);
 		b.add(tmp);

+ 6 - 5
h3d/scene/Object.hx

@@ -100,18 +100,19 @@ class Object {
 	/**
 		Return the bounds of this object, in absolute position.
 	**/
-	public function getBounds( ?b : h3d.col.Bounds ) {
-		if( b == null ) {
-			b = new h3d.col.Bounds();
+	public function getBounds( ?b : h3d.col.Bounds, rec = false ) {
+		if( !rec )
 			syncPos();
-		} else if( posChanged ) {
+		if( b == null )
+			b = new h3d.col.Bounds();
+		if( posChanged ) {
 			for( c in childs )
 				c.posChanged = true;
 			posChanged = false;
 			calcAbsPos();
 		}
 		for( c in childs )
-			c.getBounds(b);
+			c.getBounds(b, true);
 		return b;
 	}
 	

+ 2 - 2
h3d/scene/Skin.hx

@@ -75,8 +75,8 @@ class Skin extends MultiMaterial {
 	}
 	
 	
-	override function getBounds( ?b : h3d.col.Bounds ) {
-		b = super.getBounds(b);
+	override function getBounds( ?b : h3d.col.Bounds, rec = false ) {
+		b = super.getBounds(b, rec);
 		var tmp = primitive.getBounds().clone();
 		var b0 = skinData.allJoints[0];
 		// not sure if that's the good joint

+ 1 - 1
hxd/App.hx

@@ -2,7 +2,7 @@ package hxd;
 
 class App {
 	
-	var engine : h3d.Engine;
+	public var engine : h3d.Engine;
 	public var s3d : h3d.scene.Scene;
 	public var s2d : h2d.Scene;
 	

+ 38 - 0
hxd/Direction.hx

@@ -0,0 +1,38 @@
+package hxd;
+
+@:enum abstract Direction(Int) {
+
+	public var Up = 1;
+	public var Left = 4;
+	public var Right = 6;
+	public var Down = 9;
+
+	public var x(get, never) : Int;
+	public var y(get, never) : Int;
+	public var name(get, never) : String;
+
+	inline function get_x() {
+		return (this & 3) - 1;
+	}
+
+	inline function get_y() {
+		return (this >> 2) - 1;
+	}
+
+	inline function get_name() {
+		return VALUES[this];
+	}
+
+	static var VALUES = ["none", "up", null, null, "left", null, "right", null, null, "down"];
+	inline function toString() {
+		return name;
+	}
+
+	public static function from(x, y) {
+		if( x < 0 ) x = -1;
+		if( x > 0 ) x = 1;
+		if( y < 0 ) y = -1;
+		if( y > 0 ) y = 1;
+		return (x + 1) | ((y + 1) << 2);
+	}
+}

+ 1 - 0
hxd/Stage.hx

@@ -83,6 +83,7 @@ class Stage {
 	#if flash
 	function setupOnCloseEvent() {
 		var nw : flash.events.EventDispatcher = Reflect.field(stage, "nativeWindow");
+		if( nw == null ) return;
 		nw.addEventListener("closing", function(e:flash.events.Event) {
 			if( !onClose() )
 				e.preventDefault();

+ 43 - 32
hxd/System.hx

@@ -6,21 +6,22 @@ enum Cursor {
 	Move;
 	TextInput;
 	Hide;
+	Custom( frames : Array<hxd.BitmapData>, speed : Float, offsetX : Int, offsetY : Int );
 }
 
 class System {
-	
+
 	public static var width(get,never) : Int;
 	public static var height(get,never) : Int;
 	public static var isTouch(get,never) : Bool;
 	public static var isWindowed(get,never) : Bool;
 	public static var lang(get,never) : String;
 	public static var isAndroid(get, never) : Bool;
-	
+
 	public static var screenDPI(get,never) : Float;
 
 	#if flash
-	
+
 	static function get_isWindowed() {
 		var p = flash.system.Capabilities.playerType;
 		return p == "ActiveX" || p == "PlugIn" || p == "StandAlone" || p == "Desktop";
@@ -39,17 +40,17 @@ class System {
 		var Cap = flash.system.Capabilities;
 		return isWindowed ? flash.Lib.current.stage.stageHeight : Std.int(Cap.screenResolutionX > Cap.screenResolutionY ? Cap.screenResolutionY : Cap.screenResolutionX);
 	}
-	
+
 	static function get_isAndroid() {
 		return flash.system.Capabilities.manufacturer.indexOf('Android') != -1;
 	}
-	
+
 	static function get_screenDPI() {
 		return flash.system.Capabilities.screenDPI;
 	}
-	
+
 	static var loop = null;
-	
+
 	public static function setLoop( f : Void -> Void ) {
 		if( loop != null )
 			flash.Lib.current.removeEventListener(flash.events.Event.ENTER_FRAME, loop);
@@ -64,7 +65,7 @@ class System {
 	static function isAir() {
 		return flash.system.Capabilities.playerType == "Desktop";
 	}
-	
+
 	public static function exit() {
 		if( isAir() ) {
 			var d : Dynamic = flash.Lib.current.loaderInfo.applicationDomain.getDefinition("flash.desktop.NativeApplication");
@@ -72,9 +73,9 @@ class System {
 		} else
 			flash.system.System.exit(0);
 	}
-	
+
 	public static var setCursor = setNativeCursor;
-	
+
 	public static function setNativeCursor( c : Cursor ) {
 		flash.ui.Mouse.cursor = switch( c ) {
 		case Default: "auto";
@@ -82,10 +83,19 @@ class System {
 		case Move: "hand";
 		case TextInput: "ibeam";
 		case Hide: "auto";
+		case Custom(frames, speed, offsetX, offsetY):
+			var customCursor = new flash.ui.MouseCursorData();
+			var v = new flash.Vector();
+			for( f in frames ) v.push(f.toNative());
+			customCursor.data = v;
+			customCursor.frameRate = speed;
+			customCursor.hotSpot = new flash.geom.Point(offsetX, offsetY);
+			flash.ui.Mouse.registerCursor("custom", customCursor);
+			"custom";
 		}
 		if( c == Hide ) flash.ui.Mouse.hide() else flash.ui.Mouse.show();
 	}
-		
+
 
 	/**
 		Returns the device name:
@@ -117,12 +127,12 @@ class System {
 	static function get_lang() {
 		return flash.system.Capabilities.language;
 	}
-	
+
 	#elseif js
 
 	static var LOOP = null;
 	static var LOOP_INIT = false;
-	
+
 	static function loopFunc() {
 		var window : Dynamic = js.Browser.window;
 		var rqf : Dynamic = window.requestAnimationFrame ||
@@ -131,7 +141,7 @@ class System {
 		rqf(loopFunc);
 		if( LOOP != null ) LOOP();
 	}
-	
+
 	public static function setLoop( f : Void -> Void ) {
 		if( !LOOP_INIT ) {
 			LOOP_INIT = true;
@@ -141,7 +151,7 @@ class System {
 	}
 
 	public static var setCursor = setNativeCursor;
-	
+
 	public static function setNativeCursor( c : Cursor ) {
 		var canvas = js.Browser.document.getElementById("webgl");
 		if( canvas != null ) {
@@ -151,42 +161,43 @@ class System {
 			case Move: "move";
 			case TextInput: "text";
 			case Hide: "none";
+			case Custom(_): throw "Custom cursor not supported";
 			};
 		}
 	}
-	
+
 	static function get_lang() {
 		return "en";
 	}
-	
+
 	static function get_screenDPI() {
 		return 72.;
 	}
-	
+
 	static function get_isAndroid() {
 		return false;
 	}
-	
+
 	static function get_isWindowed() {
 		return true;
 	}
-	
+
 	static function get_isTouch() {
 		return false;
 	}
-	
+
 	static function get_width() {
 		return Math.round(js.Browser.document.body.clientWidth * js.Browser.window.devicePixelRatio);
 	}
-	
+
 	static function get_height() {
 		return Math.round(js.Browser.document.body.clientHeight  * js.Browser.window.devicePixelRatio);
 	}
-	
+
 	#elseif openfl
 
 	static var VIEW = null;
-	
+
 	public static function setLoop( f : Void -> Void ) {
 		if( VIEW == null ) {
 			VIEW = new openfl.display.OpenGLView();
@@ -196,7 +207,7 @@ class System {
 	}
 
 	public static var setCursor = setNativeCursor;
-	
+
 	public static function setNativeCursor( c : Cursor ) {
 		/* not supported by openFL
 		flash.ui.Mouse.cursor = switch( c ) {
@@ -206,15 +217,15 @@ class System {
 		case TextInput: "ibeam";
 		}*/
 	}
-	
+
 	static function get_lang() {
 		return flash.system.Capabilities.language.split("-")[0];
 	}
-	
+
 	static function get_screenDPI() {
 		return flash.system.Capabilities.screenDPI;
 	}
-	
+
 	static function get_isAndroid() {
 		#if android
 		return true;
@@ -240,7 +251,7 @@ class System {
 		CACHED_NAME = name;
 		return name;
 	}
-	
+
 	public static function exit() {
 		Sys.exit(0);
 	}
@@ -248,11 +259,11 @@ class System {
 	static function get_isWindowed() {
 		return true;
 	}
-	
+
 	static function get_isTouch() {
 		return false;
 	}
-	
+
 	static function get_width() {
 		var Cap = flash.system.Capabilities;
 		return isWindowed ? flash.Lib.current.stage.stageWidth : Std.int(Cap.screenResolutionX > Cap.screenResolutionY ? Cap.screenResolutionX : Cap.screenResolutionY);
@@ -264,5 +275,5 @@ class System {
 	}
 
 	#end
-	
+
 }

+ 196 - 151
hxd/fmt/fbx/Library.hx

@@ -21,13 +21,23 @@ private class AnimCurve {
 	}
 }
 
+private typedef TmpObject = {
+	var model : FbxNode;
+	var parent : TmpObject;
+	var isJoint : Bool;
+	var isMesh : Bool;
+	var childs : Array<TmpObject>;
+	@:optional var obj : h3d.scene.Object;
+	@:optional var joint : h3d.anim.Skin.Joint;
+}
+
 class DefaultMatrixes {
 	public var trans : Null<Point>;
 	public var scale : Null<Point>;
 	public var rotate : Null<Point>;
 	public var preRot : Null<Point>;
 	public var wasRemoved : Null<Int>;
-	
+
 	public function new() {
 	}
 
@@ -42,7 +52,7 @@ class DefaultMatrixes {
 		m._31 *= -1;
 		m._41 *= -1;
 	}
-	
+
 	public function toMatrix(leftHand) {
 		var m = new h3d.Matrix();
 		m.identity();
@@ -53,7 +63,7 @@ class DefaultMatrixes {
 		if( leftHand ) rightHandToLeft(m);
 		return m;
 	}
-	
+
 }
 
 class Library {
@@ -70,52 +80,54 @@ class Library {
 		Allows to prevent some terminal unskinned joints to be removed, for instance if we want to track their position
 	**/
 	public var keepJoints : Map<String,Bool>;
-	
+
 	/**
 		Allows to skip some objects from being processed as if they were not part of the FBX
 	**/
 	public var skipObjects : Map<String,Bool>;
-	
+
 	/**
 		Set how many bones per vertex should be created in skin data in makeObject(). Default is 3
 	**/
 	public var bonesPerVertex = 3;
-	
+
 	/**
 		If there are too many bones, the model will be split in separate render passes.
 	**/
 	public var maxBonesPerSkin = 34;
-	
+
 	/**
 		Consider unskinned joints to be simple objects
 	**/
 	public var unskinnedJointsAsObjects : Bool;
-	
+
+	public var allowVertexColor : Bool = true;
+
 	public function new() {
 		root = { name : "Root", props : [], childs : [] };
 		keepJoints = new Map();
 		skipObjects = new Map();
 		reset();
 	}
-	
+
 	function reset() {
 		ids = new Map();
 		connect = new Map();
 		invConnect = new Map();
 		defaultModelMatrixes = new Map();
 	}
-	
+
 	public function loadTextFile( data : String ) {
 		load(Parser.parse(data));
 	}
-	
+
 	public function load( root : FbxNode ) {
 		reset();
 		this.root = root;
 		for( c in root.childs )
 			init(c);
 	}
-	
+
 	public function loadXtra( data : String ) {
 		var xml = Xml.parse(data).firstElement();
 		if( uvAnims == null ) uvAnims = new Map();
@@ -125,7 +137,7 @@ class Library {
 			uvAnims.set(obj, frames);
 		}
 	}
-	
+
 	function convertPoints( a : Array<Float> ) {
 		var p = 0;
 		for( i in 0...Std.int(a.length / 3) ) {
@@ -133,7 +145,7 @@ class Library {
 			p += 3;
 		}
 	}
-	
+
 	public function leftHandConvert() {
 		if( leftHand ) return;
 		leftHand = true;
@@ -144,7 +156,7 @@ class Library {
 				convertPoints(v.getFloats());
 		}
 	}
-	
+
 	function init( n : FbxNode ) {
 		switch( n.name ) {
 		case "Connections":
@@ -153,7 +165,7 @@ class Library {
 					continue;
 				var child = c.props[1].toInt();
 				var parent = c.props[2].toInt();
-				
+
 				var c = connect.get(parent);
 				if( c == null ) {
 					c = [];
@@ -163,7 +175,7 @@ class Library {
 
 				if( parent == 0 )
 					continue;
-								
+
 				var c = invConnect.get(child);
 				if( c == null ) {
 					c = [];
@@ -177,7 +189,7 @@ class Library {
 		default:
 		}
 	}
-	
+
 	public function getGeometry( name : String = "" ) {
 		var geom = null;
 		for( g in root.getAll("Objects.Geometry") )
@@ -207,7 +219,7 @@ class Library {
 			throw "Missing " + node.getName() + " " + nodeName + " child";
 		return c[0];
 	}
-	
+
 	public function getChilds( node : FbxNode, ?nodeName : String ) {
 		var c = connect.get(node.getId());
 		var subs = [];
@@ -233,11 +245,11 @@ class Library {
 			}
 		return pl;
 	}
-	
+
 	public function getRoot() {
 		return root;
 	}
-	
+
 	public function ignoreMissingObject( name : String ) {
 		var def = defaultModelMatrixes.get(name);
 		if( def == null ) {
@@ -278,8 +290,8 @@ class Library {
 		}
 		return c;
 	}
-	
-	
+
+
 	public function mergeModels( modelNames : Array<String> ) {
 		if( modelNames.length == 0 )
 			return;
@@ -315,10 +327,10 @@ class Library {
 				}
 				mindex.push(idx);
 			}
-			
+
 			// merge geometry
 			geom.merge(geom2, mindex);
-			
+
 			// merge skinning
 			var def2 = getChild(geom2.getRoot(), "Deformer", true);
 			if( def2 != null ) {
@@ -334,13 +346,13 @@ class Library {
 
 					if( prevDef != null )
 						removeLink(subDef, subModel);
-					
+
 					var idx = subDef.get("Indexes", true);
 					if( idx == null ) continue;
 
-					
 					if( prevDef == null ) {
-						addLink(def2, subDef);
+						addLink(def, subDef);
+						removeLink(def2, subDef);
 						subDefs.push(subDef);
 						var idx = idx.getInts();
 						for( i in 0...idx.length )
@@ -364,15 +376,15 @@ class Library {
 		connect.get(pid).push(nid);
 		invConnect.get(nid).push(pid);
 	}
-	
+
 	function removeLink( parent : FbxNode, child : FbxNode ) {
 		var pid = parent.getId();
 		var nid = child.getId();
 		connect.get(pid).remove(nid);
 		invConnect.get(nid).remove(pid);
 	}
-	
-	
+
+
 	public function loadAnimation( mode : AnimationMode, ?animName : String, ?root : FbxNode, ?lib : Library ) : h3d.anim.Animation {
 		if( lib != null ) {
 			lib.defaultModelMatrixes = defaultModelMatrixes;
@@ -405,7 +417,7 @@ class Library {
 		var P1 = new Point(1, 1, 1);
 		var F = Math.PI / 180;
 		var allTimes = new Map();
-		
+
 		if( animNode != null ) for( cn in getChilds(animNode, "AnimationCurveNode") ) {
 			var model = getParent(cn, "Model");
 			var c = getObjectCurve(curves, model, cn.getName(), animName);
@@ -504,7 +516,7 @@ class Library {
 					allTimes.set(Std.int(f.t / 200000), f.t);
 			}
 		}
-		
+
 		var allTimes = [for( a in allTimes ) a];
 		allTimes.sort(sortDistinctFloats);
 		var maxTime = allTimes[allTimes.length - 1];
@@ -518,11 +530,11 @@ class Library {
 		}
 		var numFrames = maxTime == 0 ? 1 : 1 + Std.int((maxTime - allTimes[0]) / minDT);
 		var sampling = 15.0 / (minDT / 3079077200); // this is the DT value we get from Max when using 15 FPS export
-		
+
 		switch( mode ) {
 		case FrameAnim:
 			var anim = new h3d.anim.FrameAnimation(animName, numFrames, sampling);
-		
+
 			for( c in curves ) {
 				var frames = c.t == null && c.r == null && c.s == null ? null : new haxe.ds.Vector(numFrames);
 				var alpha = c.a == null ? null : new haxe.ds.Vector(numFrames);
@@ -576,7 +588,7 @@ class Library {
 								m.rotate(def.rotate.x, def.rotate.y, def.rotate.z);
 						} else
 							m.rotate(crx[rp-1] * F, cry[rp-1] * F, crz[rp-1] * F);
-							
+
 						if( def.preRot != null )
 							m.rotate(def.preRot.x, def.preRot.y, def.preRot.z);
 
@@ -588,7 +600,7 @@ class Library {
 
 						if( leftHand )
 							DefaultMatrixes.rightHandToLeft(m);
-							
+
 						curMat = m;
 					}
 					if( frames != null )
@@ -605,7 +617,7 @@ class Library {
 						uvs[(f<<1)|1] = cuv[uvp - 1].v;
 					}
 				}
-				
+
 				if( frames != null )
 					anim.addCurve(c.object, frames);
 				if( alpha != null )
@@ -614,9 +626,9 @@ class Library {
 					anim.addUVCurve(c.object, uvs);
 			}
 			return anim;
-			
+
 		case LinearAnim:
-			
+
 			var anim = new h3d.anim.LinearAnimation(animName, numFrames, sampling);
 			var q = new h3d.Quat(), q2 = new h3d.Quat();
 
@@ -684,12 +696,12 @@ class Library {
 								q.identity();
 						} else
 							q.initRotate(crx[rp-1] * F, cry[rp-1] * F, crz[rp-1] * F);
-							
+
 						if( def.preRot != null ) {
 							q2.initRotate(def.preRot.x, def.preRot.y, def.preRot.z);
 							q.multiply(q,q2);
 						}
-						
+
 						f.qx = q.x;
 						f.qy = q.y;
 						f.qz = q.z;
@@ -716,7 +728,7 @@ class Library {
 							f.qy *= -1;
 							f.qz *= -1;
 						}
-						
+
 						curFrame = f;
 					}
 					if( frames != null )
@@ -733,7 +745,7 @@ class Library {
 						uvs[(f<<1)|1] = cuv[uvp - 1].v;
 					}
 				}
-				
+
 				if( frames != null )
 					anim.addCurve(c.object, frames, c.r != null || def.rotate != null, c.s != null || def.scale != null);
 				if( alpha != null )
@@ -742,14 +754,14 @@ class Library {
 					anim.addUVCurve(c.object, uvs);
 			}
 			return anim;
-			
+
 		}
 	}
-	
+
 	function sortDistinctFloats( a : Float, b : Float ) {
 		return if( a > b ) 1 else -1;
 	}
-	
+
 	function isNullJoint( model : FbxNode ) {
 		if( getParent(model, "Deformer", true) != null )
 			return false;
@@ -762,15 +774,21 @@ class Library {
 		return true;
 	}
 
+	function getModelPath( model : FbxNode ) {
+		var parent = getParent(model, "Model", true);
+		var name = model.getName();
+		if( parent == null )
+			return name;
+		return getModelPath(parent) + "." + name;
+	}
+
 	public function makeObject( ?textureLoader : String -> FbxNode -> h3d.mat.MeshMaterial ) : h3d.scene.Object {
 		var scene = new h3d.scene.Object();
-		var hobjects = new Map();
 		var hgeom = new Map();
-		var objects = new Array();
-		var hjoints = new Map();
-		var joints = new Array();
 		var hskins = new Map();
-		
+		var objects = new Array<TmpObject>();
+		var hobjects = new Map<Int, TmpObject>();
+
 		if( textureLoader == null ) {
 			var tmpTex = null;
 			textureLoader = function(_,_) {
@@ -779,46 +797,73 @@ class Library {
 				return new h3d.mat.MeshMaterial(tmpTex);
 			}
 		}
-		// create all models
+
+		// init objects
+		var oroot : TmpObject = { model : null, isJoint : false, isMesh : false, childs : [], parent : null, obj : scene };
+		hobjects.set(0, oroot);
 		for( model in root.getAll("Objects.Model") ) {
-			var o : h3d.scene.Object;
-			var name = model.getName();
-			if( skipObjects.get(name) )
+			if( skipObjects.get(model.getName()) )
 				continue;
 			var mtype = model.getType();
-			if( unskinnedJointsAsObjects && mtype == "LimbNode" && isNullJoint(model) )
-				mtype = "Null";
-			switch( mtype ) {
-			case "Null", "Root", "Camera":
+			var isJoint = mtype == "LimbNode" && (!unskinnedJointsAsObjects || !isNullJoint(model));
+			var o : TmpObject = { model : model, isJoint : isJoint, isMesh : mtype == "Mesh", parent : null, childs : [], obj : null };
+			hobjects.set(model.getId(), o);
+			objects.push(o);
+		}
+
+		// build hierarchy
+		for( o in objects ) {
+			var p = getParent(o.model, "Model", true);
+			var pid = if( p == null ) 0 else p.getId();
+			var op = hobjects.get(pid);
+			if( op == null ) op = oroot; // if parent has been removed
+			op.childs.push(o);
+			o.parent = op;
+		}
+
+		// propagates joint flags
+		var changed = true;
+		while( changed ) {
+			changed = false;
+			for( o in objects ) {
+				if( o.isJoint || o.isMesh ) continue;
+				if( o.parent.isJoint ) {
+					o.isJoint = true;
+					changed = true;
+					continue;
+				}
 				var hasJoint = false;
-				for( c in getChilds(model, "Model") )
-					if( c.getType() == "LimbNode" ) {
-						if( unskinnedJointsAsObjects && isNullJoint(c) ) continue;
+				for( c in o.childs )
+					if( c.isJoint ) {
 						hasJoint = true;
 						break;
 					}
 				if( hasJoint )
-					o = new h3d.scene.Skin(null, null, scene);
-				else
-					o = new h3d.scene.Object(scene);
-			case "LimbNode":
-				var j = new h3d.anim.Skin.Joint();
-				getDefaultMatrixes(model); // store for later usage in animation
-				j.index = model.getId();
-				j.name = model.getName();
-				hjoints.set(j.index, j);
-				joints.push({ model : model, joint : j });
-				continue;
-			case "Mesh":
+					for( c in o.parent.childs )
+						if( c.isJoint ) {
+							o.isJoint = true;
+							changed = true;
+							break;
+						}
+			}
+		}
+
+
+		// create all models
+		for( o in objects ) {
+			var name = o.model.getName();
+			if( o.isMesh ) {
+				if( o.isJoint )
+					throw "Model " + getModelPath(o.model) + " was tagged as joint but is mesh";
 				// load geometry
-				var g = getChild(model, "Geometry");
+				var g = getChild(o.model, "Geometry");
 				var prim = hgeom.get(g.getId());
 				if( prim == null ) {
 					prim = new h3d.prim.FBXModel(new Geometry(this, g));
 					hgeom.set(g.getId(), prim);
 				}
 				// load materials
-				var mats = getChilds(model, "Material");
+				var mats = getChilds(o.model, "Material");
 				var tmats = [];
 				var vcolor = prim.geom.getColors() != null;
 				var lastAdded = 0;
@@ -829,7 +874,7 @@ class Library {
 						continue;
 					}
 					var mat = textureLoader(tex.get("FileName").props[0].toString(),mat);
-					if( vcolor ) {
+					if( vcolor && allowVertexColor ) {
 						throw "TODO";
 						//mat.hasVertexColor = true;
 					}
@@ -842,92 +887,92 @@ class Library {
 					tmats.push(new h3d.mat.MeshMaterial(h3d.mat.Texture.fromColor(0xFFFF00FF)));
 				// create object
 				if( tmats.length == 1 )
-					o = new h3d.scene.Mesh(prim, tmats[0], scene);
+					o.obj = new h3d.scene.Mesh(prim, tmats[0], scene);
 				else {
 					prim.multiMaterial = true;
-					o = new h3d.scene.MultiMaterial(prim, tmats, scene);
+					o.obj = new h3d.scene.MultiMaterial(prim, tmats, scene);
 				}
-			case type:
-				throw "Unknown model type " + type+" for "+model.getName();
+			} else if( o.isJoint ) {
+				var j = new h3d.anim.Skin.Joint();
+				getDefaultMatrixes(o.model); // store for later usage in animation
+				j.index = o.model.getId();
+				j.name = o.model.getName();
+				o.joint = j;
+				continue;
+			} else {
+				var hasJoint = false;
+				for( c in o.childs )
+					if( c.isJoint ) {
+						hasJoint = true;
+						break;
+					}
+				if( hasJoint )
+					o.obj = new h3d.scene.Skin(null);
+				else
+					o.obj = new h3d.scene.Object();
 			}
-			o.name = name;
-			var m = getDefaultMatrixes(model);
+			o.obj.name = name;
+			var m = getDefaultMatrixes(o.model);
 			if( m.trans != null || m.rotate != null || m.scale != null || m.preRot != null )
-				o.defaultTransform = m.toMatrix(leftHand);
-			hobjects.set(model.getId(), o);
-			objects.push( { model : model, obj : o } );
+				o.obj.defaultTransform = m.toMatrix(leftHand);
 		}
-		// rebuild joints hierarchy
-		for( j in joints ) {
-			var p = getParent(j.model, "Model");
-			var jparent = hjoints.get(p.getId());
-			if( jparent != null ) {
-				jparent.subs.push(j.joint);
-				j.joint.parent = jparent;
-			} else if( p.getType() != "Root" && p.getType() != "Null" )
-				throw "Parent joint not found " + p.getName();
-		}
-		// rebuild model hierarchy and additional inits
+		// rebuild scene hierarchy
 		for( o in objects ) {
-			var rootJoints = [];
-			for( sub in getChilds(o.model, "Model") ) {
-				var sobj = hobjects.get(sub.getId());
-				if( sobj == null ) {
-					if( skipObjects.get(sub.getName()) )
-						continue;
-					if( sub.getType() == "LimbNode" ) {
-						var j = hjoints.get(sub.getId());
-						if( j == null ) throw "Missing sub joint " + sub.getName();
-						rootJoints.push(j);
-						continue;
-					}
-					throw "Missing sub " + sub.getName();
-				}
-				o.obj.addChild(sobj);
-			}
-			if( rootJoints.length != 0 ) {
-				if( !Std.is(o.obj,h3d.scene.Skin) )
-					throw o.obj.name + ":" + o.model.getType() + " should be a skin";
-				var skin : h3d.scene.Skin = cast o.obj;
-				var skinData = createSkin(hskins, hgeom, rootJoints, bonesPerVertex);
-				// if we have a skinned object, remove it (only keep the skin) and set the material
-				for( osub in objects ) {
-					if( !osub.obj.isMesh() ) continue;
-					var m = osub.obj.toMesh();
-					if( m.primitive != skinData.primitive || m == skin )
-						continue;
-					var mt = Std.instance(m, h3d.scene.MultiMaterial);
-					skin.materials = mt == null ? [m.material] : mt.materials;
-					skin.material = skin.materials[0];
-					m.remove();
-					// ignore key frames for this object
-					defaultModelMatrixes.get(osub.obj.name).wasRemoved = o.model.getId();
+			if( o.isJoint ) {
+				if( o.parent.isJoint ) {
+					o.joint.parent = o.parent.joint;
+					o.parent.joint.subs.push(o.joint);
 				}
-				// set the skin data
-				if( skinData.boundJoints.length > maxBonesPerSkin )
-					skinData.split(maxBonesPerSkin, Std.instance(skinData.primitive,h3d.prim.FBXModel).geom.getIndexes().vidx);
-				skin.setSkinData(skinData);
+			} else {
+				// put it into the first non-joint parent
+				var p = o.parent;
+				while( p.obj == null )
+					p = p.parent;
+				p.obj.addChild(o.obj);
 			}
 		}
-		// make child models follow the bone
-		// /!\ this follow will not be cloned
-		for( j in joints ) {
-			var jobj = null;
-			for( o in getChilds(j.model, "Model") ) {
-				var obj = hobjects.get(o.getId());
-				if( obj != null ) {
-					if( jobj == null ) jobj = scene.getObjectByName(j.joint.name);
-					obj.follow = jobj;
-				}
+		// build skins
+		for( o in objects ) {
+			if( o.isJoint ) continue;
+
+
+			// /!\ currently, childs of joints will work but will not cloned
+			if( o.parent.isJoint )
+				o.obj.follow = scene.getObjectByName(o.parent.joint.name);
+
+			var skin = Std.instance(o.obj, h3d.scene.Skin);
+			if( skin == null ) continue;
+			var rootJoints = [];
+			for( j in o.childs )
+				if( j.isJoint )
+					rootJoints.push(j.joint);
+			var skinData = createSkin(hskins, hgeom, rootJoints, bonesPerVertex);
+			// remove the corresponding Geometry-Model and copy its material
+			for( o2 in objects ) {
+				if( o2.obj == null || o2 == o || !o2.obj.isMesh() ) continue;
+				var m = o2.obj.toMesh();
+				if( m.primitive != skinData.primitive ) continue;
+
+				var mt = Std.instance(m, h3d.scene.MultiMaterial);
+				skin.materials = mt == null ? [m.material] : mt.materials;
+				skin.material = skin.materials[0];
+				m.remove();
+				// ignore key frames for this object
+				defaultModelMatrixes.get(m.name).wasRemoved = o.model.getId();
 			}
+			// set skin after materials
+			if( skinData.boundJoints.length > maxBonesPerSkin )
+				skinData.split(maxBonesPerSkin, Std.instance(skinData.primitive,h3d.prim.FBXModel).geom.getIndexes().vidx);
+			skin.setSkinData(skinData);
 		}
+
 		return scene.numChildren == 1 ? scene.getChildAt(0) : scene;
 	}
-	
+
 	function keepJoint( j : h3d.anim.Skin.Joint ) {
 		return keepJoints.get(j.name);
 	}
-	
+
 	function createSkin( hskins : Map<Int,h3d.anim.Skin>, hgeom : Map<Int,h3d.prim.FBXModel>, rootJoints : Array<h3d.anim.Skin.Joint>, bonesPerVertex ) {
 		var allJoints = [];
 		function collectJoints(j:h3d.anim.Skin.Joint) {
@@ -946,7 +991,7 @@ class Library {
 			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) )
@@ -976,7 +1021,7 @@ class Library {
 			}
 			j.transPos = h3d.Matrix.L(subDef.get("Transform").getFloats());
 			if( leftHand ) DefaultMatrixes.rightHandToLeft(j.transPos);
-			
+
 			var weights = subDef.getAll("Weights");
 			if( weights.length > 0 ) {
 				var weights = weights[0].getFloats();
@@ -998,7 +1043,7 @@ class Library {
 		skin.initWeights();
 		return skin;
 	}
-	
+
 	function getDefaultMatrixes( model : FbxNode ) {
 		var d = new DefaultMatrixes();
 		var F = Math.PI / 180;
@@ -1019,5 +1064,5 @@ class Library {
 		defaultModelMatrixes.set(model.getName(), d);
 		return d;
 	}
-	
+
 }

+ 25 - 0
hxd/impl/Memory.hx

@@ -31,6 +31,31 @@ class MemoryReader {
 		#end
 	}
 
+	public inline function float( addr : Int ) : Float {
+		#if flash
+		return flash.Memory.getFloat(addr);
+		#else
+		throw "TODO";
+		return 0.;
+		#end
+	}
+
+	public inline function wfloat( addr : Int, v : Float ) : Void {
+		#if flash
+		flash.Memory.setFloat(addr, v);
+		#else
+		throw "TODO";
+		#end
+	}
+
+	public inline function wdouble( addr : Int, v : Float ) : Void {
+		#if flash
+		flash.Memory.setDouble(addr, v);
+		#else
+		throw "TODO";
+		#end
+	}
+	
 	public inline function i32( addr : Int ) : Int {
 		#if flash
 		return flash.Memory.getI32(addr);

+ 8 - 8
hxd/res/FileEntry.hx

@@ -1,33 +1,33 @@
 package hxd.res;
 
 class FileEntry {
-	
+
 	public var name(default, null) : String;
 	public var path(get, never) : String;
 	public var extension(get, never) : String;
 	public var size(get, never) : Int;
 	public var isDirectory(get, never) : Bool;
 	public var isAvailable(get, never) : Bool;
-	
+
 	// first four bytes of the file
 	public function getSign() : Int return 0;
-	
+
 	public function getBytes() : haxe.io.Bytes return null;
-	
+
 	public function open() { }
 	public function skip( nbytes : Int ) { }
 	public function readByte() : Int return 0;
 	public function read( out : haxe.io.Bytes, pos : Int, size : Int ) {}
 	public function close() {}
-	
+
 	public function load( ?onReady : Void -> Void ) : Void {}
 	public function loadBitmap( onLoaded : LoadedBitmap -> Void ) : Void {}
-
+	public function watch( onChanged : Null<Void -> Void> ) { }
 	public function exists( name : String ) : Bool return false;
 	public function get( name : String ) : FileEntry return null;
-	
+
 	public function iterator() : hxd.impl.ArrayIterator<FileEntry> return null;
-	
+
 	function get_isAvailable() return true;
 	function get_isDirectory() return false;
 	function get_size() return 0;

+ 10 - 10
hxd/res/FontBuilder.hx

@@ -17,17 +17,17 @@ class FontBuilder {
 	var font : h2d.Font;
 	var options : FontBuildOptions;
 	var innerTex : h3d.mat.Texture;
-	
+
 	function new(name, size, opt) {
 		this.font = new h2d.Font(name, size);
 		this.options = opt == null ? { } : opt;
 		if( options.antiAliasing == null ) options.antiAliasing = true;
 		if( options.chars == null ) options.chars = hxd.Charset.DEFAULT_CHARS;
 	}
-	
+
 	#if flash
 
-	function build() {
+	function build() : h2d.Font {
 		font.lineHeight = 0;
 		var tf = new flash.text.TextField();
 		var fmt = tf.defaultTextFormat;
@@ -100,10 +100,10 @@ class FontBuilder {
 				x += w + 1;
 			}
 		} while( bmp == null );
-		
+
 		var pixels = hxd.BitmapData.fromNative(bmp).getPixels();
 		bmp.dispose();
-		
+
 		// let's remove alpha premult (all pixels should be white with alpha)
 		pixels.convert(BGRA);
 		var r = hxd.impl.Memory.select(pixels.bytes);
@@ -118,7 +118,7 @@ class FontBuilder {
 			}
 		}
 		r.end();
-		
+
 		if( innerTex == null ) {
 			innerTex = h3d.mat.Texture.fromPixels(pixels);
 			font.tile = h2d.Tile.fromTexture(innerTex);
@@ -219,14 +219,14 @@ class FontBuilder {
 
 	
 	#else
-	
+
 	function build() {
 		throw "Font building not supported on this platform";
 		return null;
 	}
-	
+
 	#end
-	
+
 	public static function getFont( name : String, size : Int, ?options : FontBuildOptions ) {
 		var key = name + "#" + size;
 		var f = FONTS.get(key);
@@ -236,7 +236,7 @@ class FontBuilder {
 		FONTS.set(key, f);
 		return f;
 	}
-	
+
 	public static function dispose() {
 		for( f in FONTS )
 			f.dispose();

+ 45 - 10
hxd/res/Image.hx

@@ -1,10 +1,16 @@
 package hxd.res;
 
 class Image extends Resource {
-	
+
+	/**
+		Specify if we will automatically convert non-power-of-two textures to power-of-two.
+	**/
+	public static var ALLOW_NPOT = #if flash11_8 true #else false #end;
+	public static var DEFAULT_FILTER : h3d.mat.Data.Filter = Linear;
+
 	var tex : h3d.mat.Texture;
 	var inf : { width : Int, height : Int, isPNG : Bool };
-	
+
 	public function isPNG() {
 		getSize();
 		return inf.isPNG;
@@ -58,7 +64,7 @@ class Image extends Resource {
 		inf = { width : width, height : height, isPNG : isPNG };
 		return inf;
 	}
-	
+
 	public function getPixels() {
 		getSize();
 		if( inf.isPNG ) {
@@ -73,7 +79,7 @@ class Image extends Resource {
 			return new Pixels(p.width,p.height,p.pixels, BGRA);
 		}
 	}
-	
+
 	public function toBitmap() : hxd.BitmapData {
 		getSize();
 		var bmp = new hxd.BitmapData(inf.width, inf.height);
@@ -82,16 +88,29 @@ class Image extends Resource {
 		pixels.dispose();
 		return bmp;
 	}
-	
+
+	function watchCallb() {
+		var w = inf.width, h = inf.height;
+		inf = null;
+		var s = getSize();
+		if( w != s.width || h != s.height )
+			tex.resize(w, h);
+		tex.realloc = null;
+		loadTexture();
+	}
+
 	function loadTexture() {
 		if( inf.isPNG ) {
 			function load() {
 				// immediately loading the PNG is faster than going through loadBitmap
 				tex.alloc();
 				var pixels = getPixels();
+				if( pixels.width != tex.width || pixels.height != tex.height )
+					pixels.makeSquare();
 				tex.uploadPixels(pixels);
 				pixels.dispose();
 				tex.realloc = loadTexture;
+				watch(watchCallb);
 			}
 			if( entry.isAvailable )
 				load();
@@ -102,26 +121,42 @@ class Image extends Resource {
 			entry.loadBitmap(function(bmp) {
 				var bmp = bmp.toBitmap();
 				tex.alloc();
-				tex.uploadBitmap(bmp);
+				if( bmp.width != tex.width || bmp.height != tex.height ) {
+					var pixels = bmp.getPixels();
+					pixels.makeSquare();
+					tex.uploadPixels(pixels);
+					pixels.dispose();
+				} else
+					tex.uploadBitmap(bmp);
 				bmp.dispose();
 				tex.realloc = loadTexture;
+				watch(watchCallb);
 			});
 		}
 	}
-	
+
 	public function toTexture() : h3d.mat.Texture {
 		if( tex != null )
 			return tex;
 		getSize();
-		tex = new h3d.mat.Texture(inf.width, inf.height, [NoAlloc]);
+		var width = inf.width, height = inf.height;
+		if( !ALLOW_NPOT ) {
+			var tw = 1, th = 1;
+			while( tw < width ) tw <<= 1;
+			while( th < height ) th <<= 1;
+			width = tw;
+			height = th;
+		}
+		tex = new h3d.mat.Texture(width, height, [NoAlloc]);
+		if( DEFAULT_FILTER != Linear ) tex.filter = DEFAULT_FILTER;
 		tex.setName(entry.path);
 		loadTexture();
 		return tex;
 	}
-	
+
 	public function toTile() : h2d.Tile {
 		var size = getSize();
 		return h2d.Tile.fromTexture(toTexture()).sub(0, 0, size.width, size.height);
 	}
-	
+
 }

+ 65 - 25
hxd/res/LocalFileSystem.hx

@@ -5,7 +5,7 @@ package hxd.res;
 @:allow(hxd.res.LocalFileSystem)
 @:access(hxd.res.LocalFileSystem)
 private class LocalEntry extends FileEntry {
-	
+
 	var fs : LocalFileSystem;
 	var relPath : String;
 	#if air3
@@ -24,9 +24,9 @@ private class LocalEntry extends FileEntry {
 		if( fs.createXBX && extension == "fbx" )
 			convertToXBX();
 	}
-	
+
 	static var INVALID_CHARS = ~/[^A-Za-z0-9_]/g;
-	
+
 	function convertToXBX() {
 		function getXBX() {
 			var fbx = null;
@@ -85,7 +85,7 @@ private class LocalEntry extends FileEntry {
 		return sys.io.File.getBytes(file);
 		#end
 	}
-	
+
 	override function open() {
 		#if air3
 		if( fread != null )
@@ -101,7 +101,7 @@ private class LocalEntry extends FileEntry {
 			fread = sys.io.File.read(file);
 		#end
 	}
-	
+
 	override function skip(nbytes:Int) {
 		#if air3
 		fread.position += nbytes;
@@ -109,7 +109,7 @@ private class LocalEntry extends FileEntry {
 		fread.seek(nbytes, SeekCur);
 		#end
 	}
-	
+
 	override function readByte() {
 		#if air3
 		return fread.readUnsignedByte();
@@ -117,7 +117,7 @@ private class LocalEntry extends FileEntry {
 		return fread.readByte();
 		#end
 	}
-	
+
 	override function read( out : haxe.io.Bytes, pos : Int, size : Int ) : Void {
 		#if air3
 		fread.readBytes(out.getData(), pos, size);
@@ -139,7 +139,7 @@ private class LocalEntry extends FileEntry {
 		}
 		#end
 	}
-	
+
 	override function get_isDirectory() {
 		#if air3
 		return file.isDirectory;
@@ -148,7 +148,7 @@ private class LocalEntry extends FileEntry {
 		return false;
 		#end
 	}
-	
+
 	override function load( ?onReady : Void -> Void ) : Void {
 		#if air3
 		if( onReady != null ) haxe.Timer.delay(onReady, 1);
@@ -156,7 +156,7 @@ private class LocalEntry extends FileEntry {
 		throw "TODO";
 		#end
 	}
-	
+
 	override function loadBitmap( onLoaded : hxd.BitmapData -> Void ) : Void {
 		#if flash
 		var loader = new flash.display.Loader();
@@ -173,19 +173,19 @@ private class LocalEntry extends FileEntry {
 		throw "TODO";
 		#end
 	}
-	
+
 	override function get_path() {
 		return relPath == null ? "<root>" : relPath;
 	}
-	
+
 	override function exists( name : String ) {
 		return fs.exists(relPath == null ? name : relPath + "/" + name);
 	}
-	
+
 	override function get( name : String ) {
 		return fs.get(relPath == null ? name : relPath + "/" + name);
 	}
-	
+
 	override function get_size() {
 		#if air3
 		return Std.int(file.size);
@@ -218,16 +218,56 @@ private class LocalEntry extends FileEntry {
 		return new hxd.impl.ArrayIterator(arr);
 		#end
 	}
-	
+
+	#if air3
+
+	var watchCallback : Void -> Void;
+	var watchTime : Float;
+	static var WATCH_LIST : Array<LocalEntry> = null;
+
+	static function checkFiles(_) {
+		for( w in WATCH_LIST ) {
+			var t = try w.file.modificationDate.getTime() catch( e : Dynamic ) -1;
+			if( t != w.watchTime ) {
+				// check we can read (might be deleted/renamed/currently writing)
+				try { w.close(); w.open(); w.close(); } catch( e : Dynamic ) continue;
+				w.watchTime = t;
+				w.watchCallback();
+			}
+		}
+	}
+
+	override function watch( onChanged : Null < Void -> Void > ) {
+		if( onChanged == null ) {
+			if( watchCallback != null ) {
+				WATCH_LIST.remove(this);
+				watchCallback = null;
+			}
+			return;
+		}
+		if( watchCallback == null ) {
+			if( WATCH_LIST == null ) {
+				WATCH_LIST = [];
+				flash.Lib.current.stage.addEventListener(flash.events.Event.ENTER_FRAME, checkFiles);
+			}
+			WATCH_LIST.push(this);
+		}
+		watchTime = file.modificationDate.getTime();
+		watchCallback = onChanged;
+		return;
+	}
+
+	#end
+
 }
 
 class LocalFileSystem implements FileSystem {
-	
+
 	var root : FileEntry;
 	public var baseDir(default,null) : String;
 	public var createXBX : Bool;
 	public var tmpDir : String;
-	
+
 	public function new( dir : String ) {
 		baseDir = dir;
 		#if air3
@@ -248,11 +288,11 @@ class LocalFileSystem implements FileSystem {
 		#end
 		tmpDir = baseDir + ".tmp/";
 	}
-	
+
 	public dynamic function xbxFilter( entry : FileEntry, fbx : h3d.fbx.Data.FbxNode ) : h3d.fbx.Data.FbxNode {
 		return fbx;
 	}
-	
+
 	public function getRoot() : FileEntry {
 		return root;
 	}
@@ -272,7 +312,7 @@ class LocalFileSystem implements FileSystem {
 		return f;
 		#end
 	}
-	
+
 	public function exists( path : String ) {
 		#if air3
 		var f = open(path);
@@ -282,7 +322,7 @@ class LocalFileSystem implements FileSystem {
 		return f != null && sys.FileSystem.exists(f);
 		#end
 	}
-	
+
 	public function get( path : String ) {
 		#if air3
 		var f = open(path);
@@ -296,7 +336,7 @@ class LocalFileSystem implements FileSystem {
 		return new LocalEntry(this, path.split("/").pop(), path, f);
 		#end
 	}
-	
+
 }
 
 #else
@@ -304,7 +344,7 @@ class LocalFileSystem implements FileSystem {
 class LocalFileSystem implements FileSystem {
 
 	public var baseDir(default,null) : String;
-	
+
 	public function new( dir : String ) {
 		#if flash
 		if( flash.system.Capabilities.playerType == "Desktop" )
@@ -312,11 +352,11 @@ class LocalFileSystem implements FileSystem {
 		#end
 		throw "Local file system is not supported for this platform";
 	}
-	
+
 	public function exists(path:String) {
 		return false;
 	}
-	
+
 	public function get(path:String) : FileEntry {
 		return null;
 	}

+ 10 - 5
hxd/res/Resource.hx

@@ -1,21 +1,26 @@
 package hxd.res;
 
 class Resource {
-	
+
+	public static var LIVE_UPDATE = #if debug true #else false #end;
+
 	public var name(get, never) : String;
 	public var entry(default,null) : FileEntry;
-	
+
 	public function new(entry) {
 		this.entry = entry;
 	}
-	
+
 	inline function get_name() {
 		return entry.name;
 	}
-	
+
 	function toString() {
 		return entry.path;
 	}
 
-	
+	public function watch( onChanged : Null < Void -> Void > ) {
+		if( LIVE_UPDATE	) entry.watch(onChanged);
+	}
+
 }

+ 1 - 1
hxd/res/Sound.hx

@@ -130,7 +130,7 @@ class Sound extends Resource {
 			bytesPosition = 0;
 			snd.addEventListener(flash.events.SampleDataEvent.SAMPLE_DATA, onWavSample);
 			
-		case 255: // MP3
+		case 255, 'I'.code: // MP3 (or ID3)
 			
 			snd.loadCompressedDataFromByteArray(bytes.getData(), bytes.length);
 			if( loop ) {

+ 49 - 0
samples/bounds/Bounds.hx

@@ -0,0 +1,49 @@
+class Bounds extends hxd.App {
+
+	var boxes : Array<h2d.Bitmap>;
+	var g : h2d.Graphics;
+	var colors = [0xFF0000 , 0x00FF00 , 0x0000FF, 0xFF00FF];
+
+	override function init() {
+		boxes = [];
+		g = new h2d.Graphics(s2d);
+		for( i in 0...colors.length ) {
+			var size = Std.int(200 / (i + 4));
+			var c = colors[i];
+			var b = new h2d.Bitmap(h2d.Tile.fromColor(c | 0x80000000, size, size).sub(0, 0, size, size, -Std.random(size), -Std.random(size)), i == 0 ? s2d : boxes[i - 1]);
+			b.addChild(new h2d.Bitmap(h2d.Tile.fromColor(0xFFFFFFFF, 8, 8).sub(0, 0, 8, 8, -4, -4)));
+			if( i == 0 ) {
+				b.x = s2d.width >> 1;
+				b.y = s2d.height >> 1;
+			} else {
+				b.x = Std.random(50) - 25;
+				b.y = Std.random(50) - 25;
+				if( b.x < 0 ) b.x -= size * 1.5 else b.x += size * 1.5;
+				if( b.y < 0 ) b.y -= size * 1.5 else b.y += size * 1.5;
+			}
+			b.scale(1.2 - i * 0.1);
+			boxes.push(b);
+		}
+		var tf = new h2d.Text(hxd.res.FontBuilder.getFont("Verdana", 16), boxes[0]);
+		tf.text = "Some quite long rotating text";
+		tf.x = -5;
+		tf.y = 15;
+		tf.filter = true;
+	}
+
+	override function update(dt:Float) {
+		g.clear();
+		for( i in 0...boxes.length ) {
+			var b = boxes[i];
+			b.rotate( (i + 1) * dt * 0.01 );
+			var b = b.getBounds();
+			g.beginFill((colors[i]>>2)&0x3F3F3F);
+			g.drawRect(b.x, b.y, b.width, b.height);
+		}
+	}
+
+	static function main() {
+		new Bounds();
+	}
+
+}

+ 6 - 0
samples/bounds/bounds.hxml

@@ -0,0 +1,6 @@
+-swf bounds.swf
+-swf-header 800:600:60:FFFFFF
+--flash-strict
+-swf-version 11
+-main Bounds
+-lib h3d

+ 59 - 0
samples/bounds/bounds.hxproj

@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<project version="2">
+  <!-- Output SWF options -->
+  <output>
+    <movie outputType="Application" />
+    <movie input="" />
+    <movie path="bounds.swf" />
+    <movie fps="60" />
+    <movie width="800" />
+    <movie height="600" />
+    <movie version="11" />
+    <movie minorVersion="0" />
+    <movie platform="Flash Player" />
+    <movie background="#FFFFFF" />
+  </output>
+  <!-- Other classes to be compiled into your SWF -->
+  <classpaths>
+    <!-- example: <class path="..." /> -->
+  </classpaths>
+  <!-- Build options -->
+  <build>
+    <option directives="" />
+    <option flashStrict="True" />
+    <option noInlineOnDebug="False" />
+    <option mainClass="Bounds" />
+    <option enabledebug="False" />
+    <option additional="-lib h3d" />
+  </build>
+  <!-- haxelib libraries -->
+  <haxelib>
+    <!-- example: <library name="..." /> -->
+  </haxelib>
+  <!-- Class files to compile (other referenced classes will automatically be included) -->
+  <compileTargets>
+    <!-- example: <compile path="..." /> -->
+  </compileTargets>
+  <!-- Assets to embed into the output SWF -->
+  <library>
+    <!-- example: <asset path="..." id="..." update="..." glyphs="..." mode="..." place="..." sharepoint="..." /> -->
+  </library>
+  <!-- Paths to exclude from the Project Explorer tree -->
+  <hiddenPaths>
+    <hidden path="draw.swf" />
+    <hidden path="draw.hxml" />
+    <hidden path="obj" />
+  </hiddenPaths>
+  <!-- Executed before build -->
+  <preBuildCommand />
+  <!-- Executed after build -->
+  <postBuildCommand alwaysRun="False" />
+  <!-- Other project options -->
+  <options>
+    <option showHiddenPaths="False" />
+    <option testMovie="Default" />
+    <option testMovieCommand="" />
+  </options>
+  <!-- Plugin storage -->
+  <storage />
+</project>