Explorar o código

Merge branch 'master' of https://github.com/HeapsIO/heaps

trethaller %!s(int64=6) %!d(string=hai) anos
pai
achega
0e5e9de39c

+ 0 - 1
all.hxml

@@ -1,7 +1,6 @@
 -lib heaps
 -lib heaps
 -lib castle
 -lib castle
 -lib hxbit
 -lib hxbit
--lib stb_ogg_sound
 --macro include('h3d')
 --macro include('h3d')
 --macro include('h2d',true,['h2d.domkit'])
 --macro include('h2d',true,['h2d.domkit'])
 --macro include('hxsl',true,['hxsl.Macros','hxsl.CacheFile','hxsl.CacheFileBuilder','hxsl.Checker'])
 --macro include('hxsl',true,['hxsl.Macros','hxsl.CacheFile','hxsl.CacheFileBuilder','hxsl.Checker'])

+ 1 - 1
h2d/Drawable.hx

@@ -149,7 +149,7 @@ class Drawable extends Object {
 	**/
 	**/
 	public function getShader< T:hxsl.Shader >( stype : Class<T> ) : T {
 	public function getShader< T:hxsl.Shader >( stype : Class<T> ) : T {
 		if (shaders != null) for( s in shaders ) {
 		if (shaders != null) for( s in shaders ) {
-			var s = Std.instance(s, stype);
+			var s = hxd.impl.Api.downcast(s, stype);
 			if( s != null )
 			if( s != null )
 				return s;
 				return s;
 		}
 		}

+ 6 - 3
h2d/Flow.hx

@@ -452,6 +452,7 @@ class Flow extends Object {
 
 
 	override function addChildAt( s, pos ) {
 	override function addChildAt( s, pos ) {
 		if( background != null ) pos++;
 		if( background != null ) pos++;
+		if( interactive != null ) pos++;
 		var fp = getProperties(s);
 		var fp = getProperties(s);
 		super.addChildAt(s, pos);
 		super.addChildAt(s, pos);
 		if( fp == null ) fp = new FlowProperties(s) else properties.remove(fp);
 		if( fp == null ) fp = new FlowProperties(s) else properties.remove(fp);
@@ -560,9 +561,11 @@ class Flow extends Object {
 			return b;
 			return b;
 		if( b ) {
 		if( b ) {
 			if( interactive == null ) {
 			if( interactive == null ) {
-				interactive = new h2d.Interactive(0, 0, this);
+				var interactive = new h2d.Interactive(0, 0);
+				addChildAt(interactive,0);
+				this.interactive = interactive;
 				interactive.cursor = Default;
 				interactive.cursor = Default;
-				properties[properties.length - 1].isAbsolute = true;
+				getProperties(interactive).isAbsolute = true;
 				if( !needReflow ) {
 				if( !needReflow ) {
 					interactive.width = calculatedWidth;
 					interactive.width = calculatedWidth;
 					interactive.height = calculatedHeight;
 					interactive.height = calculatedHeight;
@@ -584,7 +587,7 @@ class Flow extends Object {
 			if( background == null ) {
 			if( background == null ) {
 				var background = new h2d.ScaleGrid(t, borderWidth, borderHeight);
 				var background = new h2d.ScaleGrid(t, borderWidth, borderHeight);
 				addChildAt(background, 0);
 				addChildAt(background, 0);
-				properties[0].isAbsolute = true;
+				getProperties(background).isAbsolute = true;
 				this.background = background;
 				this.background = background;
 				if( !needReflow ) {
 				if( !needReflow ) {
 					background.width = Math.ceil(calculatedWidth);
 					background.width = Math.ceil(calculatedWidth);

+ 1 - 1
h2d/Interactive.hx

@@ -87,7 +87,7 @@ class Interactive extends Drawable implements hxd.SceneEvents.Interactive {
 		parentMask = null;
 		parentMask = null;
 		var p = parent;
 		var p = parent;
 		while( p != null ) {
 		while( p != null ) {
-			var m = Std.instance(p, Mask);
+			var m = hxd.impl.Api.downcast(p, Mask);
 			if( m != null ) {
 			if( m != null ) {
 				parentMask = m;
 				parentMask = m;
 				break;
 				break;

+ 2 - 2
h2d/KeyFrames.hx

@@ -97,7 +97,7 @@ class KeyFrames extends Mask {
 
 
 	public function set_smooth( v : Bool ) : Bool {
 	public function set_smooth( v : Bool ) : Bool {
 		for( l in layers ){
 		for( l in layers ){
-			var bmp = Std.instance(l.spr, h2d.Bitmap);
+			var bmp = hxd.impl.Api.downcast(l.spr, h2d.Bitmap);
 			if( bmp != null )
 			if( bmp != null )
 				bmp.smooth = v;
 				bmp.smooth = v;
 		}
 		}
@@ -201,7 +201,7 @@ class KeyFrames extends Mask {
 
 
 		switch( f.property ) {
 		switch( f.property ) {
 		case AnchorPoint:
 		case AnchorPoint:
-			var bmp = Std.instance(l.spr, h2d.Bitmap);
+			var bmp = hxd.impl.Api.downcast(l.spr, h2d.Bitmap);
 			if( bmp != null ) {
 			if( bmp != null ) {
 				bmp.tile.dx = -calcValue(0);
 				bmp.tile.dx = -calcValue(0);
 				bmp.tile.dy = -calcValue(1);
 				bmp.tile.dy = -calcValue(1);

+ 1 - 1
h2d/Mask.hx

@@ -27,7 +27,7 @@ class Mask extends Object {
 		parentMask = null;
 		parentMask = null;
 		var p = parent;
 		var p = parent;
 		while( p != null ) {
 		while( p != null ) {
-			var m = Std.instance(p, Mask);
+			var m = hxd.impl.Api.downcast(p, Mask);
 			if( m != null ) {
 			if( m != null ) {
 				parentMask = m;
 				parentMask = m;
 				break;
 				break;

+ 1 - 1
h2d/Object.hx

@@ -300,7 +300,7 @@ class Object {
 	public function getScene() : Scene {
 	public function getScene() : Scene {
 		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 hxd.impl.Api.downcast(p, Scene);
 	}
 	}
 
 
 	function set_visible(b) {
 	function set_visible(b) {

+ 29 - 10
h2d/RenderContext.hx

@@ -93,7 +93,7 @@ class RenderContext extends h3d.impl.RenderContext {
 		// todo : we might prefer to auto-detect this by running a test and capturing its output
 		// todo : we might prefer to auto-detect this by running a test and capturing its output
 		baseShader.pixelAlign = #if flash true #else false #end;
 		baseShader.pixelAlign = #if flash true #else false #end;
 		baseShader.halfPixelInverse.set(0.5 / engine.width, 0.5 / engine.height);
 		baseShader.halfPixelInverse.set(0.5 / engine.width, 0.5 / engine.height);
-		baseShader.viewport.set( -scene.width * 0.5, -scene.height * 0.5, 2 / scene.width, -2 * baseFlipY / scene.height);
+		baseShader.viewport.set( -scene.width * 0.5 - scene.offsetX, -scene.height * 0.5 - scene.offsetY, 2 / scene.width * scene.ratioX, -2 * baseFlipY / scene.height * scene.ratioY);
 		baseShader.filterMatrixA.set(1, 0, 0);
 		baseShader.filterMatrixA.set(1, 0, 0);
 		baseShader.filterMatrixB.set(0, 1, 0);
 		baseShader.filterMatrixB.set(0, 1, 0);
 		baseShaderList.next = null;
 		baseShaderList.next = null;
@@ -190,14 +190,33 @@ class RenderContext extends h3d.impl.RenderContext {
 
 
 		if( restore ) {
 		if( restore ) {
 			var tinf = targetsStack[targetsStackIndex - 1];
 			var tinf = targetsStack[targetsStackIndex - 1];
-			var t = tinf == null ? null : tinf.t;
-			var startX = tinf == null ? 0 : tinf.x;
-			var startY = tinf == null ? 0 : tinf.y;
-			var width = tinf == null ? scene.width : tinf.w;
-			var height = tinf == null ? scene.height : tinf.h;
+			var t : h3d.mat.Texture;
+			var startX : Int, startY : Int, width : Int, height : Int;
+			var ratioX : Float, ratioY : Float, offsetX : Float, offsetY : Float;
+			if ( tinf == null ) {
+				t = null;
+				startX = 0;
+				startY = 0;
+				width = scene.width;
+				height = scene.height;
+				ratioX = scene.ratioX;
+				ratioY = scene.ratioY;
+				offsetX = scene.offsetX;
+				offsetY = scene.offsetY;
+			} else {
+				t = tinf.t;
+				startX = tinf.x;
+				startY = tinf.y;
+				width = tinf.w;
+				height = tinf.h;
+				ratioX = 1;
+				ratioY = 1;
+				offsetX = 0;
+				offsetY = 0;
+			}
 			initShaders(baseShaderList);
 			initShaders(baseShaderList);
 			baseShader.halfPixelInverse.set(0.5 / (t == null ? engine.width : t.width), 0.5 / (t == null ? engine.height : t.height));
 			baseShader.halfPixelInverse.set(0.5 / (t == null ? engine.width : t.width), 0.5 / (t == null ? engine.height : t.height));
-			baseShader.viewport.set( -width * 0.5 - startX, -height * 0.5 - startY, 2 / width, -2 * (t == null ? baseFlipY : targetFlipY) / height);
+			baseShader.viewport.set( -width * 0.5 - startX - offsetX, -height * 0.5 - startY - offsetY, 2 / width * ratioX, -2 * (t == null ? baseFlipY : targetFlipY) / height * ratioY);
 			curX = startX;
 			curX = startX;
 			curY = startY;
 			curY = startY;
 			curWidth = width;
 			curWidth = width;
@@ -213,8 +232,8 @@ class RenderContext extends h3d.impl.RenderContext {
 		renderY = y;
 		renderY = y;
 		renderW = w;
 		renderW = w;
 		renderH = h;
 		renderH = h;
-		var scaleX = engine.width / scene.width;
-		var scaleY = engine.height / scene.height;
+		var scaleX = engine.width * scene.ratioX / scene.width;
+		var scaleY = engine.height * scene.ratioY / scene.height;
 		if( inFilter != null ) {
 		if( inFilter != null ) {
 			var fa = baseShader.filterMatrixA;
 			var fa = baseShader.filterMatrixA;
 			var fb = baseShader.filterMatrixB;
 			var fb = baseShader.filterMatrixB;
@@ -228,7 +247,7 @@ class RenderContext extends h3d.impl.RenderContext {
 			w = rx2 - rx1;
 			w = rx2 - rx1;
 			h = ry2 - ry1;
 			h = ry2 - ry1;
 		}
 		}
-		engine.setRenderZone(Std.int((x - curX) * scaleX + 1e-10), Std.int((y - curY) * scaleY + 1e-10), Std.int(w * scaleX + 1e-10), Std.int(h * scaleY + 1e-10));
+		engine.setRenderZone(Std.int((x - curX + scene.viewportX) * scaleX + 1e-10), Std.int((y - curY + scene.viewportY) * scaleY + 1e-10), Std.int(w * scaleX + 1e-10), Std.int(h * scaleY + 1e-10));
 	}
 	}
 
 
 	public inline function clearRenderZone() {
 	public inline function clearRenderZone() {

+ 250 - 32
h2d/Scene.hx

@@ -1,21 +1,117 @@
 package h2d;
 package h2d;
 import hxd.Math;
 import hxd.Math;
 
 
+/**
+	Viewport alignment when scaling mode supports it.
+**/
+enum ScaleModeAlign {
+	/** Anchor Scene viewport horizontally to left side of the window. When passed to verticalAlign it will be treated as Center. **/
+	Left;
+	/** Anchor Scene viewport horizontally to right side of the window. When passed to verticalAlign it will be treated as Center. **/
+	Right;
+	/** Anchor to the center of window. **/
+	Center;
+	/** Anchor Scene viewport vertically to the top of a window. When passed to horizontalAlign it will be treated as Center. **/
+	Top;
+	/** Anchor Scene viewport vertically to the bottom of a window. When passed to horizontalAlign it will be treated as Center. **/
+	Bottom;
+}
+
+/**
+	Scaling mode of the 2D Scene.
+	See `ScaleMode2D` sample for showcase.
+**/
+enum ScaleMode {
+
+	/**
+		Matches scene size to window size. `width` and `height` of Scene will match window size. Default scaling mode.
+	**/
+	Resize;
+
+	/**
+		Sets constant Scene size and stretches it to cover entire window. This behavior is same as old `setFixedSize` method.
+	**/
+	Stretch(width : Int, height : Int);
+
+	/**
+		Sets constant scene size and upscales it with preserving aspect-ratio to fit the window.
+		If `integerScale` is `true` - scaling will be performed  with only integer increments (1x, 2x, 3x, ...). Default: `false`
+		`horizontalAlign` controls viewport anchoring horizontally. Accepted values are `Left`, `Center` and `Right`. Default: `Center`
+		`verticalAlign` controls viewport anchoring vertically. Accepted values are `Top`, `Center` and `Bottom`. Default: `Center`
+		With `800x600` window, `LetterBox(320, 260)` will result in center-aligned Scene of size `320x260` upscaled to fit into screen.
+	**/
+	LetterBox(width : Int, height : Int, ?integerScale : Bool, ?horizontalAlign : ScaleModeAlign, ?verticalAlign : ScaleModeAlign);
+
+	/**
+		Sets constant Scene size, scale and alignment. Does not perform any adaptation to the screen apart from alignment.
+		`horizontalAlign` controls viewport anchoring horizontally. Accepted values are `Left`, `Center` and `Right`. Default: `Center`
+		`verticalAlign` controls viewport anchoring vertically. Accepted values are `Top`, `Center` and `Bottom`. Default: `Center`
+		With `800x600` window, `Fixed(200, 150, 2, Left, Center)` will result in Scene size of `200x150`, and visually upscaled to `400x300`, and aligned to middle-left of the window.
+	**/
+	Fixed(width : Int, height: Int, zoom : Float, ?horizontalAlign : ScaleModeAlign, ?verticalAlign : ScaleModeAlign);
+
+	/**
+		Upscales/downscales Scene according to `level` and matches Scene size to `ceil(window size / level)`.
+		With `800x600` window, `Zoom(2)` will result in `400x300` Scene size upscaled to fill entire window.
+	**/
+	Zoom(level : Float);
+
+	/**
+		Ensures that Scene size will be of minimum specified size.
+		Automatically calculates zoom level based on provided size according to `min(window width / min width, window height / min height)`, then applies same scaling as `Zoom(level)`.
+		Behavior is similiar to LetterBox, however instead of letterboxing effect, Scene size will change to cover the letterboxed parts.
+		`minWidth` or `minHeight` can be set to `0` in order to force scaling adjustment account only for either horizontal of vertical window size.
+		If `integerScale` is `true` - scaling will be performed  with only integer increments (1x, 2x, 3x, ...). Default: `false`
+		With `800x600` window, `AutoZoom(320, 260, false)` will result in Scene size of `347x260`. `AutoZoom(320, 260, true)` will result in size of `400x300`.
+	**/
+	AutoZoom(minWidth : Int, minHeight : Int, ?integerScaling : Bool);
+}
+
 /**
 /**
 	h2d.Scene is the root class for a 2D scene. All root objects are added to it before being drawn on screen.
 	h2d.Scene is the root class for a 2D scene. All root objects are added to it before being drawn on screen.
 **/
 **/
 class Scene extends Layers implements h3d.IDrawable implements hxd.SceneEvents.InteractiveScene {
 class Scene extends Layers implements h3d.IDrawable implements hxd.SceneEvents.InteractiveScene {
 
 
 	/**
 	/**
-		The current width (in pixels) of the scene. Can change if the screen gets resized.
+		The current width (in pixels) of the scene. Can change if the screen gets resized or `scaleMode` changes.
 	**/
 	**/
 	public var width(default,null) : Int;
 	public var width(default,null) : Int;
 
 
 	/**
 	/**
-		The current height (in pixels) of the scene. Can change if the screen gets resized.
+		The current height (in pixels) of the scene. Can change if the screen gets resized or `scaleMode` changes.
 	**/
 	**/
 	public var height(default, null) : Int;
 	public var height(default, null) : Int;
 
 
+	/**
+		Horizontal viewport offset relative to top-left corner of the window. Can change if the screen gets resized or `scaleMode` changes.
+		Offset is in internal Scene resolution pixels.
+	**/
+	public var viewportX(default, null) : Float;
+	/**
+		Vertical viewport offset relative to top-left corner of the window. Can change if the screen gets resized or `scaleMode` changes.
+		Offset is in internal Scene resolution pixels.
+	**/
+	public var viewportY(default, null) : Float;
+	/**
+		Physical vertical viewport offset relative to the center of the window. Assigned if the screen gets resized or `scaleMode` changes.
+		Offset is in internal Scene resolution pixels.
+	**/
+	public var offsetX : Float;
+	/**
+		Physical horizontal viewport offset relative to the center of the window. Assigned if the screen gets resized or `scaleMode` changes.
+		Offset is in internal Scene resolution pixels.
+	**/
+	public var offsetY : Float;
+
+	/**
+		Horizontal ratio of the window used by the Scene (including scaling). Can change if the screen gets resized or `scaleMode` changes.
+	**/
+	public var ratioX(default, null) : Float;
+	/**
+		Vertical ratio of the window used by the Scene (including scaling). Can change if the screen gets resized or `scaleMode` changes.
+	**/
+	public var ratioY(default, null) : Float;
+
 	/**
 	/**
 		The current mouse X coordinates (in pixel) relative to the scene.
 		The current mouse X coordinates (in pixel) relative to the scene.
 	**/
 	**/
@@ -30,7 +126,16 @@ class Scene extends Layers implements h3d.IDrawable implements hxd.SceneEvents.I
 		The zoom factor of the scene, allows to set a fixed x2, x4 etc. zoom for pixel art
 		The zoom factor of the scene, allows to set a fixed x2, x4 etc. zoom for pixel art
 		When setting a zoom > 0, the scene resize will be automaticaly managed.
 		When setting a zoom > 0, the scene resize will be automaticaly managed.
 	**/
 	**/
-	public var zoom(default, set) : Int = 0;
+	@:deprecated("zoom is deprecated, use scaleMode = Zoom(v) instead")
+	public var zoom(get, set) : Int;
+
+	/**
+		Scene scaling mode. ( default : Fill )
+		Important thing to keep in mind - Scene does not clip rendering to it's scaled size and
+		graphics can render outside of it. However `drawTile` does check for those bounds and
+		will clip out tiles that are outside of the scene bounds.
+	**/
+	public var scaleMode(default, set) : ScaleMode = Resize;
 
 
 	/**
 	/**
 		Set the default value for `h2d.Drawable.smooth` (default: false)
 		Set the default value for `h2d.Drawable.smooth` (default: false)
@@ -42,7 +147,6 @@ class Scene extends Layers implements h3d.IDrawable implements hxd.SceneEvents.I
 	**/
 	**/
 	public var renderer(get, set) : RenderContext;
 	public var renderer(get, set) : RenderContext;
 
 
-	var fixedSize : Bool;
 	var interactive : Array<Interactive>;
 	var interactive : Array<Interactive>;
 	var eventListeners : Array< hxd.Event -> Void >;
 	var eventListeners : Array< hxd.Event -> Void >;
 	var ctx : RenderContext;
 	var ctx : RenderContext;
@@ -60,6 +164,12 @@ class Scene extends Layers implements h3d.IDrawable implements hxd.SceneEvents.I
 		ctx = new RenderContext(this);
 		ctx = new RenderContext(this);
 		width = e.width;
 		width = e.width;
 		height = e.height;
 		height = e.height;
+		offsetX = 0;
+		offsetY = 0;
+		ratioX = 1;
+		ratioY = 1;
+		viewportX = 0;
+		viewportY = 0;
 		interactive = new Array();
 		interactive = new Array();
 		eventListeners = new Array();
 		eventListeners = new Array();
 		shapePoint = new h2d.col.Point();
 		shapePoint = new h2d.col.Point();
@@ -75,17 +185,22 @@ class Scene extends Layers implements h3d.IDrawable implements hxd.SceneEvents.I
 		this.events = events;
 		this.events = events;
 	}
 	}
 
 
+	function get_zoom() : Int {
+		return switch ( scaleMode ) {
+			case Zoom(level): Std.int(level);
+			default: 0;
+		}
+	}
+
 	function set_zoom(v:Int) {
 	function set_zoom(v:Int) {
-		var e = h3d.Engine.getCurrent();
-		var twidth = Math.ceil(window.width / v);
-		var theight = Math.ceil(window.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 zoom = v;
+		scaleMode = Zoom(v);
+		return v;
+	}
+
+	function set_scaleMode( v : ScaleMode ) {
+		scaleMode = v;
+		checkResize();
+		return v;
 	}
 	}
 
 
 	function get_renderer() return ctx;
 	function get_renderer() return ctx;
@@ -94,32 +209,109 @@ class Scene extends Layers implements h3d.IDrawable implements hxd.SceneEvents.I
 	/**
 	/**
 		Set the fixed size for the scene, will prevent automatic scene resizing when screen size changes.
 		Set the fixed size for the scene, will prevent automatic scene resizing when screen size changes.
 	**/
 	**/
+	@:deprecated("setFixedSize is deprecated, use scaleMode = Strech(w, h) instead")
 	public function setFixedSize( w : Int, h : Int ) {
 	public function setFixedSize( w : Int, h : Int ) {
-		width = w;
-		height = h;
-		fixedSize = true;
-		posChanged = true;
+		scaleMode = Stretch(w, h);
 	}
 	}
 
 
 	@:dox(hide) @:noCompletion
 	@:dox(hide) @:noCompletion
 	public function checkResize() {
 	public function checkResize() {
-		if( fixedSize && zoom == 0 ) return;
 		var engine = h3d.Engine.getCurrent();
 		var engine = h3d.Engine.getCurrent();
-		var scale = zoom == 0 ? 1 : zoom;
-		if( width * scale != engine.width || height * scale != engine.height ) {
-			width = engine.width;
-			height = engine.height;
-			posChanged = true;
-			if( zoom != 0 ) this.zoom = zoom;
+
+		inline function setSceneSize( w : Int, h : Int ) {
+			if ( w != this.width || h != this.height ) {
+				width = w;
+				height = h;
+				posChanged = true;
+			}
+		}
+
+		inline function calcRatio( scale : Float ) {
+			ratioX = (width * scale) / engine.width;
+			ratioY = (height * scale) / engine.height;
+		}
+
+		inline function calcViewport( horizontal : ScaleModeAlign, vertical : ScaleModeAlign, zoom : Float ) {
+			if ( horizontal == null ) horizontal = Center;
+			switch ( horizontal ) {
+				case Left:
+					offsetX = (engine.width - width * zoom) / (2 * zoom);
+					viewportX = 0;
+				case Right:
+					offsetX = -((engine.width - width * zoom) / (2 * zoom));
+					viewportX = (engine.width - width * zoom) / zoom;
+				default:
+					offsetX = 0;
+					viewportX = (engine.width - width * zoom) / (2 * zoom);
+			}
+
+			if ( vertical == null ) vertical = Center;
+			switch ( vertical ) {
+				case Top:
+					offsetY = (engine.height - height * zoom) / (2 * zoom);
+					viewportY = 0;
+				case Bottom:
+					offsetY = -((engine.height - height * zoom) / (2 * zoom));
+					viewportY = (engine.height - height * zoom) / zoom;
+				default:
+					offsetY = 0;
+					viewportY = (engine.height - height * zoom) / (2 * zoom);
+			}
+		}
+
+		inline function zeroViewport() {
+			offsetX = 0;
+			offsetY = 0;
+			viewportX = 0;
+			viewportY = 0;
+		}
+
+		switch ( scaleMode ) {
+			case Resize:
+				setSceneSize(engine.width, engine.height);
+				ratioX = 1;
+				ratioY = 1;
+				zeroViewport();
+			case Stretch(_width, _height):
+				setSceneSize(_width, _height);
+				ratioX = 1;
+				ratioY = 1;
+				zeroViewport();
+			case LetterBox(_width, _height, integerScale, horizontalAlign, verticalAlign):
+				setSceneSize(_width, _height);
+				var zoom = Math.min(engine.width / _width, engine.height / _height);
+				if ( integerScale ) {
+					zoom = Std.int(zoom);
+					if (zoom == 0) zoom = 1;
+				}
+				calcRatio(zoom);
+				calcViewport(horizontalAlign, verticalAlign, zoom);
+			case Fixed(_width, _height, zoom, horizontalAlign, verticalAlign):
+				setSceneSize(_width, _height);
+				calcRatio(zoom);
+				calcViewport(horizontalAlign, verticalAlign, zoom);
+			case Zoom(level):
+				setSceneSize(Math.ceil(engine.width / level), Math.ceil(engine.height / level));
+				calcRatio(level);
+				zeroViewport();
+			case AutoZoom(minWidth, minHeight, integerScaling):
+				var zoom = Math.min(engine.width / minWidth, engine.height / minHeight);
+				if ( integerScaling ) {
+					zoom = Std.int(zoom);
+					if ( zoom == 0 ) zoom = 1;
+				}
+				setSceneSize(Math.ceil(engine.width / zoom), Math.ceil(engine.height / zoom));
+				calcRatio(zoom);
+				zeroViewport();
 		}
 		}
 	}
 	}
 
 
 	inline function screenXToLocal(mx:Float) {
 	inline function screenXToLocal(mx:Float) {
-		return mx * width / (window.width * scaleX) - x;
+		return mx * width / (window.width * ratioX * scaleX) - x - viewportX;
 	}
 	}
 
 
 	inline function screenYToLocal(my:Float) {
 	inline function screenYToLocal(my:Float) {
-		return my * height / (window.height * scaleY) - y;
+		return my * height / (window.height * ratioY * scaleY) - y - viewportY;
 	}
 	}
 
 
 	function get_mouseX() {
 	function get_mouseX() {
@@ -369,7 +561,7 @@ class Scene extends Layers implements h3d.IDrawable implements hxd.SceneEvents.I
 		var f = events.getFocus();
 		var f = events.getFocus();
 		if( f == null )
 		if( f == null )
 			return null;
 			return null;
-		var i = Std.instance(f, h2d.Interactive);
+		var i = hxd.impl.Api.downcast(f, h2d.Interactive);
 		if( i == null )
 		if( i == null )
 			return null;
 			return null;
 		return interactive[interactive.indexOf(i)];
 		return interactive[interactive.indexOf(i)];
@@ -497,10 +689,22 @@ class Scene extends Layers implements h3d.IDrawable implements hxd.SceneEvents.I
 	override function sync( ctx : RenderContext ) {
 	override function sync( ctx : RenderContext ) {
 		if( !allocated )
 		if( !allocated )
 			onAdd();
 			onAdd();
-		checkResize();
 		super.sync(ctx);
 		super.sync(ctx);
 	}
 	}
 
 
+	override function onAdd()
+	{
+		checkResize();
+		super.onAdd();
+		window.addResizeEvent(checkResize);
+	}
+
+	override function onRemove()
+	{
+		super.onRemove();
+		window.removeResizeEvent(checkResize);
+	}
+
 	/**
 	/**
 		Capture the scene into a texture and render the resulting Bitmap
 		Capture the scene into a texture and render the resulting Bitmap
 	**/
 	**/
@@ -515,14 +719,28 @@ class Scene extends Layers implements h3d.IDrawable implements hxd.SceneEvents.I
 
 
 		var tex = target.getTexture();
 		var tex = target.getTexture();
 		engine.pushTarget(tex);
 		engine.pushTarget(tex);
-		var ow = width, oh = height, of = fixedSize;
-		setFixedSize(tex.width, tex.height);
+		var ow = width, oh = height, ox = offsetX, oy = offsetY;
+		var ovx = viewportX, ovy = viewportY, orx = ratioX, ory = ratioY;
+		width = tex.width;
+		height = tex.height;
+		ratioX = 1;
+		ratioY = 1;
+		offsetX = 0;
+		offsetY = 0;
+		viewportX = 0;
+		viewportY = 0;
+		posChanged = true;
 		render(engine);
 		render(engine);
 		engine.popTarget();
 		engine.popTarget();
 
 
 		width = ow;
 		width = ow;
 		height = oh;
 		height = oh;
-		fixedSize = of;
+		ratioX = orx;
+		ratioY = ory;
+		offsetX = ox;
+		offsetY = oy;
+		viewportX = ovx;
+		viewportY = ovy;
 		posChanged = true;
 		posChanged = true;
 		engine.setRenderZone();
 		engine.setRenderZone();
 		engine.end();
 		engine.end();

+ 1 - 1
h3d/anim/Animation.hx

@@ -156,7 +156,7 @@ class Animation implements hxd.impl.Serializable {
 				objects.remove(a);
 				objects.remove(a);
 				continue;
 				continue;
 			}
 			}
-			var joint = Std.instance(obj, h3d.scene.Skin.Joint);
+			var joint = hxd.impl.Api.downcast(obj, h3d.scene.Skin.Joint);
 			if( joint != null ) {
 			if( joint != null ) {
 				currentSkin = cast joint.parent;
 				currentSkin = cast joint.parent;
 				a.targetSkin = currentSkin;
 				a.targetSkin = currentSkin;

+ 1 - 1
h3d/anim/BufferAnimation.hx

@@ -81,7 +81,7 @@ class BufferAnimation extends Animation {
 		if( a == null )
 		if( a == null )
 			a = new BufferAnimation(name, frameCount, sampling);
 			a = new BufferAnimation(name, frameCount, sampling);
 		super.clone(a);
 		super.clone(a);
-		var la = Std.instance(a, BufferAnimation);
+		var la = hxd.impl.Api.downcast(a, BufferAnimation);
 		la.setData(data, stride);
 		la.setData(data, stride);
 		return a;
 		return a;
 	}
 	}

+ 2 - 0
h3d/impl/GlDriver.hx

@@ -1383,6 +1383,8 @@ class GlDriver extends Driver {
 			gl.disable(GL.SCISSOR_TEST);
 			gl.disable(GL.SCISSOR_TEST);
 		else {
 		else {
 			gl.enable(GL.SCISSOR_TEST);
 			gl.enable(GL.SCISSOR_TEST);
+			if( curTarget == null )
+				y = bufferHeight - (y + height);
 			gl.scissor(x, y, width, height);
 			gl.scissor(x, y, width, height);
 		}
 		}
 	}
 	}

+ 1 - 1
h3d/mat/MaterialDatabase.hx

@@ -26,7 +26,7 @@ class MaterialDatabase {
 	function saveData( model : hxd.res.Resource, data : Dynamic ) {
 	function saveData( model : hxd.res.Resource, data : Dynamic ) {
 		var file = getFilePath(model);
 		var file = getFilePath(model);
 		#if (sys || nodejs)
 		#if (sys || nodejs)
-		var fs = Std.instance(hxd.res.Loader.currentInstance.fs, hxd.fs.LocalFileSystem);
+		var fs = hxd.impl.Api.downcast(hxd.res.Loader.currentInstance.fs, hxd.fs.LocalFileSystem);
 		if( fs != null && !haxe.io.Path.isAbsolute(file) )
 		if( fs != null && !haxe.io.Path.isAbsolute(file) )
 			file = fs.baseDir + file;
 			file = fs.baseDir + file;
 		if( data == null )
 		if( data == null )

+ 2 - 1
h3d/mat/Pass.hx

@@ -42,6 +42,7 @@ class Pass implements hxd.impl.Serializable {
 	@:bits(bits) public var blendAlphaOp : Operation;
 	@:bits(bits) public var blendAlphaOp : Operation;
 	@:bits(bits) public var wireframe : Bool;
 	@:bits(bits) public var wireframe : Bool;
 	public var colorMask : Int;
 	public var colorMask : Int;
+	public var layer : Int = 0;
 
 
 	@:s public var stencil : Stencil;
 	@:s public var stencil : Stencil;
 
 
@@ -233,7 +234,7 @@ class Pass implements hxd.impl.Serializable {
 	public function getShader< T:hxsl.Shader >(t:Class<T>) : T {
 	public function getShader< T:hxsl.Shader >(t:Class<T>) : T {
 		var s = shaders;
 		var s = shaders;
 		while( s != parentShaders ) {
 		while( s != parentShaders ) {
-			var sh = Std.instance(s.s, t);
+			var sh = hxd.impl.Api.downcast(s.s, t);
 			if( sh != null )
 			if( sh != null )
 				return sh;
 				return sh;
 			s = s.next;
 			s = s.next;

+ 130 - 6
h3d/mat/PbrMaterial.hx

@@ -2,7 +2,6 @@ package h3d.mat;
 
 
 @:enum abstract PbrMode(String) {
 @:enum abstract PbrMode(String) {
 	var PBR = "PBR";
 	var PBR = "PBR";
-	var Albedo = "Albedo";
 	var Overlay = "Overlay";
 	var Overlay = "Overlay";
 	var Decal = "Decal";
 	var Decal = "Decal";
 	var BeforeTonemapping = "BeforeTonemapping";
 	var BeforeTonemapping = "BeforeTonemapping";
@@ -29,15 +28,49 @@ package h3d.mat;
 	var NotEqual= "NotEqual";
 	var NotEqual= "NotEqual";
 }
 }
 
 
+@:enum abstract PbrStencilOp(String) {
+	var Keep = "Keep";
+	var Zero = "Zero";
+	var Replace = "Replace";
+	var Increment = "Increment";
+	var IncrementWrap = "IncrementWrap";
+	var Decrement = "Decrement";
+	var DecrementWrap = "DecrementWrap";
+	var Invert = "Invert";
+}
+
+@:enum abstract PbrStencilCompare(String) {
+	var Always = "Always";
+	var Never = "Never";
+	var Equal = "Equal";
+	var NotEqual = "NotEqual";
+	var Greater = "Greater";
+	var GreaterEqual = "GreaterEqual";
+	var Less = "Less";
+	var LessEqual = "LessEqual";
+}
+
 typedef PbrProps = {
 typedef PbrProps = {
 	var mode : PbrMode;
 	var mode : PbrMode;
 	var blend : PbrBlend;
 	var blend : PbrBlend;
 	var shadows : Bool;
 	var shadows : Bool;
 	var culling : Bool;
 	var culling : Bool;
 	var depthTest : PbrDepthTest;
 	var depthTest : PbrDepthTest;
+	var colorMask : Int;
 	@:optional var alphaKill : Bool;
 	@:optional var alphaKill : Bool;
 	@:optional var emissive : Float;
 	@:optional var emissive : Float;
 	@:optional var parallax : Float;
 	@:optional var parallax : Float;
+	
+	var enableStencil : Bool;
+	@:optional var stencilCompare : PbrStencilCompare;
+	@:optional var stencilPassOp : PbrStencilOp;
+	@:optional var stencilFailOp : PbrStencilOp;
+	@:optional var depthFailOp : PbrStencilOp;
+	@:optional var stencilValue : Int;
+	@:optional var stencilWriteMask : Int;
+	@:optional var stencilReadMask : Int;
+
+	@:optional var drawOrder : Int;
 }
 }
 
 
 class PbrMaterial extends Material {
 class PbrMaterial extends Material {
@@ -65,6 +98,8 @@ class PbrMaterial extends Material {
 				shadows : false,
 				shadows : false,
 				culling : false,
 				culling : false,
 				depthTest : Less,
 				depthTest : Less,
+				colorMask : 1 << 0 | 1 << 1 | 1 << 2 | 1 << 3,
+				enableStencil : false,
 			};
 			};
 		case "ui":
 		case "ui":
 			props = {
 			props = {
@@ -74,6 +109,8 @@ class PbrMaterial extends Material {
 				culling : false,
 				culling : false,
 				alphaKill : true,
 				alphaKill : true,
 				depthTest : Less,
 				depthTest : Less,
+				colorMask : 1 << 0 | 1 << 1 | 1 << 2 | 1 << 3,
+				enableStencil : false,
 			};
 			};
 		case "decal":
 		case "decal":
 			props = {
 			props = {
@@ -82,6 +119,8 @@ class PbrMaterial extends Material {
 				shadows : false,
 				shadows : false,
 				culling : true,
 				culling : true,
 				depthTest : Less,
 				depthTest : Less,
+				colorMask : 1 << 0 | 1 << 1 | 1 << 2 | 1 << 3,
+				enableStencil : false,
 			};
 			};
 		default:
 		default:
 			props = {
 			props = {
@@ -90,6 +129,8 @@ class PbrMaterial extends Material {
 				shadows : true,
 				shadows : true,
 				culling : true,
 				culling : true,
 				depthTest : Less,
 				depthTest : Less,
+				colorMask : 1 << 0 | 1 << 1 | 1 << 2 | 1 << 3,
+				enableStencil : false,
 			};
 			};
 		}
 		}
 		return props;
 		return props;
@@ -102,6 +143,7 @@ class PbrMaterial extends Material {
 			case Alpha: Alpha;
 			case Alpha: Alpha;
 			case Add: Add;
 			case Add: Add;
 			case Multiply: Multiply;
 			case Multiply: Multiply;
+			case AlphaMultiply: AlphaMultiply;
 			default: throw "Unsupported Model blendMode "+blendMode;
 			default: throw "Unsupported Model blendMode "+blendMode;
 		}
 		}
 		props.depthTest = switch (mainPass.depthTest) {
 		props.depthTest = switch (mainPass.depthTest) {
@@ -118,22 +160,38 @@ class PbrMaterial extends Material {
 	}
 	}
 
 
 	function resetProps() {
 	function resetProps() {
+		var props : PbrProps = props;
 		// Remove superfluous shader
 		// Remove superfluous shader
 		mainPass.removeShader(mainPass.getShader(h3d.shader.VolumeDecal));
 		mainPass.removeShader(mainPass.getShader(h3d.shader.VolumeDecal));
 		mainPass.removeShader(mainPass.getShader(h3d.shader.pbr.StrengthValues));
 		mainPass.removeShader(mainPass.getShader(h3d.shader.pbr.StrengthValues));
 		mainPass.removeShader(mainPass.getShader(h3d.shader.pbr.AlphaMultiply));
 		mainPass.removeShader(mainPass.getShader(h3d.shader.pbr.AlphaMultiply));
 		mainPass.removeShader(mainPass.getShader(h3d.shader.Parallax));
 		mainPass.removeShader(mainPass.getShader(h3d.shader.Parallax));
+		// Backward compatibility
 		if( !Reflect.hasField(props, "depthTest") ) Reflect.setField(props, "depthTest", Less);
 		if( !Reflect.hasField(props, "depthTest") ) Reflect.setField(props, "depthTest", Less);
+		if( !Reflect.hasField(props, "colorMask") ) Reflect.setField(props, "colorMask", 1 << 0 | 1 << 1 | 1 << 2 | 1 << 3);
+		if( !Reflect.hasField(props, "enableStencil") ) Reflect.setField(props, "enableStencil", false);
+		// Remove unused fields
+		if( props.emissive == 0 )
+			Reflect.deleteField(props,"emissive");
+		if( !props.enableStencil ) {
+			Reflect.deleteField(props, "stencilWriteMask");
+			Reflect.deleteField(props, "stencilReadMask");
+			Reflect.deleteField(props, "stencilValue");
+			Reflect.deleteField(props, "stencilFailOp");
+			Reflect.deleteField(props, "depthFailOp");
+			Reflect.deleteField(props, "stencilPassOp");
+			Reflect.deleteField(props, "stencilCompare");
+		} 
 	}
 	}
 
 
 	override function refreshProps() {
 	override function refreshProps() {
 		resetProps();
 		resetProps();
 		var props : PbrProps = props;
 		var props : PbrProps = props;
+
+		// Preset
 		switch( props.mode ) {
 		switch( props.mode ) {
 		case PBR:
 		case PBR:
 			mainPass.setPassName("default");
 			mainPass.setPassName("default");
-		case Albedo:
-			mainPass.setPassName("albedo");
 		case BeforeTonemapping:
 		case BeforeTonemapping:
 			mainPass.setPassName("beforeTonemapping");
 			mainPass.setPassName("beforeTonemapping");
 			var e = mainPass.getShader(h3d.shader.Emissive);
 			var e = mainPass.getShader(h3d.shader.Emissive);
@@ -158,6 +216,8 @@ class PbrMaterial extends Material {
 				mainPass.addShader(sv);
 				mainPass.addShader(sv);
 			}
 			}
 		}
 		}
+
+		// Blend modes
 		switch( props.blend ) {
 		switch( props.blend ) {
 		case None:
 		case None:
 			mainPass.setBlendMode(None);
 			mainPass.setBlendMode(None);
@@ -183,12 +243,16 @@ class PbrMaterial extends Material {
 			mainPass.setBlendMode(AlphaMultiply);
 			mainPass.setBlendMode(AlphaMultiply);
 			mainPass.depthWrite = false;
 			mainPass.depthWrite = false;
 		}
 		}
+
+		// Enable/Disable AlphaKill
 		var tshader = textureShader;
 		var tshader = textureShader;
 		if( tshader != null ) {
 		if( tshader != null ) {
 			tshader.killAlpha = props.alphaKill;
 			tshader.killAlpha = props.alphaKill;
 			tshader.killAlphaThreshold = 0.5;
 			tshader.killAlphaThreshold = 0.5;
 		}
 		}
+
 		mainPass.culling = props.culling ? Back : None;
 		mainPass.culling = props.culling ? Back : None;
+
 		shadows = props.shadows;
 		shadows = props.shadows;
 		if( shadows ) getPass("shadow").culling = mainPass.culling;
 		if( shadows ) getPass("shadow").culling = mainPass.culling;
 
 
@@ -204,7 +268,7 @@ class PbrMaterial extends Material {
 			default: Less;
 			default: Less;
 		}
 		}
 
 
-		// get values from specular texture
+		// Get values from specular texture
 		var emit = props.emissive == null ? 0 : props.emissive;
 		var emit = props.emissive == null ? 0 : props.emissive;
 		var tex = mainPass.getShader(h3d.shader.pbr.PropsTexture);
 		var tex = mainPass.getShader(h3d.shader.pbr.PropsTexture);
 		var def = mainPass.getShader(h3d.shader.pbr.PropsValues);
 		var def = mainPass.getShader(h3d.shader.pbr.PropsValues);
@@ -215,7 +279,7 @@ class PbrMaterial extends Material {
 		if( tex != null ) tex.emissive = emit;
 		if( tex != null ) tex.emissive = emit;
 		if( def != null ) def.emissive = emit;
 		if( def != null ) def.emissive = emit;
 
 
-		// parallax
+		// Parallax
 		var ps = mainPass.getShader(h3d.shader.Parallax);
 		var ps = mainPass.getShader(h3d.shader.Parallax);
 		if( props.parallax > 0 ) {
 		if( props.parallax > 0 ) {
 			if( ps == null ) {
 			if( ps == null ) {
@@ -228,6 +292,67 @@ class PbrMaterial extends Material {
 		} else if( ps != null )
 		} else if( ps != null )
 			mainPass.removeShader(ps);
 			mainPass.removeShader(ps);
 
 
+		setColorMask();
+
+		setStencil();
+
+		mainPass.layer = props.drawOrder == null ? 0 : props.drawOrder;
+	}
+
+	function setColorMask() {
+		var props : PbrProps = props;
+		mainPass.setColorMask(	props.colorMask & (1<<0) > 0 ? true : false, 
+								props.colorMask & (1<<1) > 0 ? true : false, 
+								props.colorMask & (1<<2) > 0 ? true : false, 
+								props.colorMask & (1<<3) > 0 ? true : false);
+	}
+
+	function setStencil() {
+		var props : PbrProps = props;
+		if( props.enableStencil ) {
+
+			if( !Reflect.hasField(props, "stencilFailOp") ) 	Reflect.setField(props, "stencilFailOp", Keep);
+			if( !Reflect.hasField(props, "depthFailOp") ) 		Reflect.setField(props, "depthFailOp", Keep);
+			if( !Reflect.hasField(props, "stencilPassOp") ) 	Reflect.setField(props, "stencilPassOp", Replace);
+			if( !Reflect.hasField(props, "stencilCompare") ) 	Reflect.setField(props, "stencilCompare", Always);
+			if( !Reflect.hasField(props, "stencilValue") ) 		Reflect.setField(props, "stencilValue", 0);
+			if( !Reflect.hasField(props, "stencilReadMask") ) 	Reflect.setField(props, "stencilReadMask", 0);
+			if( !Reflect.hasField(props, "stencilWriteMask") ) 	Reflect.setField(props, "stencilWriteMask", 0);
+
+			inline function getStencilOp( op : PbrStencilOp ) : Data.StencilOp {
+				return switch op {
+					case Keep:Keep;
+					case Zero:Zero;
+					case Replace:Replace;
+					case Increment:Increment;
+					case IncrementWrap:IncrementWrap;
+					case Decrement:Decrement;
+					case DecrementWrap:DecrementWrap;
+					case Invert:Invert;
+				}
+			}
+
+			inline function getStencilCompare( op : PbrStencilCompare ) : Data.Compare {
+				return switch op {
+					case Always:Always;
+					case Never:Never;
+					case Equal:Equal;
+					case NotEqual:NotEqual;
+					case Greater:Greater;
+					case GreaterEqual:GreaterEqual;
+					case Less:Less;
+					case LessEqual:LessEqual;
+				}
+			}
+
+			var s = new Stencil();
+			s.setFunc(getStencilCompare(props.stencilCompare), props.stencilValue, props.stencilReadMask, props.stencilWriteMask);
+			s.setOp(getStencilOp(props.stencilFailOp), getStencilOp(props.depthFailOp), getStencilOp(props.stencilPassOp));
+			mainPass.stencil = s;
+		}
+		else {
+			mainPass.stencil = null;
+		}
 	}
 	}
 
 
 	override function get_specularTexture() {
 	override function get_specularTexture() {
@@ -283,7 +408,6 @@ class PbrMaterial extends Material {
 	#if editor
 	#if editor
 	override function editProps() {
 	override function editProps() {
 		var props : PbrProps = props;
 		var props : PbrProps = props;
-		if( props.emissive == 0 ) Reflect.deleteField(props,"emissive");
 		return new js.jquery.JQuery('
 		return new js.jquery.JQuery('
 			<dl>
 			<dl>
 				<dt>Mode</dt>
 				<dt>Mode</dt>

+ 1 - 1
h3d/pass/Base.hx

@@ -21,7 +21,7 @@ class Base {
 	public function dispose() {
 	public function dispose() {
 	}
 	}
 
 
-	public function draw( passes : PassList ) {
+	public function draw( passes : PassList, ?sort : h3d.pass.PassList -> Void ) {
 	}
 	}
 
 
 }
 }

+ 6 - 22
h3d/pass/Default.hx

@@ -6,11 +6,7 @@ class Default extends Base {
 
 
 	var manager : ShaderManager;
 	var manager : ShaderManager;
 	var globals(get, never) : hxsl.Globals;
 	var globals(get, never) : hxsl.Globals;
-	var shaderCount : Int = 1;
-	var textureCount : Int = 1;
-	var shaderIdMap : Array<Int>;
-	var textureIdMap : Array<Int>;
-	var sortPasses = true;
+	var defaultSort = new SortByMaterial().sort;
 
 
 	inline function get_globals() return manager.globals;
 	inline function get_globals() return manager.globals;
 
 
@@ -31,8 +27,6 @@ class Default extends Base {
 	public function new(name) {
 	public function new(name) {
 		super(name);
 		super(name);
 		manager = new ShaderManager(getOutputs());
 		manager = new ShaderManager(getOutputs());
-		shaderIdMap = [];
-		textureIdMap = [];
 		initGlobals();
 		initGlobals();
 	}
 	}
 
 
@@ -92,27 +86,17 @@ class Default extends Base {
 	}
 	}
 
 
 	@:access(h3d.scene)
 	@:access(h3d.scene)
-	override function draw( passes : h3d.pass.PassList ) {
+	override function draw( passes : h3d.pass.PassList, ?sort : h3d.pass.PassList -> Void ) {
 		if( passes.isEmpty() )
 		if( passes.isEmpty() )
 			return;
 			return;
 		for( g in ctx.sharedGlobals )
 		for( g in ctx.sharedGlobals )
 			globals.fastSet(g.gid, g.value);
 			globals.fastSet(g.gid, g.value);
 		setGlobals();
 		setGlobals();
 		setupShaders(passes);
 		setupShaders(passes);
-		if( sortPasses ) {
-			var shaderStart = shaderCount, textureStart = textureCount;
-			for( p in passes ) {
-				if( shaderIdMap[p.shader.id] < shaderStart #if js || shaderIdMap[p.shader.id] == null #end )
-					shaderIdMap[p.shader.id] = shaderCount++;
-				if( textureIdMap[p.texture] < textureStart #if js || textureIdMap[p.shader.id] == null #end )
-					textureIdMap[p.texture] = textureCount++;
-			}
-			passes.sort(function(o1, o2) {
-				var d = shaderIdMap[o1.shader.id] - shaderIdMap[o2.shader.id];
-				if( d != 0 ) return d;
-				return textureIdMap[o1.texture] - textureIdMap[o2.texture];
-			});
-		}
+		if( sort == null )
+			defaultSort(passes);
+		else
+			sort(passes);
 		ctx.currentManager = manager;
 		ctx.currentManager = manager;
 		var buf = ctx.shaderBuffers, prevShader = null;
 		var buf = ctx.shaderBuffers, prevShader = null;
 		for( p in passes ) {
 		for( p in passes ) {

+ 2 - 2
h3d/pass/DefaultShadowMap.hx

@@ -24,8 +24,8 @@ class DefaultShadowMap extends DirShadowMap {
 		shadowBiasId = hxsl.Globals.allocID("shadow.bias");
 		shadowBiasId = hxsl.Globals.allocID("shadow.bias");
 	}
 	}
 
 
-	override function draw( passes ) {
-		super.draw(passes);
+	override function draw( passes, ?sort ) {
+		super.draw(passes, sort);
 		ctx.setGlobalID(shadowMapId, { texture : dshader.shadowMap, channel : format == h3d.mat.Texture.nativeFormat ? hxsl.Channel.PackedFloat : hxsl.Channel.R });
 		ctx.setGlobalID(shadowMapId, { texture : dshader.shadowMap, channel : format == h3d.mat.Texture.nativeFormat ? hxsl.Channel.PackedFloat : hxsl.Channel.R });
 		ctx.setGlobalID(shadowProjId, getShadowProj());
 		ctx.setGlobalID(shadowProjId, getShadowProj());
 		ctx.setGlobalID(shadowColorId, color);
 		ctx.setGlobalID(shadowColorId, color);

+ 2 - 2
h3d/pass/DirShadowMap.hx

@@ -185,7 +185,7 @@ class DirShadowMap extends Shadows {
 		return true;
 		return true;
 	}
 	}
 
 
-	override function draw( passes ) {
+	override function draw( passes, ?sort ) {
 		if( !filterPasses(passes) )
 		if( !filterPasses(passes) )
 			return;
 			return;
 
 
@@ -215,7 +215,7 @@ class DirShadowMap extends Shadows {
 
 
 		ctx.engine.pushTarget(texture);
 		ctx.engine.pushTarget(texture);
 		ctx.engine.clear(0xFFFFFF, 1);
 		ctx.engine.clear(0xFFFFFF, 1);
-		super.draw(passes);
+		super.draw(passes, sort);
 		if( border != null ) border.render();
 		if( border != null ) border.render();
 		ctx.engine.popTarget();
 		ctx.engine.popTarget();
 
 

+ 2 - 2
h3d/pass/HardwarePick.hx

@@ -63,7 +63,7 @@ class HardwarePick extends Default {
 		fixedColor.colorID.setColor(0xFF000000 | (++colorID));
 		fixedColor.colorID.setColor(0xFF000000 | (++colorID));
 	}
 	}
 
 
-	override function draw(passes:h3d.pass.PassList) {
+	override function draw(passes:h3d.pass.PassList,?sort) {
 
 
 		for( cur in passes ) @:privateAccess {
 		for( cur in passes ) @:privateAccess {
 			// force all materials to use opaque blend
 			// force all materials to use opaque blend
@@ -78,7 +78,7 @@ class HardwarePick extends Default {
 		ctx.engine.pushTarget(texOut);
 		ctx.engine.pushTarget(texOut);
 		ctx.engine.clear(0xFF000000, 1);
 		ctx.engine.clear(0xFF000000, 1);
 		ctx.extraShaders = ctx.allocShaderList(fixedColor);
 		ctx.extraShaders = ctx.allocShaderList(fixedColor);
-		super.draw(passes);
+		super.draw(passes,sort);
 		ctx.extraShaders = null;
 		ctx.extraShaders = null;
 		ctx.engine.popTarget();
 		ctx.engine.popTarget();
 
 

+ 2 - 2
h3d/pass/PointShadowMap.hx

@@ -138,7 +138,7 @@ class PointShadowMap extends Shadows {
 		return tex;
 		return tex;
 	}
 	}
 
 
-	override function draw( passes ) {
+	override function draw( passes, ?sort ) {
 		if( !filterPasses(passes) )
 		if( !filterPasses(passes) )
 			return;
 			return;
 
 
@@ -177,7 +177,7 @@ class PointShadowMap extends Shadows {
 
 
 			var save = passes.save();
 			var save = passes.save();
 			cullPasses(passes, function(col) return col.inFrustum(lightCamera.frustum));
 			cullPasses(passes, function(col) return col.inFrustum(lightCamera.frustum));
-			super.draw(passes);
+			super.draw(passes,sort);
 			passes.load(save);
 			passes.load(save);
 			ctx.engine.popTarget();
 			ctx.engine.popTarget();
 		}
 		}

+ 1 - 1
h3d/pass/ScreenFx.hx

@@ -55,7 +55,7 @@ class ScreenFx<T:h3d.shader.ScreenShader> {
 
 
 	public function getShader<T:hxsl.Shader>(cl:Class<T>) : T {
 	public function getShader<T:hxsl.Shader>(cl:Class<T>) : T {
 		for( s in shaders ) {
 		for( s in shaders ) {
-			var si = Std.instance(s, cl);
+			var si = hxd.impl.Api.downcast(s, cl);
 			if( si != null ) return si;
 			if( si != null ) return si;
 		}
 		}
 		return null;
 		return null;

+ 30 - 0
h3d/pass/SortByMaterial.hx

@@ -0,0 +1,30 @@
+package h3d.pass;
+
+class SortByMaterial {
+
+	var shaderCount : Int = 1;
+	var textureCount : Int = 1;
+	var shaderIdMap : Array<Int>;
+	var textureIdMap : Array<Int>;
+
+	public function new() {
+		shaderIdMap = [];
+		textureIdMap = [];
+	}
+
+	public function sort( passes : PassList ) {
+		var shaderStart = shaderCount, textureStart = textureCount;
+		for( p in passes ) {
+			if( shaderIdMap[p.shader.id] < shaderStart #if js || shaderIdMap[p.shader.id] == null #end )
+				shaderIdMap[p.shader.id] = shaderCount++;
+			if( textureIdMap[p.texture] < textureStart #if js || textureIdMap[p.shader.id] == null #end )
+				textureIdMap[p.texture] = textureCount++;
+		}
+		passes.sort(function(o1, o2) {
+			var d = shaderIdMap[o1.shader.id] - shaderIdMap[o2.shader.id];
+			if( d != 0 ) return d;
+			return textureIdMap[o1.texture] - textureIdMap[o2.texture];
+		});
+	}
+
+}

+ 2 - 2
h3d/pass/SpotShadowMap.hx

@@ -102,7 +102,7 @@ class SpotShadowMap extends Shadows {
 		return true;
 		return true;
 	}
 	}
 
 
-	override function draw( passes ) {
+	override function draw( passes, ?sort ) {
 		if( !filterPasses(passes) )
 		if( !filterPasses(passes) )
 			return;
 			return;
 
 
@@ -118,7 +118,7 @@ class SpotShadowMap extends Shadows {
 
 
 		ctx.engine.pushTarget(texture);
 		ctx.engine.pushTarget(texture);
 		ctx.engine.clear(0xFFFFFF, 1);
 		ctx.engine.clear(0xFFFFFF, 1);
-		super.draw(passes);
+		super.draw(passes, sort);
 		if( border != null ) border.render();
 		if( border != null ) border.render();
 		ctx.engine.popTarget();
 		ctx.engine.popTarget();
 
 

+ 7 - 7
h3d/scene/Object.hx

@@ -364,7 +364,7 @@ class Object implements hxd.impl.Serializable {
 	**/
 	**/
 	public function getMeshes( ?out : Array<Mesh> ) {
 	public function getMeshes( ?out : Array<Mesh> ) {
 		if( out == null ) out = [];
 		if( out == null ) out = [];
-		var m = Std.instance(this, Mesh);
+		var m = hxd.impl.Api.downcast(this, Mesh);
 		if( m != null ) out.push(m);
 		if( m != null ) out.push(m);
 		for( c in children )
 		for( c in children )
 			c.getMeshes(out);
 			c.getMeshes(out);
@@ -375,7 +375,7 @@ class Object implements hxd.impl.Serializable {
 		Search for an mesh recursively by name, return null if not found.
 		Search for an mesh recursively by name, return null if not found.
 	**/
 	**/
 	public function getMeshByName( name : String) {
 	public function getMeshByName( name : String) {
-		return Std.instance(getObjectByName(name), Mesh);
+		return hxd.impl.Api.downcast(getObjectByName(name), Mesh);
 	}
 	}
 
 
 	/**
 	/**
@@ -466,7 +466,7 @@ class Object implements hxd.impl.Serializable {
 		if( !visible || (culled && inheritCulled) )
 		if( !visible || (culled && inheritCulled) )
 			return;
 			return;
 		if( !culled ) {
 		if( !culled ) {
-			var m = Std.instance(this, Mesh);
+			var m = hxd.impl.Api.downcast(this, Mesh);
 			if( m != null ) callb(m);
 			if( m != null ) callb(m);
 		}
 		}
 		for( o in children )
 		for( o in children )
@@ -525,7 +525,7 @@ class Object implements hxd.impl.Serializable {
 	public function getScene() {
 	public 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 hxd.impl.Api.downcast(p, Scene);
 	}
 	}
 
 
 	/**
 	/**
@@ -540,14 +540,14 @@ class Object implements hxd.impl.Serializable {
 		Tell if the object is a Mesh.
 		Tell if the object is a Mesh.
 	**/
 	**/
 	public inline function isMesh() {
 	public inline function isMesh() {
-		return Std.instance(this, Mesh) != null;
+		return hxd.impl.Api.downcast(this, Mesh) != null;
 	}
 	}
 
 
 	/**
 	/**
 		If the object is a Mesh, return the corresponding Mesh. If not, throw an exception.
 		If the object is a Mesh, return the corresponding Mesh. If not, throw an exception.
 	**/
 	**/
 	public function toMesh() : Mesh {
 	public function toMesh() : Mesh {
-		var m = Std.instance(this, Mesh);
+		var m = hxd.impl.Api.downcast(this, Mesh);
 		if( m != null )
 		if( m != null )
 			return m;
 			return m;
 		throw this + " is not a Mesh";
 		throw this + " is not a Mesh";
@@ -567,7 +567,7 @@ class Object implements hxd.impl.Serializable {
 		for( obj in children ) {
 		for( obj in children ) {
 			var c = obj.getCollider();
 			var c = obj.getCollider();
 			if( c == null ) continue;
 			if( c == null ) continue;
-			var cgrp = Std.instance(c, h3d.col.Collider.GroupCollider);
+			var cgrp = hxd.impl.Api.downcast(c, h3d.col.Collider.GroupCollider);
 			if( cgrp != null ) {
 			if( cgrp != null ) {
 				for( c in cgrp.colliders )
 				for( c in cgrp.colliders )
 					colliders.push(c);
 					colliders.push(c);

+ 9 - 12
h3d/scene/Renderer.hx

@@ -26,15 +26,20 @@ class Renderer extends hxd.impl.AnyProps {
 	var emptyPasses = new h3d.pass.PassList();
 	var emptyPasses = new h3d.pass.PassList();
 	var ctx : RenderContext;
 	var ctx : RenderContext;
 	var hasSetTarget = false;
 	var hasSetTarget = false;
+	var frontToBack : h3d.pass.PassList -> Void;
+	var backToFront : h3d.pass.PassList -> Void;
 
 
 	public var effects : Array<h3d.impl.RendererFX> = [];
 	public var effects : Array<h3d.impl.RendererFX> = [];
-	
+
 	public var renderMode : RenderMode = Default;
 	public var renderMode : RenderMode = Default;
 
 
 	public function new() {
 	public function new() {
 		allPasses = [];
 		allPasses = [];
 		passObjects = new SMap();
 		passObjects = new SMap();
 		props = getDefaultProps();
 		props = getDefaultProps();
+		// pre allocate closures
+		frontToBack = depthSort.bind(true);
+		backToFront = depthSort.bind(false);
 	}
 	}
 
 
 	public function dispose() {
 	public function dispose() {
@@ -82,7 +87,7 @@ class Renderer extends hxd.impl.AnyProps {
 	}
 	}
 
 
 	@:access(h3d.scene.Object)
 	@:access(h3d.scene.Object)
-	function depthSort( passes : h3d.pass.PassList, frontToBack = false ) {
+	function depthSort( frontToBack, passes : h3d.pass.PassList ) {
 		var cam = ctx.camera.m;
 		var cam = ctx.camera.m;
 		for( p in passes ) {
 		for( p in passes ) {
 			var z = p.obj.absPos._41 * cam._13 + p.obj.absPos._42 * cam._23 + p.obj.absPos._43 * cam._33 + cam._43;
 			var z = p.obj.absPos._41 * cam._13 + p.obj.absPos._42 * cam._23 + p.obj.absPos._43 * cam._33 + cam._43;
@@ -90,9 +95,9 @@ class Renderer extends hxd.impl.AnyProps {
 			p.depth = z / w;
 			p.depth = z / w;
 		}
 		}
 		if( frontToBack )
 		if( frontToBack )
-			passes.sort(function(p1, p2) return p1.depth > p2.depth ? 1 : -1);
+			passes.sort(function(p1, p2) return p1.pass.layer == p2.pass.layer ? (p1.depth > p2.depth ? 1 : -1) : p1.pass.layer - p2.pass.layer);
 		else
 		else
-			passes.sort(function(p1, p2) return p1.depth > p2.depth ? -1 : 1);
+			passes.sort(function(p1, p2) return p1.pass.layer == p2.pass.layer ? (p1.depth > p2.depth ? -1 : 1) : p1.pass.layer - p2.pass.layer);
 	}
 	}
 
 
 	inline function clear( ?color, ?depth, ?stencil ) {
 	inline function clear( ?color, ?depth, ?stencil ) {
@@ -137,14 +142,6 @@ class Renderer extends hxd.impl.AnyProps {
 		return p.passes;
 		return p.passes;
 	}
 	}
 
 
-	function getSort( name : String, front2Back = false ) {
-		var p = passObjects.get(name);
-		if( p == null ) return emptyPasses;
-		depthSort(p.passes, front2Back);
-		p.rendered = true;
-		return p.passes;
-	}
-
 	function draw( name : String ) {
 	function draw( name : String ) {
 		defaultPass.draw(get(name));
 		defaultPass.draw(get(name));
 	}
 	}

+ 7 - 1
h3d/scene/World.hx

@@ -199,6 +199,7 @@ class World extends Object {
 	var allChunks : Array<WorldChunk>;
 	var allChunks : Array<WorldChunk>;
 	var bigTextures : Array<{ diffuse : h3d.mat.BigTexture, spec : h3d.mat.BigTexture, normal : h3d.mat.BigTexture }>;
 	var bigTextures : Array<{ diffuse : h3d.mat.BigTexture, spec : h3d.mat.BigTexture, normal : h3d.mat.BigTexture }>;
 	var textures : Map<String, WorldMaterial>;
 	var textures : Map<String, WorldMaterial>;
+	var autoCollect : Bool;
 
 
 	public function new( chunkSize : Int, worldSize : Int, ?parent, ?autoCollect = true ) {
 	public function new( chunkSize : Int, worldSize : Int, ?parent, ?autoCollect = true ) {
 		super(parent);
 		super(parent);
@@ -209,6 +210,7 @@ class World extends Object {
 		this.chunkSize = chunkSize;
 		this.chunkSize = chunkSize;
 		this.worldSize = worldSize;
 		this.worldSize = worldSize;
 		this.worldStride = Math.ceil(worldSize / chunkSize);
 		this.worldStride = Math.ceil(worldSize / chunkSize);
+		this.autoCollect = autoCollect;
 		if( autoCollect )
 		if( autoCollect )
 			h3d.Engine.getCurrent().mem.garbage = garbage;
 			h3d.Engine.getCurrent().mem.garbage = garbage;
 	}
 	}
@@ -530,7 +532,7 @@ class World extends Object {
 					c.buffers.set(g.m.bits, b);
 					c.buffers.set(g.m.bits, b);
 					initMaterial(b, g.m);
 					initMaterial(b, g.m);
 				}
 				}
-				var p = Std.instance(b.primitive, h3d.prim.BigPrimitive);
+				var p = hxd.impl.Api.downcast(b.primitive, h3d.prim.BigPrimitive);
 
 
 				if(e.optimized) {
 				if(e.optimized) {
 					var m = e.transform;
 					var m = e.transform;
@@ -606,8 +608,12 @@ class World extends Object {
 		}
 		}
 		bigTextures = [];
 		bigTextures = [];
 		textures = new Map();
 		textures = new Map();
+		if( autoCollect )
+			h3d.Engine.getCurrent().mem.garbage = noGarbage;
 	}
 	}
 
 
+	static function noGarbage() {}
+
 	public function onContextLost() {
 	public function onContextLost() {
 		for( c in allChunks )
 		for( c in allChunks )
 			cleanChunk(c);
 			cleanChunk(c);

+ 2 - 2
h3d/scene/fwd/LightSystem.hx

@@ -21,11 +21,11 @@ class LightSystem extends h3d.scene.LightSystem {
 	}
 	}
 
 
 	function get_additiveLighting() {
 	function get_additiveLighting() {
-		return Std.instance(ambientShader,h3d.shader.AmbientLight).additive;
+		return hxd.impl.Api.downcast(ambientShader,h3d.shader.AmbientLight).additive;
 	}
 	}
 
 
 	function set_additiveLighting(b) {
 	function set_additiveLighting(b) {
-		return Std.instance(ambientShader,h3d.shader.AmbientLight).additive = b;
+		return hxd.impl.Api.downcast(ambientShader,h3d.shader.AmbientLight).additive = b;
 	}
 	}
 
 
 	override function initLights(ctx) {
 	override function initLights(ctx) {

+ 8 - 8
h3d/scene/fwd/Renderer.hx

@@ -16,11 +16,11 @@ class DepthPass extends h3d.pass.Default {
 		return [PackFloat(Value("output.depth"))];
 		return [PackFloat(Value("output.depth"))];
 	}
 	}
 
 
-	override function draw( passes ) {
+	override function draw( passes, ?sort ) {
 		var texture = ctx.textures.allocTarget("depthMap", ctx.engine.width, ctx.engine.height, true);
 		var texture = ctx.textures.allocTarget("depthMap", ctx.engine.width, ctx.engine.height, true);
 		ctx.engine.pushTarget(texture);
 		ctx.engine.pushTarget(texture);
 		ctx.engine.clear(enableSky ? 0 : 0xFF0000, 1);
 		ctx.engine.clear(enableSky ? 0 : 0xFF0000, 1);
-		super.draw(passes);
+		super.draw(passes, sort);
 		ctx.engine.popTarget();
 		ctx.engine.popTarget();
 		ctx.setGlobalID(depthMapId, { texture : texture });
 		ctx.setGlobalID(depthMapId, { texture : texture });
 	}
 	}
@@ -40,11 +40,11 @@ class NormalPass extends h3d.pass.Default {
 		return [PackNormal(Value("output.normal"))];
 		return [PackNormal(Value("output.normal"))];
 	}
 	}
 
 
-	override function draw( passes ) {
+	override function draw( passes, ?sort ) {
 		var texture = ctx.textures.allocTarget("normalMap", ctx.engine.width, ctx.engine.height);
 		var texture = ctx.textures.allocTarget("normalMap", ctx.engine.width, ctx.engine.height);
 		ctx.engine.pushTarget(texture);
 		ctx.engine.pushTarget(texture);
 		ctx.engine.clear(0x808080, 1);
 		ctx.engine.clear(0x808080, 1);
-		super.draw(passes);
+		super.draw(passes, sort);
 		ctx.engine.popTarget();
 		ctx.engine.popTarget();
 		ctx.setGlobalID(normalMapId, texture);
 		ctx.setGlobalID(normalMapId, texture);
 	}
 	}
@@ -67,8 +67,8 @@ class Renderer extends h3d.scene.Renderer {
 	inline function get_def() return defaultPass;
 	inline function get_def() return defaultPass;
 
 
 	// can be overriden for benchmark purposes
 	// can be overriden for benchmark purposes
-	function renderPass(p:h3d.pass.Base, passes) {
-		p.draw(passes);
+	function renderPass(p:h3d.pass.Base, passes, ?sort) {
+		p.draw(passes, sort);
 	}
 	}
 
 
 	override function render() {
 	override function render() {
@@ -81,8 +81,8 @@ class Renderer extends h3d.scene.Renderer {
 		if( has("normal") )
 		if( has("normal") )
 			renderPass(normal,get("normal"));
 			renderPass(normal,get("normal"));
 
 
-		renderPass(defaultPass, getSort("default", true) );
-		renderPass(defaultPass, getSort("alpha") );
+		renderPass(defaultPass, get("default") );
+		renderPass(defaultPass, get("alpha"), backToFront );
 		renderPass(defaultPass, get("additive") );
 		renderPass(defaultPass, get("additive") );
 	}
 	}
 
 

+ 2 - 2
h3d/scene/pbr/LightSystem.hx

@@ -4,7 +4,7 @@ package h3d.scene.pbr;
 class LightSystem extends h3d.scene.LightSystem {
 class LightSystem extends h3d.scene.LightSystem {
 
 
 	override function computeLight( obj : h3d.scene.Object, shaders : hxsl.ShaderList ) : hxsl.ShaderList {
 	override function computeLight( obj : h3d.scene.Object, shaders : hxsl.ShaderList ) : hxsl.ShaderList {
-		var light = Std.instance(obj, h3d.scene.pbr.Light);
+		var light = hxd.impl.Api.downcast(obj, h3d.scene.pbr.Light);
 		if( light != null ) {
 		if( light != null ) {
 			shaders = ctx.allocShaderList(light.shader, shaders);
 			shaders = ctx.allocShaderList(light.shader, shaders);
 			if( light.shadows.shader != null && light.shadows.mode != None )
 			if( light.shadows.shader != null && light.shadows.mode != None )
@@ -26,7 +26,7 @@ class LightSystem extends h3d.scene.LightSystem {
 		var width = currentTarget == null ? ctx.engine.width : currentTarget.width;
 		var width = currentTarget == null ? ctx.engine.width : currentTarget.width;
 		var height = currentTarget == null ? ctx.engine.height : currentTarget.height;
 		var height = currentTarget == null ? ctx.engine.height : currentTarget.height;
 		while( plight != null ) {
 		while( plight != null ) {
-			var light = Std.instance(plight, h3d.scene.pbr.Light);
+			var light = hxd.impl.Api.downcast(plight, h3d.scene.pbr.Light);
 			if( light != null && light.primitive == null ) {
 			if( light != null && light.primitive == null ) {
 				if( light.shadows.shader != null ) lightPass.addShader(light.shadows.shader);
 				if( light.shadows.shader != null ) lightPass.addShader(light.shadows.shader);
 				lightPass.addShader(light.shader);
 				lightPass.addShader(light.shader);

+ 7 - 10
h3d/scene/pbr/Renderer.hx

@@ -180,15 +180,15 @@ class Renderer extends h3d.scene.Renderer {
 		passes.reset();
 		passes.reset();
 	}
 	}
 
 
-	function renderPass(p:h3d.pass.Base, passes) {
+	function renderPass(p:h3d.pass.Base, passes, ?sort) {
 		cullPasses(passes, function(col) return col.inFrustum(ctx.camera.frustum));
 		cullPasses(passes, function(col) return col.inFrustum(ctx.camera.frustum));
-		p.draw(passes);
+		p.draw(passes, sort);
 		passes.reset();
 		passes.reset();
 	}
 	}
 
 
 	function mainDraw() {
 	function mainDraw() {
-		renderPass(output, getSort("default", true));
-		renderPass(output, getSort("alpha"));
+		renderPass(output, get("default"), frontToBack);
+		renderPass(output, get("alpha"), backToFront);
 		renderPass(output, get("additive"));
 		renderPass(output, get("additive"));
 	}
 	}
 
 
@@ -206,7 +206,7 @@ class Renderer extends h3d.scene.Renderer {
 		if( !shadows )
 		if( !shadows )
 			passes.clear();
 			passes.clear();
 		while( light != null ) {
 		while( light != null ) {
-			var plight = Std.instance(light, h3d.scene.pbr.Light);
+			var plight = hxd.impl.Api.downcast(light, h3d.scene.pbr.Light);
 			if( plight != null ) ls.drawLight(plight, passes);
 			if( plight != null ) ls.drawLight(plight, passes);
 			light = light.next;
 			light = light.next;
 		}
 		}
@@ -222,7 +222,7 @@ class Renderer extends h3d.scene.Renderer {
 		var light = @:privateAccess ctx.lights;
 		var light = @:privateAccess ctx.lights;
 		var passes = get("shadow");
 		var passes = get("shadow");
 		while( light != null ) {
 		while( light != null ) {
-			var plight = Std.instance(light, h3d.scene.pbr.Light);
+			var plight = hxd.impl.Api.downcast(light, h3d.scene.pbr.Light);
 			if( plight != null ) {
 			if( plight != null ) {
 				plight.shadows.setContext(ctx);
 				plight.shadows.setContext(ctx);
 				plight.shadows.computeStatic(passes);
 				plight.shadows.computeStatic(passes);
@@ -246,7 +246,7 @@ class Renderer extends h3d.scene.Renderer {
 		ctx.setGlobal("occlusionMap",{ texture : pbr, channel : hxsl.Channel.B });
 		ctx.setGlobal("occlusionMap",{ texture : pbr, channel : hxsl.Channel.B });
 		ctx.setGlobal("bloom",null);
 		ctx.setGlobal("bloom",null);
 
 
-		var ls = Std.instance(getLightSystem(), LightSystem);
+		var ls = hxd.impl.Api.downcast(getLightSystem(), LightSystem);
 		var count = ctx.engine.drawCalls;
 		var count = ctx.engine.drawCalls;
 		if( ls != null ) drawShadows(ls);
 		if( ls != null ) drawShadows(ls);
 		if( ctx.lightSystem != null ) ctx.lightSystem.drawPasses = ctx.engine.drawCalls - count;
 		if( ctx.lightSystem != null ) ctx.lightSystem.drawPasses = ctx.engine.drawCalls - count;
@@ -266,9 +266,6 @@ class Renderer extends h3d.scene.Renderer {
 		setTargets([albedo,normal,pbr]);
 		setTargets([albedo,normal,pbr]);
 		renderPass(decalsOutput, get("decal"));
 		renderPass(decalsOutput, get("decal"));
 
 
-		setTarget(albedo);
-		draw("albedo");
-
 		if(renderMode == Default){
 		if(renderMode == Default){
 			if( displayMode == Env )
 			if( displayMode == Env )
 				clear(0xFF404040);
 				clear(0xFF404040);

+ 4 - 4
hxd/App.hx

@@ -64,8 +64,8 @@ class App implements h3d.IDrawable {
 		If you call disposePrevious, it will call dispose() on the previous scene.
 		If you call disposePrevious, it will call dispose() on the previous scene.
 	**/
 	**/
 	public function setScene( scene : hxd.SceneEvents.InteractiveScene, disposePrevious = true ) {
 	public function setScene( scene : hxd.SceneEvents.InteractiveScene, disposePrevious = true ) {
-		var new2D = Std.instance(scene, h2d.Scene);
-		var new3D = Std.instance(scene, h3d.scene.Scene);
+		var new2D = hxd.impl.Api.downcast(scene, h2d.Scene);
+		var new3D = hxd.impl.Api.downcast(scene, h3d.scene.Scene);
 		if( new2D != null )
 		if( new2D != null )
 			sevents.removeScene(s2d);
 			sevents.removeScene(s2d);
 		if( new3D != null )
 		if( new3D != null )
@@ -148,7 +148,7 @@ class App implements h3d.IDrawable {
 		                method when loading is complete
 		                method when loading is complete
 	**/
 	**/
 	@:dox(show)
 	@:dox(show)
-	function loadAssets( onLoaded ) {
+	function loadAssets( onLoaded : Void->Void ) {
 		onLoaded();
 		onLoaded();
 	}
 	}
 
 
@@ -188,4 +188,4 @@ class App implements h3d.IDrawable {
 
 
 	static function staticHandler() {}
 	static function staticHandler() {}
 
 
-}
+}

+ 3 - 3
hxd/fmt/fbx/Library.hx

@@ -113,7 +113,7 @@ class Library extends BaseLibrary {
 			if( o.parent.isJoint )
 			if( o.parent.isJoint )
 				o.obj.follow = scene.getObjectByName(o.parent.joint.name);
 				o.obj.follow = scene.getObjectByName(o.parent.joint.name);
 
 
-			var skin = Std.instance(o.obj, h3d.scene.Skin);
+			var skin = hxd.impl.Api.downcast(o.obj, h3d.scene.Skin);
 			if( skin == null ) continue;
 			if( skin == null ) continue;
 			var rootJoints = [];
 			var rootJoints = [];
 			for( j in o.childs )
 			for( j in o.childs )
@@ -126,7 +126,7 @@ class Library extends BaseLibrary {
 				var m = o2.obj.toMesh();
 				var m = o2.obj.toMesh();
 				if( m.primitive != skinData.primitive ) continue;
 				if( m.primitive != skinData.primitive ) continue;
 
 
-				var mt = Std.instance(m, h3d.scene.MultiMaterial);
+				var mt = hxd.impl.Api.downcast(m, h3d.scene.MultiMaterial);
 				skin.materials = mt == null ? [m.material] : mt.materials;
 				skin.materials = mt == null ? [m.material] : mt.materials;
 				skin.material = skin.materials[0];
 				skin.material = skin.materials[0];
 				m.remove();
 				m.remove();
@@ -135,7 +135,7 @@ class Library extends BaseLibrary {
 			}
 			}
 			// set skin after materials
 			// set skin after materials
 			if( skinData.boundJoints.length > maxBonesPerSkin ) {
 			if( skinData.boundJoints.length > maxBonesPerSkin ) {
-				var model = Std.instance(skinData.primitive, h3d.prim.FBXModel);
+				var model = hxd.impl.Api.downcast(skinData.primitive, h3d.prim.FBXModel);
 				var idx = model.geom.getIndexes();
 				var idx = model.geom.getIndexes();
 				skinData.split(maxBonesPerSkin, [for( i in idx.idx) idx.vidx[i]], model.multiMaterial ? model.geom.getMaterialByTriangle() : null);
 				skinData.split(maxBonesPerSkin, [for( i in idx.idx) idx.vidx[i]], model.multiMaterial ? model.geom.getMaterialByTriangle() : null);
 			}
 			}

+ 2 - 2
hxd/fmt/hsd/Serializer.hx

@@ -45,7 +45,7 @@ class Serializer extends hxbit.Serializer {
 			}
 			}
 			return true;
 			return true;
 		}
 		}
-		var tch = Std.instance(t, h3d.mat.TextureChannels);
+		var tch = hxd.impl.Api.downcast(t, h3d.mat.TextureChannels);
 		if( tch != null ) {
 		if( tch != null ) {
 			addInt(3);
 			addInt(3);
 			var channels = @:privateAccess tch.channels;
 			var channels = @:privateAccess tch.channels;
@@ -161,7 +161,7 @@ class Serializer extends hxbit.Serializer {
 		} else
 		} else
 			s = Type.createEmptyInstance(cl);
 			s = Type.createEmptyInstance(cl);
 		@:privateAccess s.initialize();
 		@:privateAccess s.initialize();
-		var sdyn = Std.instance(s, hxsl.DynamicShader);
+		var sdyn = hxd.impl.Api.downcast(s, hxsl.DynamicShader);
 		for( v in @:privateAccess s.shader.data.vars ) {
 		for( v in @:privateAccess s.shader.data.vars ) {
 			if( !canSerializeVar(v) ) continue;
 			if( !canSerializeVar(v) ) continue;
 			var val : Dynamic = getShaderVar(v, s);
 			var val : Dynamic = getShaderVar(v, s);

+ 1 - 1
hxd/fmt/pak/Loader.hx

@@ -16,7 +16,7 @@ class Loader extends h2d.Object {
 		this.onDone = onDone;
 		this.onDone = onDone;
 		if( hxd.res.Loader.currentInstance == null )
 		if( hxd.res.Loader.currentInstance == null )
 			hxd.res.Loader.currentInstance = new hxd.res.Loader(new FileSystem());
 			hxd.res.Loader.currentInstance = new hxd.res.Loader(new FileSystem());
-		fs = Std.instance(hxd.res.Loader.currentInstance.fs, FileSystem);
+		fs = hxd.impl.Api.downcast(hxd.res.Loader.currentInstance.fs, FileSystem);
 		if( fs == null )
 		if( fs == null )
 			throw "Can only use loader with PAK file system";
 			throw "Can only use loader with PAK file system";
 		hxd.System.setLoop(render);
 		hxd.System.setLoop(render);

+ 13 - 0
hxd/impl/Api.hx

@@ -0,0 +1,13 @@
+package hxd.impl;
+
+class Api {
+
+	public static inline function downcast<T:{},S:T>( value : T, c : Class<S> ) : S {
+		#if haxe4
+		return Std.downcast(value,c);
+		#else
+		return Std.instance(value,c);
+		#end
+	}
+
+}

+ 1 - 1
hxd/inspect/PropManager.hx

@@ -230,7 +230,7 @@ class PropManager extends vdom.Client {
 	public function getResPath() {
 	public function getResPath() {
 		if( cachedResPath != null )
 		if( cachedResPath != null )
 			return cachedResPath;
 			return cachedResPath;
-		var lfs = Std.instance(hxd.res.Loader.currentInstance.fs, hxd.fs.LocalFileSystem);
+		var lfs = hxd.impl.Api.downcast(hxd.res.Loader.currentInstance.fs, hxd.fs.LocalFileSystem);
 		if( lfs != null )
 		if( lfs != null )
 			cachedResPath = lfs.baseDir;
 			cachedResPath = lfs.baseDir;
 		else {
 		else {

+ 3 - 3
hxd/inspect/ScenePanel.hx

@@ -200,15 +200,15 @@ private class CustomSceneProps extends SceneProps {
 
 
 	override function getObjectProps( o : h3d.scene.Object ) {
 	override function getObjectProps( o : h3d.scene.Object ) {
 		var props = super.getObjectProps(o);
 		var props = super.getObjectProps(o);
-		var world = Std.instance(o, h3d.scene.World);
+		var world = hxd.impl.Api.downcast(o, h3d.scene.World);
 		var worldObject = world == null ? o : null;
 		var worldObject = world == null ? o : null;
 		if( world == null ) {
 		if( world == null ) {
-			world = Std.instance(o.parent, h3d.scene.World);
+			world = hxd.impl.Api.downcast(o.parent, h3d.scene.World);
 			if( world != null && !Lambda.exists(@:privateAccess world.allChunks, function(c) return c.root == o) )
 			if( world != null && !Lambda.exists(@:privateAccess world.allChunks, function(c) return c.root == o) )
 				world = null;
 				world = null;
 		}
 		}
 		if( world == null && o.parent != null ) {
 		if( world == null && o.parent != null ) {
-			world = Std.instance(o.parent.parent, h3d.scene.World);
+			world = hxd.impl.Api.downcast(o.parent.parent, h3d.scene.World);
 			if( world != null && !Lambda.exists(@:privateAccess world.allChunks, function(c) return c.root == o.parent) )
 			if( world != null && !Lambda.exists(@:privateAccess world.allChunks, function(c) return c.root == o.parent) )
 				world = null;
 				world = null;
 		}
 		}

+ 10 - 10
hxd/inspect/SceneProps.hx

@@ -81,7 +81,7 @@ class SceneProps {
 
 
 			var ls = scene.lightSystem;
 			var ls = scene.lightSystem;
 			var props = [];
 			var props = [];
-			var fls = Std.instance(ls, h3d.scene.fwd.LightSystem);
+			var fls = hxd.impl.Api.downcast(ls, h3d.scene.fwd.LightSystem);
 			if( fls != null )
 			if( fls != null )
 				props.push(PGroup("LightSystem",[
 				props.push(PGroup("LightSystem",[
 					PRange("maxLightsPerObject", 0, 10, function() return fls.maxLightsPerObject, function(s) fls.maxLightsPerObject = Std.int(s), 1),
 					PRange("maxLightsPerObject", 0, 10, function() return fls.maxLightsPerObject, function(s) fls.maxLightsPerObject = Std.int(s), 1),
@@ -249,13 +249,13 @@ class SceneProps {
 		props.push(PColor("color", false, function() return l.color, function(c) l.color.load(c)));
 		props.push(PColor("color", false, function() return l.color, function(c) l.color.load(c)));
 		props.push(PRange("priority", 0, 10, function() return l.priority, function(p) l.priority = Std.int(p),1));
 		props.push(PRange("priority", 0, 10, function() return l.priority, function(p) l.priority = Std.int(p),1));
 		props.push(PBool("enableSpecular", function() return l.enableSpecular, function(b) l.enableSpecular = b));
 		props.push(PBool("enableSpecular", function() return l.enableSpecular, function(b) l.enableSpecular = b));
-		var dl = Std.instance(l, h3d.scene.fwd.DirLight);
+		var dl = hxd.impl.Api.downcast(l, h3d.scene.fwd.DirLight);
 		if( dl != null )
 		if( dl != null )
 			props.push(PFloats("direction", function() {
 			props.push(PFloats("direction", function() {
 				var dir = dl.getDirection();
 				var dir = dl.getDirection();
 				return [dl.x, dl.y, dl.z];
 				return [dl.x, dl.y, dl.z];
 			}, function(fl) dl.setDirection(new h3d.Vector(fl[0], fl[1], fl[2]))));
 			}, function(fl) dl.setDirection(new h3d.Vector(fl[0], fl[1], fl[2]))));
-		var pl = Std.instance(l, h3d.scene.fwd.PointLight);
+		var pl = hxd.impl.Api.downcast(l, h3d.scene.fwd.PointLight);
 		if( pl != null )
 		if( pl != null )
 			props.push(PFloats("params", function() return [pl.params.x, pl.params.y, pl.params.z], function(fl) pl.params.set(fl[0], fl[1], fl[2], fl[3])));
 			props.push(PFloats("params", function() return [pl.params.x, pl.params.y, pl.params.z], function(fl) pl.params.set(fl[0], fl[1], fl[2], fl[3])));
 		return PGroup("Light", props);
 		return PGroup("Light", props);
@@ -269,17 +269,17 @@ class SceneProps {
 		props.push(PBool("visible", function() return o.visible, function(v) o.visible = v));
 		props.push(PBool("visible", function() return o.visible, function(v) o.visible = v));
 
 
 		if( o.isMesh() ) {
 		if( o.isMesh() ) {
-			var multi = Std.instance(o, h3d.scene.MultiMaterial);
+			var multi = hxd.impl.Api.downcast(o, h3d.scene.MultiMaterial);
 			if( multi != null && multi.materials.length > 1 ) {
 			if( multi != null && multi.materials.length > 1 ) {
 				for( m in multi.materials )
 				for( m in multi.materials )
 					props.push(getMaterialProps(m));
 					props.push(getMaterialProps(m));
 			} else
 			} else
 				props.push(getMaterialProps(o.toMesh().material));
 				props.push(getMaterialProps(o.toMesh().material));
 		} else {
 		} else {
-			var c = Std.instance(o, h3d.scene.CustomObject);
+			var c = hxd.impl.Api.downcast(o, h3d.scene.CustomObject);
 			if( c != null )
 			if( c != null )
 				props.push(getMaterialProps(c.material));
 				props.push(getMaterialProps(c.material));
-			var l = Std.instance(o, h3d.scene.Light);
+			var l = hxd.impl.Api.downcast(o, h3d.scene.Light);
 			if( l != null )
 			if( l != null )
 				props.push(getLightProps(l));
 				props.push(getLightProps(l));
 		}
 		}
@@ -292,13 +292,13 @@ class SceneProps {
 			addDynamicProps(props, v);
 			addDynamicProps(props, v);
 			return props;
 			return props;
 		}
 		}
-		var s = Std.instance(v, hxsl.Shader);
+		var s = hxd.impl.Api.downcast(v, hxsl.Shader);
 		if( s != null )
 		if( s != null )
 			return [getShaderProps(s)];
 			return [getShaderProps(s)];
-		var o = Std.instance(v, h3d.scene.Object);
+		var o = hxd.impl.Api.downcast(v, h3d.scene.Object);
 		if( o != null )
 		if( o != null )
 			return getObjectProps(o);
 			return getObjectProps(o);
-		var s = Std.instance(v, hxsl.Shader);
+		var s = hxd.impl.Api.downcast(v, hxsl.Shader);
 		if( s != null )
 		if( s != null )
 			return [getShaderProps(s)];
 			return [getShaderProps(s)];
 		return null;
 		return null;
@@ -366,7 +366,7 @@ class SceneProps {
 
 
 	function getPassProps( p : h3d.pass.Base ) {
 	function getPassProps( p : h3d.pass.Base ) {
 		var props = [];
 		var props = [];
-		var def = Std.instance(p, h3d.pass.Default);
+		var def = hxd.impl.Api.downcast(p, h3d.pass.Default);
 		if( def == null ) return props;
 		if( def == null ) return props;
 
 
 		addDynamicProps(props, p);
 		addDynamicProps(props, p);

+ 1 - 1
hxd/res/Loader.hx

@@ -46,7 +46,7 @@ class Loader {
 			currentInstance = old;
 			currentInstance = old;
 			cache.set(path, res);
 			cache.set(path, res);
 		} else {
 		} else {
-			if( Std.instance(res,c) == null )
+			if( hxd.impl.Api.downcast(res,c) == null )
 				throw path+" has been reintrepreted from "+Type.getClass(res)+" to "+c;
 				throw path+" has been reintrepreted from "+Type.getClass(res)+" to "+c;
 		}
 		}
 		return res;
 		return res;

+ 1 - 1
hxd/snd/ChannelBase.hx

@@ -18,7 +18,7 @@ class ChannelBase {
 	public function getEffect<T:Effect>( etype : Class<T> ) : T {
 	public function getEffect<T:Effect>( etype : Class<T> ) : T {
 		if(effects == null) return null;  // Already released
 		if(effects == null) return null;  // Already released
 		for (e in effects) {
 		for (e in effects) {
-			var e = Std.instance(e, etype);
+			var e = hxd.impl.Api.downcast(e, etype);
 			if (e != null) return e;
 			if (e != null) return e;
 		}
 		}
 		return null;
 		return null;

+ 14 - 2
hxd/snd/OggData.hx

@@ -80,8 +80,7 @@ class OggData extends Data {
 
 
 }
 }
 
 
-
-#else
+#elseif stb_ogg_sound
 
 
 private class BytesOutput extends haxe.io.Output {
 private class BytesOutput extends haxe.io.Output {
 
 
@@ -184,4 +183,17 @@ class OggData extends Data {
 
 
 }
 }
 
 
+#else
+
+class OggData extends Data {
+
+	public function new( bytes : haxe.io.Bytes ) {
+	}
+
+	override function decodeBuffer(out:haxe.io.Bytes, outPos:Int, sampleStart:Int, sampleCount:Int) {
+		throw "Ogg support requires -lib stb_ogg_sound";
+	}
+
+}
+
 #end
 #end

+ 1 - 1
hxd/snd/openal/PitchDriver.hx

@@ -7,7 +7,7 @@ import hxd.snd.effect.Pitch;
 class PitchDriver extends EffectDriver<Pitch> {
 class PitchDriver extends EffectDriver<Pitch> {
 
 
 	override function apply(e : Pitch, source : SourceHandle) : Void {
 	override function apply(e : Pitch, source : SourceHandle) : Void {
-		AL.sourcef(source.inst, AL.PITCH, Std.instance(e, hxd.snd.effect.Pitch).value);
+		AL.sourcef(source.inst, AL.PITCH, hxd.impl.Api.downcast(e, hxd.snd.effect.Pitch).value);
 	}
 	}
 
 
 	override function unbind(e : Pitch, source : SourceHandle) : Void {
 	override function unbind(e : Pitch, source : SourceHandle) : Void {

+ 3 - 3
hxd/snd/openal/ReverbDriver.hx

@@ -17,7 +17,7 @@ class ReverbDriver extends hxd.snd.Driver.EffectDriver<Reverb> {
 		this.driver = driver;
 		this.driver = driver;
 		this.dryFilter = new LowPass();
 		this.dryFilter = new LowPass();
 	}
 	}
-	
+
 	override function acquire() : Void {
 	override function acquire() : Void {
 		// create effect
 		// create effect
 		var bytes = driver.getTmpBytes(4);
 		var bytes = driver.getTmpBytes(4);
@@ -70,7 +70,7 @@ class ReverbDriver extends hxd.snd.Driver.EffectDriver<Reverb> {
 		EFX.auxiliaryEffectSloti(slot, EFX.EFFECTSLOT_EFFECT, inst.toInt());
 		EFX.auxiliaryEffectSloti(slot, EFX.EFFECTSLOT_EFFECT, inst.toInt());
 		EFX.auxiliaryEffectSlotf(slot, EFX.EFFECTSLOT_GAIN, e.wetDryMix / 100.0);
 		EFX.auxiliaryEffectSlotf(slot, EFX.EFFECTSLOT_GAIN, e.wetDryMix / 100.0);
 
 
-		@:privateAccess 
+		@:privateAccess
 		e.retainTime = e.decayTime + e.reflectionsDelay + e.reverbDelay;
 		e.retainTime = e.decayTime + e.reflectionsDelay + e.reverbDelay;
 	}
 	}
 
 
@@ -80,7 +80,7 @@ class ReverbDriver extends hxd.snd.Driver.EffectDriver<Reverb> {
 	}
 	}
 
 
 	override function apply(e : Reverb, s : SourceHandle) : Void {
 	override function apply(e : Reverb, s : SourceHandle) : Void {
-		var e = Std.instance(e, hxd.snd.effect.Reverb);
+		var e = hxd.impl.Api.downcast(e, hxd.snd.effect.Reverb);
 		var send = s.getAuxiliarySend(e);
 		var send = s.getAuxiliarySend(e);
 		AL.source3i(s.inst, EFX.AUXILIARY_SEND_FILTER, slot.toInt(), send, EFX.FILTER_NULL);
 		AL.source3i(s.inst, EFX.AUXILIARY_SEND_FILTER, slot.toInt(), send, EFX.FILTER_NULL);
 	}
 	}

+ 1 - 1
hxd/snd/openal/SpatializationDriver.hx

@@ -17,7 +17,7 @@ class SpatializationDriver extends EffectDriver<Spatialization> {
 	}
 	}
 
 
 	override function apply(e : Spatialization, s : SourceHandle) : Void {
 	override function apply(e : Spatialization, s : SourceHandle) : Void {
-		var e = Std.instance(e, hxd.snd.effect.Spatialization);
+		var e = hxd.impl.Api.downcast(e, hxd.snd.effect.Spatialization);
 
 
 		AL.source3f(s.inst, AL.POSITION,  -e.position.x,  e.position.y,  e.position.z);
 		AL.source3f(s.inst, AL.POSITION,  -e.position.x,  e.position.y,  e.position.z);
 		AL.source3f(s.inst, AL.VELOCITY,  -e.velocity.x,  e.velocity.y,  e.velocity.z);
 		AL.source3f(s.inst, AL.VELOCITY,  -e.velocity.x,  e.velocity.y,  e.velocity.z);

+ 77 - 0
samples/ScaleMode2D.hx

@@ -0,0 +1,77 @@
+import hxd.Key;
+import h2d.Scene;
+
+class ScaleMode2D extends SampleApp {
+
+	override function init()
+	{
+
+		var bg = new h2d.Bitmap(h2d.Tile.fromColor(0x333333), s2d);
+		var minBg = new h2d.Bitmap(h2d.Tile.fromColor(0x222222), s2d);
+
+		super.init();
+
+		var mode:Int = 0;
+		// Width and height used in Stretch, LetterBox, Fixed and AutoZoom
+		var width:Int = 320;
+		var height:Int = 240;
+		// Zoom used in Fixed and Zoom
+		var zoom:Float = 1;
+		// Integer Scale used in LetterBox and AutoZoom
+		var intScale:Bool = false;
+		// Vertical and Horizontal Align used in LetterBox and Fixed.
+		var halign:ScaleModeAlign = Center;
+		var valign:ScaleModeAlign = Center;
+
+		var sceneInfo:h2d.Text;
+
+		function setMode()
+		{
+			switch ( mode ) {
+				case 0:
+					s2d.scaleMode = Resize;
+				case 1:
+					s2d.scaleMode = Stretch(width, height);
+				case 2:
+					s2d.scaleMode = LetterBox(width, height, intScale, halign, valign);
+				case 3:
+					s2d.scaleMode = Fixed(width, height, zoom, valign, halign);
+				case 4:
+					s2d.scaleMode = Zoom(zoom);
+				case 5:
+					s2d.scaleMode = AutoZoom(width, height, intScale);
+			}
+			minBg.scaleX = width;
+			minBg.scaleY = height;
+			bg.scaleX = s2d.width;
+			bg.scaleY = s2d.height;
+			sceneInfo.text = "Scene size: " + s2d.width + "x" + s2d.height;
+		}
+
+		addText("Press R to set ScaleMode to Resize");
+		addChoice("ScaleMode", ScaleMode.getConstructors(), function(idx) { mode = idx; }, 0);
+		addSlider("width", function() { return width; }, function(v) { width = Std.int(v); }, 0, 800);
+		addSlider("height", function() { return height; }, function(v) { height = Std.int(v); }, 0, 600);
+		addSlider("zoom", function() { return zoom; }, function(v) { zoom = v; }, 0.01, 5);
+		addCheck("integerScale", function() { return intScale; }, function(v) { intScale = v; });
+		addChoice("HAlign", ["Left", "Center", "Right"], function(v) { halign = [Left, Center, Right][v]; }, 1 );
+		addChoice("VAlign", ["Top", "Center", "Bottom"], function(v) { valign = [Top, Center, Bottom][v]; }, 1 );
+		addButton("Apply", setMode);
+		sceneInfo = addText("");
+		addText("Light-grey: Actual Scene width and height");
+		addText("Dark-grey: Parameter-specified width and height");
+
+		setMode();
+	}
+
+	override function update(dt:Float)
+	{
+		if (Key.isReleased(Key.R)) s2d.scaleMode = Resize;
+	}
+
+	static function main() {
+		hxd.Res.initEmbed();
+		new ScaleMode2D();
+	}
+
+}