Bladeren bron

added efficient bounds calculus for h2d

ncannasse 11 jaren geleden
bovenliggende
commit
54fd388fdc
13 gewijzigde bestanden met toevoegingen van 469 en 128 verwijderingen
  1. 6 0
      h2d/Anim.hx
  2. 9 4
      h2d/Bitmap.hx
  3. 53 35
      h2d/Graphics.hx
  4. 29 24
      h2d/Interactive.hx
  5. 16 5
      h2d/Mask.hx
  6. 133 26
      h2d/Sprite.hx
  7. 48 14
      h2d/SpriteBatch.hx
  8. 24 5
      h2d/TileColorGroup.hx
  9. 33 15
      h2d/TileGroup.hx
  10. 4 0
      h2d/col/Bounds.hx
  11. 49 0
      samples/bounds/Bounds.hx
  12. 6 0
      samples/bounds/bounds.hxml
  13. 59 0
      samples/bounds/bounds.hxproj

+ 6 - 0
h2d/Anim.hx

@@ -22,6 +22,12 @@ class Anim extends Drawable {
 	public dynamic function onAnimEnd() {
 	public dynamic function onAnimEnd() {
 	}
 	}
 
 
+	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 ) {
 	override function sync( ctx : RenderContext ) {
 		currentFrame += speed * ctx.elapsedTime;
 		currentFrame += speed * ctx.elapsedTime;
 		if( currentFrame < frames.length )
 		if( currentFrame < frames.length )

+ 9 - 4
h2d/Bitmap.hx

@@ -3,18 +3,23 @@ package h2d;
 class Bitmap extends Drawable {
 class Bitmap extends Drawable {
 
 
 	public var tile : Tile;
 	public var tile : Tile;
-	
+
 	public function new( ?tile, ?parent ) {
 	public function new( ?tile, ?parent ) {
 		super(parent);
 		super(parent);
 		this.tile = tile;
 		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 ) {
 	override function draw( ctx : RenderContext ) {
 		drawTile(ctx.engine,tile);
 		drawTile(ctx.engine,tile);
 	}
 	}
-			
+
 	public static function create( bmp : hxd.BitmapData, ?allocPos : h3d.impl.AllocPos ) {
 	public static function create( bmp : hxd.BitmapData, ?allocPos : h3d.impl.AllocPos ) {
 		return new Bitmap(Tile.fromBitmap(bmp,allocPos));
 		return new Bitmap(Tile.fromBitmap(bmp,allocPos));
 	}
 	}
-	
+
 }
 }

+ 53 - 35
h2d/Graphics.hx

@@ -24,9 +24,9 @@ private class GraphicsContent extends h3d.prim.Primitive {
 
 
 	var tmp : hxd.FloatBuffer;
 	var tmp : hxd.FloatBuffer;
 	var index : hxd.IndexBuffer;
 	var index : hxd.IndexBuffer;
-	
+
 	var buffers : Array<{ buf : hxd.FloatBuffer, vbuf : h3d.Buffer, idx : hxd.IndexBuffer, ibuf : h3d.Indexes }>;
 	var buffers : Array<{ buf : hxd.FloatBuffer, vbuf : h3d.Buffer, idx : hxd.IndexBuffer, ibuf : h3d.Indexes }>;
-	
+
 	public function new() {
 	public function new() {
 		buffers = [];
 		buffers = [];
 	}
 	}
@@ -45,7 +45,7 @@ private class GraphicsContent extends h3d.prim.Primitive {
 		tmp.push(b);
 		tmp.push(b);
 		tmp.push(a);
 		tmp.push(a);
 	}
 	}
-	
+
 	public function next() {
 	public function next() {
 		var nvect = tmp.length >> 3;
 		var nvect = tmp.length >> 3;
 		if( nvect < 1 << 15 )
 		if( nvect < 1 << 15 )
@@ -56,7 +56,7 @@ private class GraphicsContent extends h3d.prim.Primitive {
 		super.dispose();
 		super.dispose();
 		return true;
 		return true;
 	}
 	}
-	
+
 	override function alloc( engine : h3d.Engine ) {
 	override function alloc( engine : h3d.Engine ) {
 		if (index.length <= 0) return ;
 		if (index.length <= 0) return ;
 		buffer = h3d.Buffer.ofFloats(tmp, 8);
 		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);
 			if( b.ibuf == null || b.ibuf.isDisposed() ) b.ibuf = h3d.Indexes.alloc(b.idx);
 		}
 		}
 	}
 	}
-	
+
 	override function render( engine : h3d.Engine ) {
 	override function render( engine : h3d.Engine ) {
 		if (index.length <= 0) return ;
 		if (index.length <= 0) return ;
 		if( buffer == null || buffer.isDisposed() ) alloc(engine);
 		if( buffer == null || buffer.isDisposed() ) alloc(engine);
@@ -74,7 +74,7 @@ private class GraphicsContent extends h3d.prim.Primitive {
 			engine.renderIndexed(b.vbuf, b.ibuf);
 			engine.renderIndexed(b.vbuf, b.ibuf);
 		super.render(engine);
 		super.render(engine);
 	}
 	}
-	
+
 	override function dispose() {
 	override function dispose() {
 		for( b in buffers ) {
 		for( b in buffers ) {
 			if( b.vbuf != null ) b.vbuf.dispose();
 			if( b.vbuf != null ) b.vbuf.dispose();
@@ -84,15 +84,15 @@ private class GraphicsContent extends h3d.prim.Primitive {
 		}
 		}
 		super.dispose();
 		super.dispose();
 	}
 	}
-	
-	
+
+
 	public function reset() {
 	public function reset() {
 		dispose();
 		dispose();
 		tmp = new hxd.FloatBuffer();
 		tmp = new hxd.FloatBuffer();
 		index = new hxd.IndexBuffer();
 		index = new hxd.IndexBuffer();
 		buffers = [];
 		buffers = [];
 	}
 	}
-	
+
 }
 }
 
 
 class Graphics extends Drawable {
 class Graphics extends Drawable {
@@ -112,9 +112,14 @@ class Graphics extends Drawable {
 	var lineB : Float;
 	var lineB : Float;
 	var lineA : Float;
 	var lineA : Float;
 	var doFill : Bool;
 	var doFill : Bool;
-	
+
+	var xMin : Float;
+	var yMin : Float;
+	var xMax : Float;
+	var yMax : Float;
+
 	public var tile : h2d.Tile;
 	public var tile : h2d.Tile;
-	
+
 	public function new(?parent) {
 	public function new(?parent) {
 		super(parent);
 		super(parent);
 		content = new GraphicsContent();
 		content = new GraphicsContent();
@@ -122,12 +127,12 @@ class Graphics extends Drawable {
 		tile = h2d.Tile.fromColor(0xFFFFFFFF);
 		tile = h2d.Tile.fromColor(0xFFFFFFFF);
 		clear();
 		clear();
 	}
 	}
-	
+
 	override function onDelete() {
 	override function onDelete() {
 		super.onDelete();
 		super.onDelete();
 		clear();
 		clear();
 	}
 	}
-	
+
 	public function clear() {
 	public function clear() {
 		content.reset();
 		content.reset();
 		pts = [];
 		pts = [];
@@ -135,6 +140,15 @@ class Graphics extends Drawable {
 		linePts = [];
 		linePts = [];
 		pindex = 0;
 		pindex = 0;
 		lineSize = 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> ) {
 	function isConvex( points : Array<GraphicsPoint> ) {
@@ -147,7 +161,7 @@ class Graphics extends Drawable {
 		}
 		}
 		return true;
 		return true;
 	}
 	}
-	
+
 	function flushLine() {
 	function flushLine() {
 		if( linePts.length == 0 )
 		if( linePts.length == 0 )
 			return;
 			return;
@@ -163,15 +177,15 @@ class Graphics extends Drawable {
 			var nx2 = p.y - next.y;
 			var nx2 = p.y - next.y;
 			var ny2 = next.x - p.x;
 			var ny2 = next.x - p.x;
 			var ns2 = Math.invSqrt(nx2 * nx2 + ny2 * ny2);
 			var ns2 = Math.invSqrt(nx2 * nx2 + ny2 * ny2);
-			
+
 			var nx = (nx1 * ns1 + nx2 * ns2) * lineSize * 0.5;
 			var nx = (nx1 * ns1 + nx2 * ns2) * lineSize * 0.5;
 			var ny = (ny1 * ns1 + ny2 * 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);
 			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;
 			var pnext = i == last ? start : pindex + 2;
-			
+
 			content.addIndex(pindex);
 			content.addIndex(pindex);
 			content.addIndex(pindex + 1);
 			content.addIndex(pindex + 1);
 			content.addIndex(pnext);
 			content.addIndex(pnext);
@@ -179,9 +193,9 @@ class Graphics extends Drawable {
 			content.addIndex(pindex + 1);
 			content.addIndex(pindex + 1);
 			content.addIndex(pnext);
 			content.addIndex(pnext);
 			content.addIndex(pnext + 1);
 			content.addIndex(pnext + 1);
-			
+
 			pindex += 2;
 			pindex += 2;
-			
+
 			prev = p;
 			prev = p;
 			p = next;
 			p = next;
 		}
 		}
@@ -189,7 +203,7 @@ class Graphics extends Drawable {
 		if( content.next() )
 		if( content.next() )
 			pindex = 0;
 			pindex = 0;
 	}
 	}
-	
+
 	function flushFill() {
 	function flushFill() {
 		if( pts.length > 0 ) {
 		if( pts.length > 0 ) {
 			prev.push(pts);
 			prev.push(pts);
@@ -197,7 +211,7 @@ class Graphics extends Drawable {
 		}
 		}
 		if( prev.length == 0 )
 		if( prev.length == 0 )
 			return;
 			return;
-			
+
 		if( prev.length == 1 && isConvex(prev[0]) ) {
 		if( prev.length == 1 && isConvex(prev[0]) ) {
 			var p0 = prev[0][0].id;
 			var p0 = prev[0][0].id;
 			for( i in 1...prev[0].length - 1 ) {
 			for( i in 1...prev[0].length - 1 ) {
@@ -209,31 +223,31 @@ class Graphics extends Drawable {
 			var ctx = new hxd.poly2tri.SweepContext();
 			var ctx = new hxd.poly2tri.SweepContext();
 			for( p in prev )
 			for( p in prev )
 				ctx.addPolyline(p);
 				ctx.addPolyline(p);
-				
+
 			var p = new hxd.poly2tri.Sweep(ctx);
 			var p = new hxd.poly2tri.Sweep(ctx);
 			p.triangulate();
 			p.triangulate();
-			
+
 			for( t in ctx.triangles )
 			for( t in ctx.triangles )
 				for( p in t.points )
 				for( p in t.points )
 					content.addIndex(p.id);
 					content.addIndex(p.id);
 		}
 		}
-				
+
 		prev = [];
 		prev = [];
 		if( content.next() )
 		if( content.next() )
 			pindex = 0;
 			pindex = 0;
 	}
 	}
-	
+
 	function flush() {
 	function flush() {
 		flushFill();
 		flushFill();
 		flushLine();
 		flushLine();
 	}
 	}
-	
+
 	public function beginFill( color : Int = 0, alpha = 1.  ) {
 	public function beginFill( color : Int = 0, alpha = 1.  ) {
 		flush();
 		flush();
 		setColor(color,alpha);
 		setColor(color,alpha);
 		doFill = true;
 		doFill = true;
 	}
 	}
-	
+
 	public function lineStyle( size : Float = 0, color = 0, alpha = 1. ) {
 	public function lineStyle( size : Float = 0, color = 0, alpha = 1. ) {
 		flush();
 		flush();
 		this.lineSize = size;
 		this.lineSize = size;
@@ -242,26 +256,26 @@ class Graphics extends Drawable {
 		lineG = ((color >> 8) & 0xFF) / 255.;
 		lineG = ((color >> 8) & 0xFF) / 255.;
 		lineB = (color & 0xFF) / 255.;
 		lineB = (color & 0xFF) / 255.;
 	}
 	}
-	
+
 	public function endFill() {
 	public function endFill() {
 		flush();
 		flush();
 		doFill = false;
 		doFill = false;
 	}
 	}
-	
+
 	public inline function setColor( color : Int, alpha : Float = 1. ) {
 	public inline function setColor( color : Int, alpha : Float = 1. ) {
 		curA = alpha;
 		curA = alpha;
 		curR = ((color >> 16) & 0xFF) / 255.;
 		curR = ((color >> 16) & 0xFF) / 255.;
 		curG = ((color >> 8) & 0xFF) / 255.;
 		curG = ((color >> 8) & 0xFF) / 255.;
 		curB = (color & 0xFF) / 255.;
 		curB = (color & 0xFF) / 255.;
 	}
 	}
-	
+
 	public function drawRect( x : Float, y : Float, w : Float, h : Float ) {
 	public function drawRect( x : Float, y : Float, w : Float, h : Float ) {
 		addPoint(x, y);
 		addPoint(x, y);
 		addPoint(x + w, y);
 		addPoint(x + w, y);
 		addPoint(x + w, y + h);
 		addPoint(x + w, y + h);
 		addPoint(x, y + h);
 		addPoint(x, y + h);
 	}
 	}
-	
+
 	public function drawCircle( cx : Float, cy : Float, ray : Float, nsegments = 0 ) {
 	public function drawCircle( cx : Float, cy : Float, ray : Float, nsegments = 0 ) {
 		if( nsegments == 0 )
 		if( nsegments == 0 )
 			nsegments = Math.ceil(ray * 3.14 * 2 / 4);
 			nsegments = Math.ceil(ray * 3.14 * 2 / 4);
@@ -272,19 +286,23 @@ class Graphics extends Drawable {
 			addPoint(cx + Math.cos(a) * ray, cy + Math.sin(a) * ray);
 			addPoint(cx + Math.cos(a) * ray, cy + Math.sin(a) * ray);
 		}
 		}
 	}
 	}
-	
+
 	public function addHole() {
 	public function addHole() {
 		if( pts.length > 0 ) {
 		if( pts.length > 0 ) {
 			prev.push(pts);
 			prev.push(pts);
 			pts = [];
 			pts = [];
 		}
 		}
 	}
 	}
-	
+
 	public inline function addPoint( x : Float, y : Float ) {
 	public inline function addPoint( x : Float, y : Float ) {
 		addPointFull(x, y, curR, curG, curB, curA);
 		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. ) {
 	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 ) {
 		if( doFill ) {
 			var p = new GraphicsPoint(x, y);
 			var p = new GraphicsPoint(x, y);
 			p.id = pindex++;
 			p.id = pindex++;
@@ -294,7 +312,7 @@ class Graphics extends Drawable {
 		if( lineSize > 0 )
 		if( lineSize > 0 )
 			linePts.push(new LinePoint(x, y, lineR, lineG, lineB, lineA));
 			linePts.push(new LinePoint(x, y, lineR, lineG, lineB, lineA));
 	}
 	}
-	
+
 	override function draw(ctx:RenderContext) {
 	override function draw(ctx:RenderContext) {
 		flush();
 		flush();
 		setupShader(ctx.engine, tile, 0);
 		setupShader(ctx.engine, tile, 0);

+ 29 - 24
h2d/Interactive.hx

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

+ 133 - 26
h2d/Sprite.hx

@@ -4,10 +4,11 @@ import hxd.Math;
 @:allow(h2d.Tools)
 @:allow(h2d.Tools)
 class Sprite {
 class Sprite {
 
 
+	static var tmpMatrix = new h3d.Matrix();
 	var childs : Array<Sprite>;
 	var childs : Array<Sprite>;
 	public var parent(default, null) : Sprite;
 	public var parent(default, null) : Sprite;
 	public var numChildren(get, never) : Int;
 	public var numChildren(get, never) : Int;
-	
+
 	public var x(default,set) : Float;
 	public var x(default,set) : Float;
 	public var y(default, set) : Float;
 	public var y(default, set) : Float;
 	public var scaleX(default,set) : Float;
 	public var scaleX(default,set) : Float;
@@ -21,11 +22,11 @@ class Sprite {
 	var matD : Float;
 	var matD : Float;
 	var absX : Float;
 	var absX : Float;
 	var absY : Float;
 	var absY : Float;
-	
+
 	var posChanged : Bool;
 	var posChanged : Bool;
 	var allocated : Bool;
 	var allocated : Bool;
 	var lastFrame : Int;
 	var lastFrame : Int;
-	
+
 	public function new( ?parent : Sprite ) {
 	public function new( ?parent : Sprite ) {
 		matA = 1; matB = 0; matC = 0; matD = 1; absX = 0; absY = 0;
 		matA = 1; matB = 0; matC = 0; matD = 1; absX = 0; absY = 0;
 		x = 0; y = 0; scaleX = 1; scaleY = 1; rotation = 0;
 		x = 0; y = 0; scaleX = 1; scaleY = 1; rotation = 0;
@@ -35,14 +36,120 @@ class Sprite {
 		if( parent != null )
 		if( parent != null )
 			parent.addChild(this);
 			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() {
 	public function getSpritesCount() {
 		var k = 0;
 		var k = 0;
 		for( c in childs )
 		for( c in childs )
 			k += c.getSpritesCount() + 1;
 			k += c.getSpritesCount() + 1;
 		return k;
 		return k;
 	}
 	}
-	
+
 	public function localToGlobal( ?pt : h2d.col.Point ) {
 	public function localToGlobal( ?pt : h2d.col.Point ) {
 		syncPos();
 		syncPos();
 		if( pt == null ) pt = new h2d.col.Point();
 		if( pt == null ) pt = new h2d.col.Point();
@@ -82,17 +189,17 @@ class Sprite {
 		pt.y = py;
 		pt.y = py;
 		return pt;
 		return pt;
 	}
 	}
-	
+
 	function getScene() {
 	function getScene() {
 		var p = this;
 		var p = this;
 		while( p.parent != null ) p = p.parent;
 		while( p.parent != null ) p = p.parent;
 		return Std.instance(p, Scene);
 		return Std.instance(p, Scene);
 	}
 	}
-	
+
 	public function addChild( s : Sprite ) {
 	public function addChild( s : Sprite ) {
 		addChildAt(s, childs.length);
 		addChildAt(s, childs.length);
 	}
 	}
-	
+
 	public function addChildAt( s : Sprite, pos : Int ) {
 	public function addChildAt( s : Sprite, pos : Int ) {
 		if( pos < 0 ) pos = 0;
 		if( pos < 0 ) pos = 0;
 		if( pos > childs.length ) pos = childs.length;
 		if( pos > childs.length ) pos = childs.length;
@@ -121,40 +228,40 @@ class Sprite {
 				s.onParentChanged();
 				s.onParentChanged();
 		}
 		}
 	}
 	}
-	
+
 	// called when we're allocated already but moved in hierarchy
 	// called when we're allocated already but moved in hierarchy
 	function onParentChanged() {
 	function onParentChanged() {
 	}
 	}
-	
+
 	// kept for internal init
 	// kept for internal init
 	function onAlloc() {
 	function onAlloc() {
 		allocated = true;
 		allocated = true;
 		for( c in childs )
 		for( c in childs )
 			c.onAlloc();
 			c.onAlloc();
 	}
 	}
-		
+
 	// kept for internal cleanup
 	// kept for internal cleanup
 	function onDelete() {
 	function onDelete() {
 		allocated = false;
 		allocated = false;
 		for( c in childs )
 		for( c in childs )
 			c.onDelete();
 			c.onDelete();
 	}
 	}
-	
+
 	public function removeChild( s : Sprite ) {
 	public function removeChild( s : Sprite ) {
 		if( childs.remove(s) ) {
 		if( childs.remove(s) ) {
 			if( s.allocated ) s.onDelete();
 			if( s.allocated ) s.onDelete();
 			s.parent = null;
 			s.parent = null;
 		}
 		}
 	}
 	}
-	
+
 	// shortcut for parent.removeChild
 	// shortcut for parent.removeChild
 	public inline function remove() {
 	public inline function remove() {
 		if( this != null && parent != null ) parent.removeChild(this);
 		if( this != null && parent != null ) parent.removeChild(this);
 	}
 	}
-	
+
 	function draw( ctx : RenderContext ) {
 	function draw( ctx : RenderContext ) {
 	}
 	}
-	
+
 	function sync( ctx : RenderContext ) {
 	function sync( ctx : RenderContext ) {
 		/*
 		/*
 		if( currentAnimation != null ) {
 		if( currentAnimation != null ) {
@@ -172,7 +279,7 @@ class Sprite {
 			calcAbsPos();
 			calcAbsPos();
 			posChanged = false;
 			posChanged = false;
 		}
 		}
-		
+
 		lastFrame = ctx.frame;
 		lastFrame = ctx.frame;
 		var p = 0, len = childs.length;
 		var p = 0, len = childs.length;
 		while( p < len ) {
 		while( p < len ) {
@@ -192,7 +299,7 @@ class Sprite {
 				p++;
 				p++;
 		}
 		}
 	}
 	}
-	
+
 	function syncPos() {
 	function syncPos() {
 		if( parent != null ) parent.syncPos();
 		if( parent != null ) parent.syncPos();
 		if( posChanged ) {
 		if( posChanged ) {
@@ -202,7 +309,7 @@ class Sprite {
 			posChanged = false;
 			posChanged = false;
 		}
 		}
 	}
 	}
-	
+
 	function calcAbsPos() {
 	function calcAbsPos() {
 		if( parent == null ) {
 		if( parent == null ) {
 			var cr, sr;
 			var cr, sr;
@@ -272,22 +379,22 @@ class Sprite {
 		posChanged = true;
 		posChanged = true;
 		return y = v;
 		return y = v;
 	}
 	}
-	
+
 	inline function set_scaleX(v) {
 	inline function set_scaleX(v) {
 		posChanged = true;
 		posChanged = true;
 		return scaleX = v;
 		return scaleX = v;
 	}
 	}
-	
+
 	inline function set_scaleY(v) {
 	inline function set_scaleY(v) {
 		posChanged = true;
 		posChanged = true;
 		return scaleY = v;
 		return scaleY = v;
 	}
 	}
-	
+
 	inline function set_rotation(v) {
 	inline function set_rotation(v) {
 		posChanged = true;
 		posChanged = true;
 		return rotation = v;
 		return rotation = v;
 	}
 	}
-	
+
 	public function move( dx : Float, dy : Float ) {
 	public function move( dx : Float, dy : Float ) {
 		x += dx * Math.cos(rotation);
 		x += dx * Math.cos(rotation);
 		y += dy * Math.sin(rotation);
 		y += dy * Math.sin(rotation);
@@ -297,16 +404,16 @@ class Sprite {
 		this.x = x;
 		this.x = x;
 		this.y = y;
 		this.y = y;
 	}
 	}
-	
+
 	public inline function rotate( v : Float ) {
 	public inline function rotate( v : Float ) {
 		rotation += v;
 		rotation += v;
 	}
 	}
-	
+
 	public inline function scale( v : Float ) {
 	public inline function scale( v : Float ) {
 		scaleX *= v;
 		scaleX *= v;
 		scaleY *= v;
 		scaleY *= v;
 	}
 	}
-	
+
 	public inline function setScale( v : Float ) {
 	public inline function setScale( v : Float ) {
 		scaleX = v;
 		scaleX = v;
 		scaleY = v;
 		scaleY = v;
@@ -322,7 +429,7 @@ class Sprite {
 				return i;
 				return i;
 		return -1;
 		return -1;
 	}
 	}
-	
+
 	inline function get_numChildren() {
 	inline function get_numChildren() {
 		return childs.length;
 		return childs.length;
 	}
 	}

+ 48 - 14
h2d/SpriteBatch.hx

@@ -9,24 +9,24 @@ class BatchElement {
 	public var alpha : Float;
 	public var alpha : Float;
 	public var t : Tile;
 	public var t : Tile;
 	public var batch(default, null) : SpriteBatch;
 	public var batch(default, null) : SpriteBatch;
-	
+
 	var prev : BatchElement;
 	var prev : BatchElement;
 	var next : BatchElement;
 	var next : BatchElement;
-	
+
 	function new(t) {
 	function new(t) {
 		x = 0; y = 0; alpha = 1;
 		x = 0; y = 0; alpha = 1;
 		rotation = 0; scale = 1;
 		rotation = 0; scale = 1;
 		this.t = t;
 		this.t = t;
 	}
 	}
-	
+
 	function update(et:Float) {
 	function update(et:Float) {
 		return true;
 		return true;
 	}
 	}
-	
+
 	public inline function remove() {
 	public inline function remove() {
 		batch.delete(this);
 		batch.delete(this);
 	}
 	}
-	
+
 }
 }
 
 
 class SpriteBatch extends Drawable {
 class SpriteBatch extends Drawable {
@@ -37,13 +37,13 @@ class SpriteBatch extends Drawable {
 	var first : BatchElement;
 	var first : BatchElement;
 	var last : BatchElement;
 	var last : BatchElement;
 	var tmpBuf : hxd.FloatBuffer;
 	var tmpBuf : hxd.FloatBuffer;
-		
+
 	public function new(t,?parent) {
 	public function new(t,?parent) {
 		super(parent);
 		super(parent);
 		tile = t;
 		tile = t;
 		shader.hasVertexAlpha = true;
 		shader.hasVertexAlpha = true;
 	}
 	}
-	
+
 	public function add(e:BatchElement) {
 	public function add(e:BatchElement) {
 		e.batch = this;
 		e.batch = this;
 		if( first == null )
 		if( first == null )
@@ -55,11 +55,11 @@ class SpriteBatch extends Drawable {
 		}
 		}
 		return e;
 		return e;
 	}
 	}
-	
+
 	public function alloc(t) {
 	public function alloc(t) {
 		return add(new BatchElement(t));
 		return add(new BatchElement(t));
 	}
 	}
-	
+
 	@:allow(h2d.BatchElement)
 	@:allow(h2d.BatchElement)
 	function delete(e : BatchElement) {
 	function delete(e : BatchElement) {
 		if( e.prev == null ) {
 		if( e.prev == null ) {
@@ -73,7 +73,7 @@ class SpriteBatch extends Drawable {
 		} else
 		} else
 			e.next.prev = e.prev;
 			e.next.prev = e.prev;
 	}
 	}
-	
+
 	override function sync(ctx) {
 	override function sync(ctx) {
 		super.sync(ctx);
 		super.sync(ctx);
 		if( hasUpdate ) {
 		if( hasUpdate ) {
@@ -85,8 +85,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 ) {
 	override function draw( ctx : RenderContext ) {
 		if( first == null )
 		if( first == null )
 			return;
 			return;
@@ -154,9 +188,9 @@ class SpriteBatch extends Drawable {
 		ctx.engine.renderQuadBuffer(buffer);
 		ctx.engine.renderQuadBuffer(buffer);
 		buffer.dispose();
 		buffer.dispose();
 	}
 	}
-	
+
 	public inline function isEmpty() {
 	public inline function isEmpty() {
 		return first == null;
 		return first == null;
 	}
 	}
-	
+
 }
 }

+ 24 - 5
h2d/TileColorGroup.hx

@@ -3,6 +3,10 @@ package h2d;
 private class TileLayerContent extends h3d.prim.Primitive {
 private class TileLayerContent extends h3d.prim.Primitive {
 
 
 	var tmp : hxd.FloatBuffer;
 	var tmp : hxd.FloatBuffer;
+	public var xMin : Float;
+	public var yMin : Float;
+	public var xMax : Float;
+	public var yMax : Float;
 
 
 	public function new() {
 	public function new() {
 		reset();
 		reset();
@@ -12,6 +16,10 @@ private class TileLayerContent extends h3d.prim.Primitive {
 		tmp = new hxd.FloatBuffer();
 		tmp = new hxd.FloatBuffer();
 		if( buffer != null ) buffer.dispose();
 		if( buffer != null ) buffer.dispose();
 		buffer = null;
 		buffer = null;
+		xMin = hxd.Math.POSITIVE_INFINITY;
+		yMin = hxd.Math.POSITIVE_INFINITY;
+		xMax = hxd.Math.NEGATIVE_INFINITY;
+		yMax = hxd.Math.NEGATIVE_INFINITY;
 	}
 	}
 
 
 	override public function triCount() {
 	override public function triCount() {
@@ -29,6 +37,8 @@ private class TileLayerContent extends h3d.prim.Primitive {
 	public function add( x : Int, y : Int, r : Float, g : Float, b : Float, a : Float, t : Tile ) {
 	public function add( x : Int, y : Int, r : Float, g : Float, b : Float, a : Float, t : Tile ) {
 		var sx = x + t.dx;
 		var sx = x + t.dx;
 		var sy = y + t.dy;
 		var sy = y + t.dy;
+		var sx2 = sx + t.width;
+		var sy2 = sy + t.height;
 		tmp.push(sx);
 		tmp.push(sx);
 		tmp.push(sy);
 		tmp.push(sy);
 		tmp.push(t.u);
 		tmp.push(t.u);
@@ -37,7 +47,7 @@ private class TileLayerContent extends h3d.prim.Primitive {
 		tmp.push(g);
 		tmp.push(g);
 		tmp.push(b);
 		tmp.push(b);
 		tmp.push(a);
 		tmp.push(a);
-		tmp.push(sx + t.width);
+		tmp.push(sx2);
 		tmp.push(sy);
 		tmp.push(sy);
 		tmp.push(t.u2);
 		tmp.push(t.u2);
 		tmp.push(t.v);
 		tmp.push(t.v);
@@ -46,23 +56,27 @@ private class TileLayerContent extends h3d.prim.Primitive {
 		tmp.push(b);
 		tmp.push(b);
 		tmp.push(a);
 		tmp.push(a);
 		tmp.push(sx);
 		tmp.push(sx);
-		tmp.push(sy + t.height);
+		tmp.push(sy2);
 		tmp.push(t.u);
 		tmp.push(t.u);
 		tmp.push(t.v2);
 		tmp.push(t.v2);
 		tmp.push(r);
 		tmp.push(r);
 		tmp.push(g);
 		tmp.push(g);
 		tmp.push(b);
 		tmp.push(b);
 		tmp.push(a);
 		tmp.push(a);
-		tmp.push(sx + t.width);
-		tmp.push(sy + t.height);
+		tmp.push(sx2);
+		tmp.push(sy2);
 		tmp.push(t.u2);
 		tmp.push(t.u2);
 		tmp.push(t.v2);
 		tmp.push(t.v2);
 		tmp.push(r);
 		tmp.push(r);
 		tmp.push(g);
 		tmp.push(g);
 		tmp.push(b);
 		tmp.push(b);
 		tmp.push(a);
 		tmp.push(a);
+		if( sx < xMin ) xMin = sx;
+		if( sy < yMin ) yMin = sy;
+		if( sx2 > xMax ) xMax = sx2;
+		if( sy2 > yMax ) yMax = sy2;
 	}
 	}
-	
+
 	public function addPoint( x : Float, y : Float, color : Int ) {
 	public function addPoint( x : Float, y : Float, color : Int ) {
 		tmp.push(x);
 		tmp.push(x);
 		tmp.push(y);
 		tmp.push(y);
@@ -158,6 +172,11 @@ class TileColorGroup extends Drawable {
 		content.reset();
 		content.reset();
 	}
 	}
 
 
+	override function getBoundsRec( relativeTo, out ) {
+		super.getBoundsRec(relativeTo, out);
+		addBounds(relativeTo, out, content.xMin, content.yMin, content.xMax - content.xMin, content.yMax - content.yMin);
+	}
+
 	/**
 	/**
 		Returns the number of tiles added to the group
 		Returns the number of tiles added to the group
 	**/
 	**/

+ 33 - 15
h2d/TileGroup.hx

@@ -3,21 +3,30 @@ package h2d;
 private class TileLayerContent extends h3d.prim.Primitive {
 private class TileLayerContent extends h3d.prim.Primitive {
 
 
 	var tmp : hxd.FloatBuffer;
 	var tmp : hxd.FloatBuffer;
-	
+	public var xMin : Float;
+	public var yMin : Float;
+	public var xMax : Float;
+	public var yMax : Float;
+
+
 	public function new() {
 	public function new() {
 		reset();
 		reset();
 	}
 	}
-	
+
 	public function isEmpty() {
 	public function isEmpty() {
 		return buffer == null;
 		return buffer == null;
 	}
 	}
-	
+
 	public function reset() {
 	public function reset() {
 		tmp = new hxd.FloatBuffer();
 		tmp = new hxd.FloatBuffer();
 		if( buffer != null ) buffer.dispose();
 		if( buffer != null ) buffer.dispose();
 		buffer = null;
 		buffer = null;
+		xMin = hxd.Math.POSITIVE_INFINITY;
+		yMin = hxd.Math.POSITIVE_INFINITY;
+		xMax = hxd.Math.NEGATIVE_INFINITY;
+		yMax = hxd.Math.NEGATIVE_INFINITY;
 	}
 	}
-	
+
 	public function add( x : Int, y : Int, t : Tile ) {
 	public function add( x : Int, y : Int, t : Tile ) {
 		var sx = x + t.dx;
 		var sx = x + t.dx;
 		var sy = y + t.dy;
 		var sy = y + t.dy;
@@ -39,12 +48,16 @@ private class TileLayerContent extends h3d.prim.Primitive {
 		tmp.push(sy2);
 		tmp.push(sy2);
 		tmp.push(t.u2);
 		tmp.push(t.u2);
 		tmp.push(t.v2);
 		tmp.push(t.v2);
+		if( sx < xMin ) xMin = sx;
+		if( sy < yMin ) yMin = sy;
+		if( sx2 > xMax ) xMax = sx2;
+		if( sy2 > yMax ) yMax = sy2;
 	}
 	}
-	
+
 	override public function triCount() {
 	override public function triCount() {
 		return if( buffer == null ) tmp.length >> 3 else buffer.totalVertices() >> 1;
 		return if( buffer == null ) tmp.length >> 3 else buffer.totalVertices() >> 1;
 	}
 	}
-	
+
 	override public function alloc(engine:h3d.Engine) {
 	override public function alloc(engine:h3d.Engine) {
 		if( tmp == null ) reset();
 		if( tmp == null ) reset();
 		buffer = h3d.Buffer.ofFloats(tmp, 4, [Quads]);
 		buffer = h3d.Buffer.ofFloats(tmp, 4, [Quads]);
@@ -54,44 +67,49 @@ private class TileLayerContent extends h3d.prim.Primitive {
 		if( buffer == null || buffer.isDisposed() ) alloc(engine);
 		if( buffer == null || buffer.isDisposed() ) alloc(engine);
 		engine.renderQuadBuffer(buffer, min, len);
 		engine.renderQuadBuffer(buffer, min, len);
 	}
 	}
-	
+
 }
 }
 
 
 class TileGroup extends Drawable {
 class TileGroup extends Drawable {
-	
+
 	var content : TileLayerContent;
 	var content : TileLayerContent;
-	
+
 	public var tile : Tile;
 	public var tile : Tile;
 	public var rangeMin : Int;
 	public var rangeMin : Int;
 	public var rangeMax : Int;
 	public var rangeMax : Int;
-	
+
 	public function new(t,?parent) {
 	public function new(t,?parent) {
 		tile = t;
 		tile = t;
 		rangeMin = rangeMax = -1;
 		rangeMin = rangeMax = -1;
 		content = new TileLayerContent();
 		content = new TileLayerContent();
 		super(parent);
 		super(parent);
 	}
 	}
-	
+
+	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() {
 	public function reset() {
 		content.reset();
 		content.reset();
 	}
 	}
-	
+
 	override function onDelete() {
 	override function onDelete() {
 		content.dispose();
 		content.dispose();
 		super.onDelete();
 		super.onDelete();
 	}
 	}
-	
+
 	public inline function add(x, y, t) {
 	public inline function add(x, y, t) {
 		content.add(x, y, t);
 		content.add(x, y, t);
 	}
 	}
-	
+
 	/**
 	/**
 		Returns the number of tiles added to the group
 		Returns the number of tiles added to the group
 	**/
 	**/
 	public function count() {
 	public function count() {
 		return content.triCount() >> 1;
 		return content.triCount() >> 1;
 	}
 	}
-		
+
 	override function draw(ctx:RenderContext) {
 	override function draw(ctx:RenderContext) {
 		setupShader(ctx.engine, tile, 0);
 		setupShader(ctx.engine, tile, 0);
 		var min = rangeMin < 0 ? 0 : rangeMin * 2;
 		var min = rangeMin < 0 ? 0 : rangeMin * 2;

+ 4 - 0
h2d/col/Bounds.hx

@@ -91,6 +91,10 @@ class Bounds {
 		return new Point(xMax, yMax);
 		return new Point(xMax, yMax);
 	}
 	}
 
 
+	public inline function isEmpty() {
+		return xMax <= xMin || yMax <= yMin;
+	}
+
 	public inline function empty() {
 	public inline function empty() {
 		xMin = 1e20;
 		xMin = 1e20;
 		yMin = 1e20;
 		yMin = 1e20;

+ 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>