Kaynağa Gözat

Merge branch 'h2d'

ncannasse 13 yıl önce
ebeveyn
işleme
fbbc083f2d
17 değiştirilmiş dosya ile 1789 ekleme ve 0 silme
  1. 31 0
      h2d/Bitmap.hx
  2. 9 0
      h2d/BlendMode.hx
  3. 185 0
      h2d/CachedBitmap.hx
  4. 29 0
      h2d/Event.hx
  5. 86 0
      h2d/Font.hx
  6. 63 0
      h2d/Graphics.hx
  7. 67 0
      h2d/HtmlText.hx
  8. 84 0
      h2d/Interactive.hx
  9. 47 0
      h2d/Layers.hx
  10. 275 0
      h2d/Scene.hx
  11. 184 0
      h2d/Sprite.hx
  12. 148 0
      h2d/SpriteBatch.hx
  13. 52 0
      h2d/Text.hx
  14. 151 0
      h2d/Tile.hx
  15. 154 0
      h2d/TileColorGroup.hx
  16. 130 0
      h2d/TileGroup.hx
  17. 94 0
      h2d/Tools.hx

+ 31 - 0
h2d/Bitmap.hx

@@ -0,0 +1,31 @@
+package h2d;
+
+class Bitmap extends Sprite {
+
+	public var tile : Tile;
+	public var color : h3d.Color;
+	public var alpha(get, set) : Float;
+	
+	public function new( ?tile, ?parent ) {
+		super(parent);
+		color = new h3d.Color(1, 1, 1, 1);
+		this.tile = tile;
+	}
+	
+	override function draw( engine : h3d.Engine ) {
+		Tools.drawTile(engine, this, tile, color, blendMode);
+	}
+	
+	inline function get_alpha() {
+		return color.a;
+	}
+
+	inline function set_alpha(v) {
+		return color.a = v;
+	}
+		
+	public static function create( bmp : flash.display.BitmapData ) {
+		return new Bitmap(Tile.fromBitmap(bmp));
+	}
+	
+}

+ 9 - 0
h2d/BlendMode.hx

@@ -0,0 +1,9 @@
+package h2d;
+
+enum BlendMode {
+	Normal;
+	None;
+	Add;
+	Multiply;
+	Erase;
+}

+ 185 - 0
h2d/CachedBitmap.hx

@@ -0,0 +1,185 @@
+package h2d;
+
+private class BitmapMatrixShader extends h3d.Shader {
+	static var SRC = {
+		var input : {
+			pos : Float2,
+		};
+		var tuv : Float2;
+		function vertex( size : Float3, mat1 : Float3, mat2 : Float3, uvScale : Float2 ) {
+			var tmp : Float4;
+			var spos = pos.xyw * size;
+			tmp.x = spos.dp3(mat1);
+			tmp.y = spos.dp3(mat2);
+			tmp.z = 0;
+			tmp.w = 1;
+			out = tmp;
+			tuv = pos * uvScale;
+		}
+		function fragment( tex : Texture, mcolor : M44, acolor : Float4 ) {
+			out = tex.get(tuv, nearest) * mcolor + acolor;
+		}
+	}
+}
+
+class CachedBitmap extends Sprite {
+
+	var tex : h3d.mat.Texture;
+	public var width(default, set) : Int;
+	public var height(default, set) : Int;
+	public var freezed : Bool;
+	public var colorMatrix : Null<h3d.Matrix>;
+	public var colorAdd : Null<h3d.Color>;
+	
+	var renderDone : Bool;
+	var realWidth : Int;
+	var realHeight : Int;
+	var tile : Tile;
+	
+	public function new( ?parent, width = -1, height = -1 ) {
+		super(parent);
+		this.width = width;
+		this.height = height;
+	}
+	
+	override function onDelete() {
+		if( tex != null ) {
+			tex.dispose();
+			tex = null;
+		}
+		super.onDelete();
+	}
+	
+	function set_width(w) {
+		if( tex != null ) {
+			tex.dispose();
+			tex = null;
+		}
+		width = w;
+		return w;
+	}
+
+	function set_height(h) {
+		if( tex != null ) {
+			tex.dispose();
+			tex = null;
+		}
+		height = h;
+		return h;
+	}
+
+	static var BITMAP_OBJ : h3d.CustomObject<BitmapMatrixShader> = null;
+	static var TMP_VECTOR = new h3d.Vector();
+	
+	override function draw( engine : h3d.Engine ) {
+		if( colorMatrix == null && colorAdd == null ) {
+			Tools.drawTile(engine, this, tile, new h3d.Color(1, 1, 1, 1), blendMode);
+			return;
+		}
+		var b = BITMAP_OBJ;
+		if( b == null ) {
+			var p = new h3d.prim.Quads([
+				new h3d.Point(0, 0),
+				new h3d.Point(1, 0),
+				new h3d.Point(0, 1),
+				new h3d.Point(1, 1),
+			]);
+			b = new h3d.CustomObject(p, new BitmapMatrixShader());
+			b.material.culling = None;
+			b.material.depth(false, Always);
+			BITMAP_OBJ = b;
+		}
+		Tools.setBlendMode(b.material,blendMode);
+		var tmp = TMP_VECTOR;
+		tmp.x = tile.width;
+		tmp.y = tile.height;
+		tmp.z = 1;
+		b.shader.size = tmp;
+		tmp.x = matA;
+		tmp.y = matC;
+		tmp.z = absX + tile.dx * matA + tile.dy * matC;
+		b.shader.mat1 = tmp;
+		tmp.x = matB;
+		tmp.y = matD;
+		tmp.z = absY + tile.dx * matB + tile.dy * matD;
+		b.shader.mat2 = tmp;
+		tmp.x = tile.u2 - tile.u;
+		tmp.y = tile.v2 - tile.v;
+		b.shader.uvScale = tmp;
+		b.shader.mcolor = colorMatrix == null ? h3d.Matrix.I() : colorMatrix;
+		if( colorAdd == null ) {
+			tmp.x = 0;
+			tmp.y = 0;
+			tmp.z = 0;
+			tmp.w = 0;
+		} else {
+			tmp.x = colorAdd.r;
+			tmp.y = colorAdd.g;
+			tmp.z = colorAdd.b;
+			tmp.w = colorAdd.a;
+		}
+		b.shader.acolor = tmp;
+		b.shader.tex = tile.tex;
+		b.render(engine);
+	}
+	
+	override function render( engine : h3d.Engine ) {
+		updatePos();
+		if( tex != null && ((width < 0 && tex.width < engine.width) || (height < 0 && tex.height < engine.height)) ) {
+			tex.dispose();
+			tex = null;
+		}
+		if( tex == null ) {
+			var tw = 1, th = 1;
+			realWidth = width < 0 ? engine.width : width;
+			realHeight = height < 0 ? engine.height : height;
+			while( tw < realWidth ) tw <<= 1;
+			while( th < realHeight ) th <<= 1;
+			tex = engine.mem.allocTargetTexture(tw, th);
+			renderDone = false;
+			tile = new Tile(tex,0, 0, realWidth, realHeight);
+		}
+		if( !freezed || !renderDone ) {
+			var oldA = matA, oldB = matB, oldC = matC, oldD = matD, oldX = absX, oldY = absY;
+			
+			// init matrix without rotation
+			matA = 1;
+			matB = 0;
+			matC = 0;
+			matD = 1;
+			absX = 0;
+			absY = 0;
+			
+			// adds a pixels-to-viewport transform
+			var w = 2 / tex.width;
+			var h = -2 / tex.height;
+			absX = absX * w - 1;
+			absY = absY * h + 1;
+			matA *= w;
+			matB *= h;
+			matC *= w;
+			matD *= h;
+			
+			engine.setTarget(tex);
+			engine.setRenderZone(0, 0, realWidth, realHeight);
+			for( c in childs )
+				c.render(engine);
+			engine.setTarget(null);
+			engine.setRenderZone();
+			
+			// restore
+			matA = oldA;
+			matB = oldB;
+			matC = oldC;
+			matD = oldD;
+			absX = oldX;
+			absY = oldY;
+			
+			renderDone = true;
+		}
+
+		draw(engine);
+		posChanged = false;
+	}
+	
+}

+ 29 - 0
h2d/Event.hx

@@ -0,0 +1,29 @@
+package h2d;
+
+enum EventKind {
+	EPush;
+	ERelease;
+	EMove;
+	EOver;
+	EOut;
+}
+
+class Event {
+
+	public var kind : EventKind;
+	public var relX : Float;
+	public var relY : Float;
+	public var propagate : Bool;
+	public var cancel : Bool;
+	
+	public function new(k,x=0.,y=0.) {
+		kind = k;
+		this.relX = x;
+		this.relY = y;
+	}
+	
+	public function toString() {
+		return kind + "[" + Std.int(relX) + "," + Std.int(relY) + "]";
+	}
+	
+}

+ 86 - 0
h2d/Font.hx

@@ -0,0 +1,86 @@
+package h2d;
+
+class Font extends Tile {
+
+	static var DEFAULT_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 ?!,()-/'\"éèêëÉÈÊËàâäáÀÂÄÁùûüúÙÛÜÚîïíÎÏÍôóöõÔÓÖæÆœŒçÇñÑ";
+
+	public var glyphs : Array<Tile>;
+	
+	public function new( name : String, size : Int, aa = true, ?chars ) {
+		super(null, 0, 0, 0, 0);
+		if( chars == null )
+			chars = DEFAULT_CHARS;
+		var tf = new flash.text.TextField();
+		var fmt = tf.defaultTextFormat;
+		fmt.font = name;
+		fmt.size = size;
+		fmt.color = 0xFFFFFF;
+		tf.defaultTextFormat = fmt;
+		for( f in flash.text.Font.enumerateFonts() )
+			if( f.fontName == name ) {
+				tf.embedFonts = true;
+				break;
+			}
+		if( !aa ) {
+			tf.sharpness = 400;
+			tf.gridFitType = flash.text.GridFitType.PIXEL;
+			tf.antiAliasType = flash.text.AntiAliasType.ADVANCED;
+		}
+		var surf = 0;
+		var sizes = [];
+		for( i in 0...chars.length ) {
+			tf.text = chars.charAt(i);
+			var w = Math.ceil(tf.textWidth);
+			if( w == 0 ) continue;
+			var h = Math.ceil(tf.textHeight);
+			surf += (w + 1) * (h + 1);
+			sizes[i] = { w:w, h:h };
+		}
+		var side = Math.ceil( Math.sqrt(surf) );
+		var width = 1;
+		while( side > width )
+			width <<= 1;
+		var height = width;
+		while( width * height >> 1 > surf )
+			height >>= 1;
+		var all, bmp;
+		do {
+			bmp = new flash.display.BitmapData(width, height, true, 0);
+			glyphs = [];
+			all = [];
+			var m = new flash.geom.Matrix();
+			var x = 0, y = 0, lineH = 0;
+			for( i in 0...chars.length ) {
+				var size = sizes[i];
+				if( size == null ) continue;
+				var w = size.w;
+				var h = size.h;
+				if( x + w > width ) {
+					x = 0;
+					y += lineH + 1;
+				}
+				// no space, resize
+				if( y + h > height ) {
+					bmp.dispose();
+					bmp = null;
+					height <<= 1;
+					break;
+				}
+				m.tx = x - 2;
+				m.ty = y - 2;
+				tf.text = chars.charAt(i);
+				bmp.draw(tf, m);
+				var t = sub(x, y, w, h, 2, 2);
+				all.push(t);
+				glyphs[chars.charCodeAt(i)] = t;
+				// next element
+				if( h > lineH ) lineH = h;
+				x += w + 1;
+			}
+		} while( bmp == null );
+		setTexture(Tile.fromBitmap(bmp).tex);
+		for( t in all )
+			t.setTexture(tex);
+	}
+	
+}

+ 63 - 0
h2d/Graphics.hx

@@ -0,0 +1,63 @@
+package h2d;
+
+@:allow(h2d.Graphics)
+class GraphicsContext {
+	
+	var g : Graphics;
+	var mc : flash.display.Sprite;
+	var mcg : flash.display.Graphics;
+	
+	function new(g) {
+		this.g = g;
+		this.mc = new flash.display.Sprite();
+		mcg = mc.graphics;
+	}
+	
+	public inline function beginFill( color : Int, alpha = 1. ) {
+		mcg.beginFill(color, alpha);
+	}
+	
+	public inline function drawCircle(cx, cy, radius) {
+		mcg.drawCircle(cx, cy, radius);
+	}
+	
+	public inline function drawRect(x,y,width,height) {
+		mcg.drawRect(x, y, width, height);
+	}
+	
+	public inline function endFill() {
+		mcg.endFill();
+	}
+	
+}
+
+class Graphics extends Sprite {
+
+	var tile : Tile;
+	var ctx : GraphicsContext;
+		
+	public function beginDraw() {
+		return (ctx = new GraphicsContext(this));
+	}
+	
+	override function onDelete() {
+		if( tile != null ) {
+			tile.tex.dispose();
+			tile = null;
+		}
+		super.onDelete();
+	}
+	
+	public function endDraw() {
+		if( ctx == null ) return;
+		if( tile != null ) tile.tex.dispose();
+		tile = Tile.fromSprites([ctx.mc])[0];
+	}
+	
+	override function draw(engine) {
+		if( tile == null ) endDraw();
+		if( tile == null ) return;
+		Tools.drawTile(engine, this, tile, null, blendMode);
+	}
+
+}

+ 67 - 0
h2d/HtmlText.hx

@@ -0,0 +1,67 @@
+package h2d;
+
+class HtmlText extends Sprite {
+
+	public var font(default, null) : Font;
+	public var htmlText(default, set) : String;
+	public var textColor(default, set) : Int;
+	
+	var glyphs : TileColorGroup;
+	
+	public function new( font : Font, ?parent ) {
+		super(parent);
+		this.font = font;
+		glyphs = new TileColorGroup(font, this);
+		htmlText = "";
+		textColor = 0xFFFFFF;
+	}
+	
+	function set_htmlText(t) {
+		this.htmlText = t;
+		glyphs.reset();
+		glyphs.setColor(textColor);
+		var letters = font.glyphs;
+		var x = 0, y = 0;
+		function loop( e : Xml ) {
+			if( e.nodeType == Xml.Element ) {
+				var colorChanged = false;
+				switch( e.nodeName.toLowerCase() ) {
+				case "font":
+					for( a in e.attributes() ) {
+						var v = e.get(a);
+						switch( a.toLowerCase() ) {
+						case "color":
+							colorChanged = true;
+							glyphs.setColor(Std.parseInt("0x" + v.substr(1)));
+						default:
+						}
+					}
+				default:
+				}
+				for( child in e )
+					loop(child);
+				if( colorChanged )
+					glyphs.setColor(textColor);
+			} else {
+				var t = e.nodeValue;
+				for( i in 0...t.length ) {
+					var cc = t.charCodeAt(i);
+					var e = letters[cc];
+					if( e == null ) continue;
+					glyphs.add(x, y, e);
+					x += e.width + 1;
+				}
+			}
+		}
+		for( e in Xml.parse(t) )
+			loop(e);
+		return t;
+	}
+	
+	function set_textColor(c) {
+		this.textColor = c;
+		if( htmlText != "" ) set_htmlText(htmlText);
+		return c;
+	}
+
+}

+ 84 - 0
h2d/Interactive.hx

@@ -0,0 +1,84 @@
+package h2d;
+
+class Interactive extends Sprite {
+
+	public var width : Float;
+	public var height : Float;
+	public var useMouseHand(default,set) : Bool;
+	public var isEllipse : Bool;
+	var scene : Scene;
+	
+	public function new(width, height, ?parent) {
+		super(parent);
+		this.width = width;
+		this.height = height;
+		useMouseHand = true;
+	}
+
+	override function onAlloc() {
+		var p : Sprite = this;
+		while( p.parent != null )
+			p = p.parent;
+		if( Std.is(p, Scene) ) {
+			scene = cast p;
+			scene.addEventTarget(this);
+		}
+		super.onAlloc();
+	}
+	
+	override function onDelete() {
+		if( scene != null )
+			scene.removeEventTarget(this);
+		super.onDelete();
+	}
+
+	@:allow(h2d.Scene)
+	function handleEvent( e : Event ) {
+		if( isEllipse && (e.kind != EOut && e.kind != ERelease) ) {
+			var cx = width * 0.5, cy = height * 0.5;
+			var dx = (e.relX - cx) / cx;
+			var dy = (e.relY - cy) / cy;
+			if( dx * dx + dy * dy > 1 ) {
+				e.cancel = true;
+				return;
+			}
+		}
+		switch( e.kind ) {
+		case EMove:
+			onMove(e);
+		case EPush:
+			onPush(e);
+		case ERelease:
+			onRelease(e);
+		case EOver:
+			if( useMouseHand ) flash.ui.Mouse.cursor = flash.ui.MouseCursor.BUTTON;
+			onOver(e);
+		case EOut:
+			if( useMouseHand ) flash.ui.Mouse.cursor = flash.ui.MouseCursor.AUTO;
+			onOut(e);
+		}
+	}
+	
+	function set_useMouseHand(v) {
+		this.useMouseHand = v;
+		if( scene != null && scene.currentOver == this )
+			flash.ui.Mouse.cursor = v ? flash.ui.MouseCursor.BUTTON : flash.ui.MouseCursor.AUTO;
+		return v;
+	}
+	
+	public dynamic function onOver( e : Event ) {
+	}
+
+	public dynamic function onOut( e : Event ) {
+	}
+	
+	public dynamic function onPush( e : Event ) {
+	}
+
+	public dynamic function onRelease( e : Event ) {
+	}
+	
+	public dynamic function onMove( e : Event ) {
+	}
+	
+}

+ 47 - 0
h2d/Layers.hx

@@ -0,0 +1,47 @@
+package h2d;
+
+class Layers extends Sprite {
+	
+	var layers : Array<Int>;
+	var layerCount : Int;
+	
+	public function new(?parent) {
+		super(parent);
+		layers = [];
+		layerCount = 0;
+	}
+	
+	public function add( s : Sprite, layer : Int ) {
+		// new layer
+		while( layer > layerCount )
+			layers[layerCount++] = childs.length;
+		s.remove();
+		if( layer == layerCount )
+			childs.push(s);
+		else {
+			childs.insert(layers[layer], s);
+			for( i in layer...layerCount )
+				layers[i]++;
+		}
+		s.parent = this;
+	}
+	
+	override function removeChild( s : Sprite ) {
+		for( i in 0...childs.length ) {
+			if( childs[i] == s ) {
+				childs.splice(i, 1);
+				var k = layerCount - 1;
+				while( k >= 0 && layers[k] >= i ) {
+					layers[k]--;
+					k--;
+				}
+			}
+		}
+	}
+	
+	public function ysort( layer : Int ) {
+		// TODO
+	}
+
+	
+}

+ 275 - 0
h2d/Scene.hx

@@ -0,0 +1,275 @@
+package h2d;
+
+class Scene extends Sprite {
+
+	public var width(default,null) : Int;
+	public var height(default, null) : Int;
+	
+	public var mouseX(get, null) : Float;
+	public var mouseY(get, null) : Float;
+	
+	var fixedSize : Bool;
+	var interactive : flash.Vector<Interactive>;
+	var pendingEvents : flash.Vector<Event>;
+	var stage : flash.display.Stage;
+	
+	
+	@:allow(h2d.Interactive)
+	var currentOver : Interactive;
+	var pushList : Array<Interactive>;
+	var currentDrag : Event -> Void;
+	
+	public function new() {
+		super(null);
+		var e = h3d.Engine.getCurrent();
+		width = e.width;
+		height = e.height;
+		interactive = new flash.Vector();
+		pushList = new Array();
+		stage = flash.Lib.current.stage;
+	}
+	
+	public function setFixedSize( w, h ) {
+		width = w;
+		height = h;
+		fixedSize = true;
+		posChanged = true;
+	}
+
+	override function onDelete() {
+		stage.removeEventListener(flash.events.MouseEvent.MOUSE_DOWN, onMouseDown);
+		stage.removeEventListener(flash.events.MouseEvent.MOUSE_MOVE, onMouseMove);
+		stage.removeEventListener(flash.events.MouseEvent.MOUSE_UP, onMouseUp);
+		super.onDelete();
+	}
+	
+	override function onAlloc() {
+		stage.addEventListener(flash.events.MouseEvent.MOUSE_DOWN, onMouseDown);
+		stage.addEventListener(flash.events.MouseEvent.MOUSE_MOVE, onMouseMove);
+		stage.addEventListener(flash.events.MouseEvent.MOUSE_UP, onMouseUp);
+		super.onAlloc();
+	}
+	
+	function get_mouseX() {
+		return (stage.mouseX - x) * width / (stage.stageWidth * scaleX);
+	}
+
+	function get_mouseY() {
+		return (stage.mouseY - y) * height / (stage.stageHeight * scaleY);
+	}
+	
+	function onMouseDown(e:flash.events.MouseEvent) {
+		if( pendingEvents != null )
+			pendingEvents.push(new Event(EPush, mouseX, mouseY));
+	}
+
+	function onMouseUp(e:flash.events.MouseEvent) {
+		if( pendingEvents != null )
+			pendingEvents.push(new Event(ERelease, mouseX, mouseY));
+	}
+	
+	function onMouseMove(e:flash.events.MouseEvent) {
+		if( pendingEvents != null )
+			pendingEvents.push(new Event(EMove, mouseX, mouseY));
+	}
+	
+	function emitEvent( event : Event ) {
+		var x = event.relX, y = event.relY;
+		var rx = x * matA + y * matB + absX;
+		var ry = x * matC + y * matD + absY;
+		var r = height / width;
+		var checkOver = false, checkPush = false;
+		switch( event.kind ) {
+		case EMove: checkOver = true;
+		case EPush, ERelease: checkPush = true;
+		default:
+		}
+		for( i in interactive ) {
+			// TODO : we are not sure that the positions are correctly updated !
+			
+			// 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 )
+				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 ) {
+				if( currentOver != i ) {
+					var old = event.propagate;
+					if( currentOver != null ) {
+						event.kind = EOut;
+						// relX/relY is not correct here
+						currentOver.handleEvent(event);
+					}
+					event.kind = EOver;
+					event.cancel = false;
+					i.handleEvent(event);
+					if( event.cancel )
+						currentOver = null;
+					else {
+						currentOver = i;
+						checkOver = false;
+					}
+					event.kind = EMove;
+					event.cancel = false;
+					event.propagate = old;
+				} else
+					checkOver = false;
+			} else if( checkPush ) {
+				if( event.kind == EPush )
+					pushList.push(i);
+				else
+					pushList.remove(i);
+			}
+				
+			if( event.propagate ) {
+				event.propagate = false;
+				continue;
+			}
+			break;
+		}
+		if( checkOver && currentOver != null ) {
+			event.kind = EOut;
+			currentOver.handleEvent(event);
+			event.kind = EMove;
+			currentOver = null;
+		}
+	}
+	
+	public function checkEvents() {
+		if( pendingEvents == null ) {
+			if( interactive.length == 0 )
+				return;
+			pendingEvents = new flash.Vector();
+		}
+		var old = pendingEvents;
+		if( old.length == 0 )
+			return;
+		pendingEvents = null;
+		for( e in old ) {
+			if( currentDrag != null ) {
+				currentDrag(e);
+				if( e.cancel )
+					continue;
+			}
+			emitEvent(e);
+			if( e.kind == ERelease && pushList.length > 0 ) {
+				for( i in pushList ) {
+					// relX/relY is not correct here
+					i.handleEvent(e);
+				}
+				pushList = new Array();
+			}
+		}
+		if( interactive.length > 0 )
+			pendingEvents = new flash.Vector();
+	}
+	
+	public function startDrag( f : Event -> Void ) {
+		currentDrag = f;
+	}
+	
+	public function stopDrag() {
+		currentDrag = null;
+	}
+	
+	@:allow(h2d)
+	function addEventTarget(i) {
+		interactive.push(i);
+	}
+	
+	@:allow(h2d)
+	function removeEventTarget(i) {
+		for( k in 0...interactive.length )
+			if( interactive[k] == i ) {
+				interactive.splice(k, 1);
+				break;
+			}
+	}
+
+	override function updatePos() {
+		// don't take the parent into account
+		if( !posChanged )
+			return;
+			
+		// init matrix without rotation
+		matA = scaleX;
+		matB = 0;
+		matC = 0;
+		matD = scaleY;
+		absX = x;
+		absY = y;
+		
+		// adds a pixels-to-viewport transform
+		var w = 2 / width;
+		var h = -2 / height;
+		absX = absX * w - 1;
+		absY = absY * h + 1;
+		matA *= w;
+		matB *= h;
+		matC *= w;
+		matD *= h;
+		
+		// perform final rotation around center
+		if( rotation != 0 ) {
+			var cr = Math.cos(rotation * Sprite.ROT2RAD);
+			var sr = Math.sin(rotation * Sprite.ROT2RAD);
+			var tmpA = matA * cr + matB * sr;
+			var tmpB = matA * -sr + matB * cr;
+			var tmpC = matC * cr + matD * sr;
+			var tmpD = matC * -sr + matD * cr;
+			var tmpX = absX * cr + absY * sr;
+			var tmpY = absX * -sr + absY * cr;
+			matA = tmpA;
+			matB = tmpB;
+			matC = tmpC;
+			matD = tmpD;
+			absX = tmpX;
+			absY = tmpY;
+		}
+	}
+	
+	public function dispose() {
+		if( allocated )
+			onDelete();
+	}
+	
+	override public function render( engine : h3d.Engine ) {
+		if( !allocated )
+			onAlloc();
+		if( !fixedSize && (width != engine.width || height != engine.height) ) {
+			width = engine.width;
+			height = engine.height;
+			posChanged = true;
+		}
+		super.render(engine);
+	}
+	
+	
+}

+ 184 - 0
h2d/Sprite.hx

@@ -0,0 +1,184 @@
+package h2d;
+
+@:allow(h2d.Tools)
+class Sprite {
+
+	static inline var ROT2RAD = -0.017453292519943295769236907684886;
+	
+	var childs : Array<Sprite>;
+	public var parent(default,null) : Sprite;
+	
+	public var x(default,set) : Float;
+	public var y(default, set) : Float;
+	public var scaleX(default,set) : Float;
+	public var scaleY(default,set) : Float;
+	public var rotation(default,set) : Float;
+	public var blendMode(default,set) : BlendMode;
+
+	var matA : Float;
+	var matB : Float;
+	var matC : Float;
+	var matD : Float;
+	var absX : Float;
+	var absY : Float;
+	
+	var posChanged : Bool;
+	var allocated : Bool;
+	
+	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;
+		posChanged = false;
+		childs = [];
+		blendMode = Normal;
+		if( parent != null )
+			parent.addChild(this);
+	}
+	
+	function set_blendMode(b) {
+		this.blendMode = b;
+		return b;
+	}
+	
+	public function getAbsolutePos( x = 0., y = 0. ) {
+		updatePos();
+		return { x : x * matA + y * matC + absX, y : x * matB + y * matD + absY };
+	}
+	
+	public function addChild( s : Sprite ) {
+		var p = this;
+		while( p != null ) {
+			if( p == s ) throw "Recursive addChild";
+			p = p.parent;
+		}
+		if( s.parent != null )
+			s.parent.childs.remove(s);
+		childs.push(s);
+		s.parent = this;
+		s.posChanged = true;
+		// ensure that proper alloc/delete is done if we change parent
+		if( allocated ) {
+			if( !s.allocated )
+				s.onAlloc();
+		} else if( s.allocated )
+			s.onDelete();
+	}
+	
+	// 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) ) {
+			s.parent = null;
+			if( s.allocated ) s.onDelete();
+		}
+	}
+	
+	// shortcut for parent.removeChild
+	public inline function remove() {
+		if( this != null && parent != null ) parent.removeChild(this);
+	}
+	
+	function draw( engine : h3d.Engine ) {
+	}
+	
+	function updatePos() {
+		if( parent == null ) {
+			if( posChanged ) {
+				var cr, sr;
+				if( rotation == 0 ) {
+					cr = 1.; sr = 0.;
+					matA = scaleX;
+					matB = 0;
+					matC = 0;
+					matD = scaleY;
+				} else {
+					cr = Math.cos(rotation * ROT2RAD);
+					sr = Math.sin(rotation * ROT2RAD);
+					matA = scaleX * cr;
+					matB = scaleX * -sr;
+					matC = scaleY * sr;
+					matD = scaleY * cr;
+				}
+				absX = x;
+				absY = y;
+			}
+		} else {
+			if( parent.posChanged )
+				posChanged = true;
+			if( posChanged ) {
+				// M(rel) = S . R . T
+				// M(abs) = M(rel) . P(abs)
+				if( rotation == 0 ) {
+					matA = scaleX * parent.matA;
+					matB = scaleX * parent.matB;
+					matC = scaleY * parent.matC;
+					matD = scaleY * parent.matD;
+				} else {
+					var cr = Math.cos(rotation * ROT2RAD);
+					var sr = Math.sin(rotation * ROT2RAD);
+					var tmpA = scaleX * cr;
+					var tmpB = scaleX * -sr;
+					var tmpC = scaleY * sr;
+					var tmpD = scaleY * cr;
+					matA = tmpA * parent.matA + tmpB * parent.matC;
+					matB = tmpA * parent.matB + tmpB * parent.matD;
+					matC = tmpC * parent.matA + tmpD * parent.matC;
+					matD = tmpC * parent.matB + tmpD * parent.matD;
+				}
+				absX = x * parent.matA + y * parent.matC + parent.absX;
+				absY = x * parent.matB + y * parent.matD + parent.absY;
+			}
+		}
+	}
+	
+	function render( engine : h3d.Engine ) {
+		updatePos();
+		draw(engine);
+		for( c in childs )
+			c.render(engine);
+		posChanged = false;
+	}
+	
+	inline function set_x(v) {
+		x = v;
+		posChanged = true;
+		return v;
+	}
+
+	inline function set_y(v) {
+		y = v;
+		posChanged = true;
+		return v;
+	}
+	
+	inline function set_scaleX(v) {
+		scaleX = v;
+		posChanged = true;
+		return v;
+	}
+	
+	inline function set_scaleY(v) {
+		scaleY = v;
+		posChanged = true;
+		return v;
+	}
+	
+	inline function set_rotation(v) {
+		rotation = v;
+		posChanged = true;
+		return v;
+	}
+	
+}

+ 148 - 0
h2d/SpriteBatch.hx

@@ -0,0 +1,148 @@
+package h2d;
+
+private class BatchShader extends h3d.Shader {
+	static var SRC = {
+		var input : {
+			pos : Float2,
+			uva : Float3,
+		};
+		var tuva : Float3;
+		function vertex( mat1 : Float3, mat2 : Float3 ) {
+			var tmp : Float4;
+			tmp.x = pos.xyw.dp3(mat1);
+			tmp.y = pos.xyw.dp3(mat2);
+			tmp.z = 0;
+			tmp.w = 1;
+			out = tmp;
+			tuva = uva;
+		}
+		function fragment( tex : Texture ) {
+			var c = tex.get(tuva.xy, nearest);
+			c.a *= tuva.z;
+			out = c;
+		}
+	}
+}
+
+@:allow(h2d.SpriteBatch)
+class BatchElement {
+	public var x : Float;
+	public var y : Float;
+	public var alpha : Float;
+	public var t : Tile;
+	public var batch(default, null) : SpriteBatch;
+	
+	var prev : BatchElement;
+	var next : BatchElement;
+	
+	function new(b,t) {
+		x = 0; y = 0; alpha = 1;
+		this.batch = b;
+		this.t = t;
+	}
+	
+	public inline function remove() {
+		batch.delete(this);
+	}
+	
+}
+
+class SpriteBatch extends Sprite {
+
+	public var tile : Tile;
+	var first : BatchElement;
+	var last : BatchElement;
+	var tmpBuf : flash.Vector<Float>;
+	var material : h3d.mat.Material;
+	
+	static var SHADER = null;
+	
+	public function new(t,?parent) {
+		tile = t;
+		if( SHADER == null )
+			SHADER = new BatchShader();
+		material = new h3d.mat.Material(SHADER);
+		material.depth(false, Always);
+		super(parent);
+	}
+	
+	override function set_blendMode(b) {
+		Tools.setBlendMode(material, b);
+		this.blendMode = b;
+		return b;
+	}
+	
+	public function alloc(t) {
+		var e = new BatchElement(this, t);
+		if( first == null )
+			first = last = e;
+		else {
+			last.next = e;
+			e.prev = last;
+			last = e;
+		}
+		return e;
+	}
+	
+	@:allow(h2d.BatchElement)
+	function delete(e : BatchElement) {
+		if( e.prev == null ) {
+			if( first == e )
+				first = e.next;
+		} else
+			e.prev.next = e.next;
+		if( e.next == null ) {
+			if( last == e )
+				last = e.prev;
+		} else
+			e.next.prev = e.prev;
+	}
+	
+	override function draw( engine : h3d.Engine ) {
+		if( first == null )
+			return;
+		if( tmpBuf == null ) tmpBuf = new flash.Vector();
+		var pos = 0;
+		var e = first;
+		var tmp = tmpBuf;
+		while( e != null ) {
+			var t = e.t;
+			var sx = e.x + t.dx;
+			var sy = e.y + t.dy;
+			tmp[pos++] = sx;
+			tmp[pos++] = sy;
+			tmp[pos++] = t.u;
+			tmp[pos++] = t.v;
+			tmp[pos++] = e.alpha;
+			tmp[pos++] = sx + t.width + 0.1;
+			tmp[pos++] = sy;
+			tmp[pos++] = t.u2;
+			tmp[pos++] = t.v;
+			tmp[pos++] = e.alpha;
+			tmp[pos++] = sx;
+			tmp[pos++] = sy + t.height + 0.1;
+			tmp[pos++] = t.u;
+			tmp[pos++] = t.v2;
+			tmp[pos++] = e.alpha;
+			tmp[pos++] = sx + t.width + 0.1;
+			tmp[pos++] = sy + t.height + 0.1;
+			tmp[pos++] = t.u2;
+			tmp[pos++] = t.v2;
+			tmp[pos++] = e.alpha;
+			e = e.next;
+		}
+		var buffer = engine.mem.allocVector(tmpBuf, 5, 4);
+		var shader = SHADER;
+		shader.tex = tile.tex;
+		shader.mat1 = new h3d.Vector(matA, matC, absX);
+		shader.mat2 = new h3d.Vector(matB, matD, absY);
+		engine.selectMaterial(material);
+		engine.renderQuads(buffer);
+		buffer.dispose();
+	}
+	
+	public inline function isEmpty() {
+		return first == null;
+	}
+	
+}

+ 52 - 0
h2d/Text.hx

@@ -0,0 +1,52 @@
+package h2d;
+
+class Text extends Sprite {
+
+	public var font(default, null) : Font;
+	public var text(default, set) : String;
+	public var textColor(default, set) : Int;
+	public var alpha(default, set) : Float;
+	
+	var glyphs : TileGroup;
+	
+	public function new( font : Font, ?parent ) {
+		super(parent);
+		this.font = font;
+		glyphs = new TileGroup(font, this);
+		text = "";
+		textColor = 0xFFFFFF;
+		alpha = 1.0;
+	}
+	
+	override function onRemove() {
+		glyphs.onRemove();
+	}
+	
+	function set_text(t) {
+		this.text = t;
+		glyphs.reset();
+		var letters = font.elements[0];
+		var x = 0, y = 0;
+		for( i in 0...t.length ) {
+			var cc = t.charCodeAt(i);
+			var e = letters[cc];
+			if( e == null ) continue;
+			glyphs.add(x, y, e);
+			x += e.w + 1;
+		}
+		return t;
+	}
+	
+	function set_textColor(c) {
+		this.textColor = c;
+		glyphs.color.loadInt(c, alpa);
+		return c;
+	}
+	
+	function set_alpha(a) {
+		this.alpha = a;
+		glyphs.color.a = a;
+		return a;
+	}
+
+}

+ 151 - 0
h2d/Tile.hx

@@ -0,0 +1,151 @@
+package h2d;
+
+@:allow(h2d)
+class Tile {
+	
+	static inline var EPSILON_PIXEL = 0.00001;
+	
+	var tex : h3d.mat.Texture;
+	
+	var u : Float;
+	var v : Float;
+	var u2 : Float;
+	var v2 : Float;
+	
+	public var dx : Int;
+	public var dy : Int;
+	public var x(default,null) : Int;
+	public var y(default,null) : Int;
+	public var width(default,null) : Int;
+	public var height(default,null) : Int;
+	
+	function new(tex, x, y, w, h, dx=0, dy=0) {
+		this.tex = tex;
+		this.x = x;
+		this.y = y;
+		this.width = w;
+		this.height = h;
+		this.dx = dx;
+		this.dy = dy;
+		if( tex != null ) setTexture(tex);
+	}
+	
+	public function setTexture(tex) {
+		this.tex = tex;
+		this.u = x / tex.width;
+		this.v = y / tex.height;
+		this.u2 = (x + width - EPSILON_PIXEL) / tex.width;
+		this.v2 = (y + height - EPSILON_PIXEL) / tex.height;
+	}
+	
+	public function sub( x, y, w, h, dx = 0, dy = 0 ) {
+		return new Tile(tex, this.x + x, this.y + y, w, h, dx, dy);
+	}
+	
+	public static function fromBitmap( bmp : flash.display.BitmapData, freeBitmap = true ) {
+		var w = 1, h = 1;
+		while( w < bmp.width )
+			w <<= 1;
+		while( h < bmp.height )
+			h <<= 1;
+		var tex = h3d.Engine.getCurrent().mem.allocTexture(w, h);
+		if( w != bmp.width || h != bmp.height ) {
+			var bmp2 = new flash.display.BitmapData(w, h, true, 0);
+			var p0 = new flash.geom.Point(0, 0);
+			bmp2.copyPixels(bmp, bmp.rect, p0, bmp, p0, true);
+			tex.upload(bmp2);
+			bmp2.dispose();
+		} else
+			tex.upload(bmp);
+		var t = new Tile(tex, 0, 0, bmp.width, bmp.height);
+		if( freeBitmap )
+			bmp.dispose();
+		return t;
+	}
+
+	public static function autoCut( bmp : flash.display.BitmapData, size : Int, freeBitmap = true ) {
+		var colorBG = bmp.getPixel32(bmp.width - 1, bmp.height - 1);
+		var tl = new Array();
+		var w = 1, h = 1;
+		while( w < bmp.width )
+			w <<= 1;
+		while( h < bmp.height )
+			h <<= 1;
+		var tex = h3d.Engine.getCurrent().mem.allocTexture(w, h);
+		for( y in 0...Std.int(bmp.height / size) ) {
+			var a = [];
+			tl[y] = a;
+			for( x in 0...Std.int(bmp.width / size) ) {
+				var sz = isEmpty(bmp, x * size, y * size, size, colorBG);
+				if( sz == null )
+					break;
+				a.push(new Tile(tex,x*size+sz.dx, y*size+sz.dy, sz.w, sz.h, sz.dx, sz.dy));
+			}
+		}
+		if( w != bmp.width || h != bmp.height ) {
+			var bmp2 = new flash.display.BitmapData(w, h, true, 0);
+			var p0 = new flash.geom.Point(0, 0);
+			bmp2.copyPixels(bmp, bmp.rect, p0, bmp, p0, true);
+			tex.upload(bmp2);
+			bmp2.dispose();
+		} else
+			tex.upload(bmp);
+		var main = new Tile(tex, 0, 0, bmp.width, bmp.height);
+		if( freeBitmap )
+			bmp.dispose();
+		return { main : main, tiles : tl };
+	}
+	
+	public static function fromSprites( sprites : Array<flash.display.Sprite> ) {
+		var tmp = [];
+		var width = 0;
+		var height = 0;
+		for( s in sprites ) {
+			var g = s.getBounds(s);
+			var dx = Math.floor(g.left);
+			var dy = Math.floor(g.top);
+			var w = Math.ceil(g.right) - dx;
+			var h = Math.ceil(g.bottom) - dy;
+			tmp.push( { s : s, x : width, dx : dx, dy : dy, w : w, h : h } );
+			width += w;
+			if( height < h ) height = h;
+		}
+		var rw = 1, rh = 1;
+		while( rw < width )
+			rw <<= 1;
+		while( rh < height )
+			rh <<= 1;
+		var bmp = new flash.display.BitmapData(rw, rh, true, 0);
+		var m = new flash.geom.Matrix();
+		for( t in tmp ) {
+			m.tx = t.x-t.dx;
+			m.ty = -t.dy;
+			bmp.draw(t.s, m);
+		}
+		var main = fromBitmap(bmp);
+		var tiles = [];
+		for( t in tmp )
+			tiles.push(main.sub(t.x, 0, t.w, t.h, t.dx, t.dy));
+		return tiles;
+	}
+	
+	static function isEmpty( b : flash.display.BitmapData, px, py, size, bg : UInt ) {
+		var empty = true;
+		var xmin = size, ymin = size, xmax = 0, ymax = 0;
+		for( x in 0...size )
+			for( y in 0...size ) {
+				var color = b.getPixel32(x+px, y+py);
+				if( color != bg ) {
+					empty = false;
+					if( x < xmin ) xmin = x;
+					if( y < ymin ) ymin = y;
+					if( x > xmax ) xmax = x;
+					if( y > ymax ) ymax = y;
+				}
+				if( Std.int(color) == 0xFFFF00FF )
+					b.setPixel32(x+px, y+py, 0);
+			}
+		return empty ? null : { dx : xmin, dy : ymin, w : xmax - xmin + 1, h : ymax - ymin + 1 };
+	}
+	
+}

+ 154 - 0
h2d/TileColorGroup.hx

@@ -0,0 +1,154 @@
+package h2d;
+
+private class TileShader2D extends h3d.Shader {
+	static var SRC = {
+		var input : {
+			pos : Float2,
+			uv : Float2,
+			color : Float4,
+		};
+		var tuv : Float2;
+		var tcolor : Float4;
+		function vertex( mat1 : Float3, mat2 : Float3 ) {
+			var tmp : Float4;
+			tmp.x = pos.xyw.dp3(mat1);
+			tmp.y = pos.xyw.dp3(mat2);
+			tmp.z = 0;
+			tmp.w = 1;
+			out = tmp;
+			tcolor = color;
+			tuv = uv;
+		}
+		function fragment( tex : Texture ) {
+			out = tex.get(tuv, nearest) * tcolor;
+		}
+	}
+}
+
+private class TileLayerContent extends h3d.prim.Primitive {
+
+	var tmp : flash.Vector<Float>;
+	var pos : Int;
+	
+	public function new() {
+		reset();
+	}
+	
+	public function reset() {
+		tmp = new flash.Vector();
+		pos = 0;
+		if( buffer != null ) buffer.dispose();
+		buffer = null;
+	}
+	
+	public function add( x : Int, y : Int, r : Float, g : Float, b : Float, a : Float, t : Tile ) {
+		var sx = x + t.dx;
+		var sy = y + t.dy;
+		tmp[pos++] = sx;
+		tmp[pos++] = sy;
+		tmp[pos++] = t.u;
+		tmp[pos++] = t.v;
+		tmp[pos++] = r;
+		tmp[pos++] = g;
+		tmp[pos++] = b;
+		tmp[pos++] = a;
+		tmp[pos++] = sx + t.width + 0.1;
+		tmp[pos++] = sy;
+		tmp[pos++] = t.u2;
+		tmp[pos++] = t.v;
+		tmp[pos++] = r;
+		tmp[pos++] = g;
+		tmp[pos++] = b;
+		tmp[pos++] = a;
+		tmp[pos++] = sx;
+		tmp[pos++] = sy + t.height + 0.1;
+		tmp[pos++] = t.u;
+		tmp[pos++] = t.v2;
+		tmp[pos++] = r;
+		tmp[pos++] = g;
+		tmp[pos++] = b;
+		tmp[pos++] = a;
+		tmp[pos++] = sx + t.width + 0.1;
+		tmp[pos++] = sy + t.height + 0.1;
+		tmp[pos++] = t.u2;
+		tmp[pos++] = t.v2;
+		tmp[pos++] = r;
+		tmp[pos++] = g;
+		tmp[pos++] = b;
+		tmp[pos++] = a;
+	}
+	
+	override public function alloc(engine:h3d.Engine) {
+		if( tmp == null ) reset();
+		buffer = engine.mem.allocVector(tmp, 8, 4);
+	}
+
+	override function render(engine) {
+		if( buffer == null ) alloc(engine);
+		engine.renderIndexes(buffer, engine.mem.quadIndexes, 2);
+	}
+	
+}
+
+class TileColorGroup extends Sprite {
+	
+	static var SHADER : TileShader2D = null;
+
+	var object : h3d.Object;
+	var content : TileLayerContent;
+	var curColor : h3d.Color;
+	
+	public var tile : Tile;
+	
+	public function new(t,?parent) {
+		super(parent);
+		tile = t;
+		curColor = new h3d.Color(1, 1, 1, 1);
+		content = new TileLayerContent();
+		if( SHADER == null )
+			SHADER = new TileShader2D();
+		object = new h3d.Object(content, new h3d.mat.Material(SHADER));
+		object.material.depth(false, Always);
+		object.material.culling = None;
+		setTransparency(true);
+	}
+	
+	public function reset() {
+		content.reset();
+	}
+	
+	override function onDelete() {
+		object.primitive.dispose();
+		super.onDelete();
+	}
+	
+	public function setColor( rgb : Int, alpha = 1.0 ) {
+		curColor.r = ((rgb >> 16) & 0xFF) / 255;
+		curColor.g = ((rgb >> 8) & 0xFF) / 255;
+		curColor.b = (rgb & 0xFF) / 255;
+		curColor.a = alpha;
+	}
+	
+	public inline function add(x, y, t) {
+		content.add(x, y, curColor.r, curColor.g, curColor.b, curColor.a, t);
+	}
+	
+	public inline function addColor(x, y, r, g, b, a, t) {
+		content.add(x, y, r, g, b, a, t);
+	}
+	
+	public function setTransparency(a) {
+		if( a )
+			object.material.blend(SrcAlpha, OneMinusSrcAlpha);
+		else
+			object.material.blend(One,Zero);
+	}
+	
+	override function draw(engine:h3d.Engine) {
+		var shader = SHADER;
+		shader.tex = tile.tex;
+		shader.mat1 = new h3d.Vector(matA, matC, absX);
+		shader.mat2 = new h3d.Vector(matB, matD, absY);
+		object.render(engine);
+	}
+}

+ 130 - 0
h2d/TileGroup.hx

@@ -0,0 +1,130 @@
+package h2d;
+
+private class TileShader2D extends h3d.Shader {
+	static var SRC = {
+		var input : {
+			pos : Float2,
+			uv : Float2,
+		};
+		var tuv : Float2;
+		function vertex( mat1 : Float3, mat2 : Float3 ) {
+			var tmp : Float4;
+			tmp.x = pos.xyw.dp3(mat1);
+			tmp.y = pos.xyw.dp3(mat2);
+			tmp.z = 0;
+			tmp.w = 1;
+			out = tmp;
+			tuv = uv;
+		}
+		function fragment( tex : Texture, color : Float4 ) {
+			out = tex.get(tuv, nearest) * color;
+		}
+	}
+}
+
+private class TileLayerContent extends h3d.prim.Primitive {
+
+	var tmp : flash.Vector<Float>;
+	var pos : Int;
+	
+	public function new() {
+		reset();
+	}
+	
+	public function reset() {
+		tmp = new flash.Vector();
+		pos = 0;
+		if( buffer != null ) buffer.dispose();
+		buffer = null;
+	}
+	
+	public function add( x : Int, y : Int, t : Tile ) {
+		var sx = x + t.dx;
+		var sy = y + t.dy;
+		var sx2 = sx + t.width + 0.1;
+		var sy2 = sy + t.height + 0.1;
+		tmp[pos++] = sx;
+		tmp[pos++] = sy;
+		tmp[pos++] = t.u;
+		tmp[pos++] = t.v;
+		tmp[pos++] = sx2;
+		tmp[pos++] = sy;
+		tmp[pos++] = t.u2;
+		tmp[pos++] = t.v;
+		tmp[pos++] = sx;
+		tmp[pos++] = sy2;
+		tmp[pos++] = t.u;
+		tmp[pos++] = t.v2;
+		tmp[pos++] = sx2;
+		tmp[pos++] = sy2;
+		tmp[pos++] = t.u2;
+		tmp[pos++] = t.v2;
+	}
+	
+	override public function alloc(engine:h3d.Engine) {
+		if( tmp == null ) reset();
+		buffer = engine.mem.allocVector(tmp, 4, 4);
+	}
+
+	override function render(engine) {
+		if( buffer == null ) alloc(engine);
+		engine.renderIndexes(buffer, engine.mem.quadIndexes, 2);
+	}
+	
+}
+
+class TileGroup extends Sprite {
+	
+	static var SHADER : TileShader2D = null;
+
+	var object : h3d.Object;
+	var content : TileLayerContent;
+	
+	public var tile : Tile;
+	public var color(default, null) : h3d.Color;
+	
+	public function new(t,?parent) {
+		tile = t;
+		color = new h3d.Color(1, 1, 1, 1);
+		content = new TileLayerContent();
+		if( SHADER == null )
+			SHADER = new TileShader2D();
+		object = new h3d.Object(content, new h3d.mat.Material(SHADER));
+		object.material.depth(false, Always);
+		object.material.culling = None;
+		super(parent);
+	}
+	
+	public function reset() {
+		content.reset();
+	}
+	
+	override function onDelete() {
+		object.primitive.dispose();
+		super.onDelete();
+	}
+	
+	public inline function add(x, y, t) {
+		content.add(x, y, t);
+	}
+	
+	function set_color(c) {
+		color = c;
+		return c;
+	}
+	
+	override function set_blendMode(b) {
+		this.blendMode = b;
+		Tools.setBlendMode(object.material, b);
+		return b;
+	}
+	
+	override function draw(engine:h3d.Engine) {
+		var shader = SHADER;
+		shader.tex = tile.tex;
+		shader.mat1 = new h3d.Vector(matA, matC, absX);
+		shader.mat2 = new h3d.Vector(matB, matD, absY);
+		shader.color = color.toVector();
+		object.render(engine);
+	}
+}

+ 94 - 0
h2d/Tools.hx

@@ -0,0 +1,94 @@
+package h2d;
+
+
+private class BitmapShader extends h3d.Shader {
+	static var SRC = {
+		var input : {
+			pos : Float2,
+		};
+		var tuv : Float2;
+		function vertex( size : Float3, mat1 : Float3, mat2 : Float3, uvScale : Float2, uvPos : Float2 ) {
+			var tmp : Float4;
+			var spos = pos.xyw * size;
+			tmp.x = spos.dp3(mat1);
+			tmp.y = spos.dp3(mat2);
+			tmp.z = 0;
+			tmp.w = 1;
+			out = tmp;
+			tuv = pos * uvScale + uvPos;
+		}
+		function fragment( tex : Texture, color : Float4 ) {
+			out = tex.get(tuv, nearest) * color;
+		}
+	}
+}
+
+class Tools {
+
+	static var BITMAP_OBJ : h3d.CustomObject<BitmapShader> = null;
+	static var TMP_VECTOR = new h3d.Vector();
+		
+	@:allow(h2d)
+	static function drawTile( engine : h3d.Engine, spr : Sprite, tile : Tile, color : h3d.Color, blendMode : BlendMode ) {
+		var b = BITMAP_OBJ;
+		if( b == null ) {
+			var p = new h3d.prim.Quads([
+				new h3d.Point(0, 0),
+				new h3d.Point(1, 0),
+				new h3d.Point(0, 1),
+				new h3d.Point(1, 1),
+			]);
+			b = new h3d.CustomObject(p, new BitmapShader());
+			b.material.culling = None;
+			b.material.depth(false, Always);
+			BITMAP_OBJ = b;
+		}
+		setBlendMode(b.material, blendMode);
+		var tmp = TMP_VECTOR;
+		tmp.x = tile.width;
+		tmp.y = tile.height;
+		tmp.z = 1;
+		b.shader.size = tmp;
+		tmp.x = spr.matA;
+		tmp.y = spr.matC;
+		tmp.z = spr.absX + tile.dx * spr.matA + tile.dy * spr.matC;
+		b.shader.mat1 = tmp;
+		tmp.x = spr.matB;
+		tmp.y = spr.matD;
+		tmp.z = spr.absY + tile.dx * spr.matB + tile.dy * spr.matD;
+		b.shader.mat2 = tmp;
+		tmp.x = tile.u;
+		tmp.y = tile.v;
+		b.shader.uvPos = tmp;
+		tmp.x = tile.u2 - tile.u;
+		tmp.y = tile.v2 - tile.v;
+		b.shader.uvScale = tmp;
+		if( color == null ) {
+			tmp.x = 1;
+			tmp.y = 1;
+			tmp.z = 1;
+			tmp.w = 1;
+			b.shader.color = tmp;
+		} else
+			b.shader.color = color.toVector();
+		b.shader.tex = tile.tex;
+		b.render(engine);
+	}
+
+	@:allow(h2d)
+	static function setBlendMode( mat : h3d.mat.Material, b : BlendMode ) {
+		switch( b ) {
+		case Normal:
+			mat.blend(SrcAlpha, OneMinusSrcAlpha);
+		case None:
+			mat.blend(One, Zero);
+		case Add:
+			mat.blend(SrcAlpha, One);
+		case Multiply:
+			mat.blend(DstColor, OneMinusSrcAlpha);
+		case Erase:
+			mat.blend(Zero, OneMinusSrcAlpha);
+		}
+	}
+	
+}