Bladeren bron

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

trethaller 6 jaren geleden
bovenliggende
commit
0e5e9de39c

+ 0 - 1
all.hxml

@@ -1,7 +1,6 @@
 -lib heaps
 -lib castle
 -lib hxbit
--lib stb_ogg_sound
 --macro include('h3d')
 --macro include('h2d',true,['h2d.domkit'])
 --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 {
 		if (shaders != null) for( s in shaders ) {
-			var s = Std.instance(s, stype);
+			var s = hxd.impl.Api.downcast(s, stype);
 			if( s != null )
 				return s;
 		}

+ 6 - 3
h2d/Flow.hx

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

+ 1 - 1
h2d/Interactive.hx

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

+ 2 - 2
h2d/KeyFrames.hx

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

+ 1 - 1
h2d/Mask.hx

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

+ 1 - 1
h2d/Object.hx

@@ -300,7 +300,7 @@ class Object {
 	public function getScene() : Scene {
 		var p = this;
 		while( p.parent != null ) p = p.parent;
-		return Std.instance(p, Scene);
+		return hxd.impl.Api.downcast(p, Scene);
 	}
 
 	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
 		baseShader.pixelAlign = #if flash true #else false #end;
 		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.filterMatrixB.set(0, 1, 0);
 		baseShaderList.next = null;
@@ -190,14 +190,33 @@ class RenderContext extends h3d.impl.RenderContext {
 
 		if( restore ) {
 			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);
 			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;
 			curY = startY;
 			curWidth = width;
@@ -213,8 +232,8 @@ class RenderContext extends h3d.impl.RenderContext {
 		renderY = y;
 		renderW = w;
 		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 ) {
 			var fa = baseShader.filterMatrixA;
 			var fb = baseShader.filterMatrixB;
@@ -228,7 +247,7 @@ class RenderContext extends h3d.impl.RenderContext {
 			w = rx2 - rx1;
 			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() {

+ 250 - 32
h2d/Scene.hx

@@ -1,21 +1,117 @@
 package h2d;
 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.
 **/
 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;
 
 	/**
-		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;
 
+	/**
+		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.
 	**/
@@ -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
 		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)
@@ -42,7 +147,6 @@ class Scene extends Layers implements h3d.IDrawable implements hxd.SceneEvents.I
 	**/
 	public var renderer(get, set) : RenderContext;
 
-	var fixedSize : Bool;
 	var interactive : Array<Interactive>;
 	var eventListeners : Array< hxd.Event -> Void >;
 	var ctx : RenderContext;
@@ -60,6 +164,12 @@ class Scene extends Layers implements h3d.IDrawable implements hxd.SceneEvents.I
 		ctx = new RenderContext(this);
 		width = e.width;
 		height = e.height;
+		offsetX = 0;
+		offsetY = 0;
+		ratioX = 1;
+		ratioY = 1;
+		viewportX = 0;
+		viewportY = 0;
 		interactive = new Array();
 		eventListeners = new Array();
 		shapePoint = new h2d.col.Point();
@@ -75,17 +185,22 @@ class Scene extends Layers implements h3d.IDrawable implements hxd.SceneEvents.I
 		this.events = events;
 	}
 
+	function get_zoom() : Int {
+		return switch ( scaleMode ) {
+			case Zoom(level): Std.int(level);
+			default: 0;
+		}
+	}
+
 	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;
@@ -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.
 	**/
+	@:deprecated("setFixedSize is deprecated, use scaleMode = Strech(w, h) instead")
 	public function setFixedSize( w : Int, h : Int ) {
-		width = w;
-		height = h;
-		fixedSize = true;
-		posChanged = true;
+		scaleMode = Stretch(w, h);
 	}
 
 	@:dox(hide) @:noCompletion
 	public function checkResize() {
-		if( fixedSize && zoom == 0 ) return;
 		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) {
-		return mx * width / (window.width * scaleX) - x;
+		return mx * width / (window.width * ratioX * scaleX) - x - viewportX;
 	}
 
 	inline function screenYToLocal(my:Float) {
-		return my * height / (window.height * scaleY) - y;
+		return my * height / (window.height * ratioY * scaleY) - y - viewportY;
 	}
 
 	function get_mouseX() {
@@ -369,7 +561,7 @@ class Scene extends Layers implements h3d.IDrawable implements hxd.SceneEvents.I
 		var f = events.getFocus();
 		if( f == null )
 			return null;
-		var i = Std.instance(f, h2d.Interactive);
+		var i = hxd.impl.Api.downcast(f, h2d.Interactive);
 		if( i == null )
 			return null;
 		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 ) {
 		if( !allocated )
 			onAdd();
-		checkResize();
 		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
 	**/
@@ -515,14 +719,28 @@ class Scene extends Layers implements h3d.IDrawable implements hxd.SceneEvents.I
 
 		var tex = target.getTexture();
 		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);
 		engine.popTarget();
 
 		width = ow;
 		height = oh;
-		fixedSize = of;
+		ratioX = orx;
+		ratioY = ory;
+		offsetX = ox;
+		offsetY = oy;
+		viewportX = ovx;
+		viewportY = ovy;
 		posChanged = true;
 		engine.setRenderZone();
 		engine.end();

+ 1 - 1
h3d/anim/Animation.hx

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

+ 1 - 1
h3d/anim/BufferAnimation.hx

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

+ 2 - 0
h3d/impl/GlDriver.hx

@@ -1383,6 +1383,8 @@ class GlDriver extends Driver {
 			gl.disable(GL.SCISSOR_TEST);
 		else {
 			gl.enable(GL.SCISSOR_TEST);
+			if( curTarget == null )
+				y = bufferHeight - (y + 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 ) {
 		var file = getFilePath(model);
 		#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) )
 			file = fs.baseDir + file;
 		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 wireframe : Bool;
 	public var colorMask : Int;
+	public var layer : Int = 0;
 
 	@: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 {
 		var s = shaders;
 		while( s != parentShaders ) {
-			var sh = Std.instance(s.s, t);
+			var sh = hxd.impl.Api.downcast(s.s, t);
 			if( sh != null )
 				return sh;
 			s = s.next;

+ 130 - 6
h3d/mat/PbrMaterial.hx

@@ -2,7 +2,6 @@ package h3d.mat;
 
 @:enum abstract PbrMode(String) {
 	var PBR = "PBR";
-	var Albedo = "Albedo";
 	var Overlay = "Overlay";
 	var Decal = "Decal";
 	var BeforeTonemapping = "BeforeTonemapping";
@@ -29,15 +28,49 @@ package h3d.mat;
 	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 = {
 	var mode : PbrMode;
 	var blend : PbrBlend;
 	var shadows : Bool;
 	var culling : Bool;
 	var depthTest : PbrDepthTest;
+	var colorMask : Int;
 	@:optional var alphaKill : Bool;
 	@:optional var emissive : 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 {
@@ -65,6 +98,8 @@ class PbrMaterial extends Material {
 				shadows : false,
 				culling : false,
 				depthTest : Less,
+				colorMask : 1 << 0 | 1 << 1 | 1 << 2 | 1 << 3,
+				enableStencil : false,
 			};
 		case "ui":
 			props = {
@@ -74,6 +109,8 @@ class PbrMaterial extends Material {
 				culling : false,
 				alphaKill : true,
 				depthTest : Less,
+				colorMask : 1 << 0 | 1 << 1 | 1 << 2 | 1 << 3,
+				enableStencil : false,
 			};
 		case "decal":
 			props = {
@@ -82,6 +119,8 @@ class PbrMaterial extends Material {
 				shadows : false,
 				culling : true,
 				depthTest : Less,
+				colorMask : 1 << 0 | 1 << 1 | 1 << 2 | 1 << 3,
+				enableStencil : false,
 			};
 		default:
 			props = {
@@ -90,6 +129,8 @@ class PbrMaterial extends Material {
 				shadows : true,
 				culling : true,
 				depthTest : Less,
+				colorMask : 1 << 0 | 1 << 1 | 1 << 2 | 1 << 3,
+				enableStencil : false,
 			};
 		}
 		return props;
@@ -102,6 +143,7 @@ class PbrMaterial extends Material {
 			case Alpha: Alpha;
 			case Add: Add;
 			case Multiply: Multiply;
+			case AlphaMultiply: AlphaMultiply;
 			default: throw "Unsupported Model blendMode "+blendMode;
 		}
 		props.depthTest = switch (mainPass.depthTest) {
@@ -118,22 +160,38 @@ class PbrMaterial extends Material {
 	}
 
 	function resetProps() {
+		var props : PbrProps = props;
 		// Remove superfluous shader
 		mainPass.removeShader(mainPass.getShader(h3d.shader.VolumeDecal));
 		mainPass.removeShader(mainPass.getShader(h3d.shader.pbr.StrengthValues));
 		mainPass.removeShader(mainPass.getShader(h3d.shader.pbr.AlphaMultiply));
 		mainPass.removeShader(mainPass.getShader(h3d.shader.Parallax));
+		// Backward compatibility
 		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() {
 		resetProps();
 		var props : PbrProps = props;
+
+		// Preset
 		switch( props.mode ) {
 		case PBR:
 			mainPass.setPassName("default");
-		case Albedo:
-			mainPass.setPassName("albedo");
 		case BeforeTonemapping:
 			mainPass.setPassName("beforeTonemapping");
 			var e = mainPass.getShader(h3d.shader.Emissive);
@@ -158,6 +216,8 @@ class PbrMaterial extends Material {
 				mainPass.addShader(sv);
 			}
 		}
+
+		// Blend modes
 		switch( props.blend ) {
 		case None:
 			mainPass.setBlendMode(None);
@@ -183,12 +243,16 @@ class PbrMaterial extends Material {
 			mainPass.setBlendMode(AlphaMultiply);
 			mainPass.depthWrite = false;
 		}
+
+		// Enable/Disable AlphaKill
 		var tshader = textureShader;
 		if( tshader != null ) {
 			tshader.killAlpha = props.alphaKill;
 			tshader.killAlphaThreshold = 0.5;
 		}
+
 		mainPass.culling = props.culling ? Back : None;
+
 		shadows = props.shadows;
 		if( shadows ) getPass("shadow").culling = mainPass.culling;
 
@@ -204,7 +268,7 @@ class PbrMaterial extends Material {
 			default: Less;
 		}
 
-		// get values from specular texture
+		// Get values from specular texture
 		var emit = props.emissive == null ? 0 : props.emissive;
 		var tex = mainPass.getShader(h3d.shader.pbr.PropsTexture);
 		var def = mainPass.getShader(h3d.shader.pbr.PropsValues);
@@ -215,7 +279,7 @@ class PbrMaterial extends Material {
 		if( tex != null ) tex.emissive = emit;
 		if( def != null ) def.emissive = emit;
 
-		// parallax
+		// Parallax
 		var ps = mainPass.getShader(h3d.shader.Parallax);
 		if( props.parallax > 0 ) {
 			if( ps == null ) {
@@ -228,6 +292,67 @@ class PbrMaterial extends Material {
 		} else if( ps != null )
 			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() {
@@ -283,7 +408,6 @@ class PbrMaterial extends Material {
 	#if editor
 	override function editProps() {
 		var props : PbrProps = props;
-		if( props.emissive == 0 ) Reflect.deleteField(props,"emissive");
 		return new js.jquery.JQuery('
 			<dl>
 				<dt>Mode</dt>

+ 1 - 1
h3d/pass/Base.hx

@@ -21,7 +21,7 @@ class Base {
 	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 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;
 
@@ -31,8 +27,6 @@ class Default extends Base {
 	public function new(name) {
 		super(name);
 		manager = new ShaderManager(getOutputs());
-		shaderIdMap = [];
-		textureIdMap = [];
 		initGlobals();
 	}
 
@@ -92,27 +86,17 @@ class Default extends Base {
 	}
 
 	@: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() )
 			return;
 		for( g in ctx.sharedGlobals )
 			globals.fastSet(g.gid, g.value);
 		setGlobals();
 		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;
 		var buf = ctx.shaderBuffers, prevShader = null;
 		for( p in passes ) {

+ 2 - 2
h3d/pass/DefaultShadowMap.hx

@@ -24,8 +24,8 @@ class DefaultShadowMap extends DirShadowMap {
 		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(shadowProjId, getShadowProj());
 		ctx.setGlobalID(shadowColorId, color);

+ 2 - 2
h3d/pass/DirShadowMap.hx

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

+ 2 - 2
h3d/pass/HardwarePick.hx

@@ -63,7 +63,7 @@ class HardwarePick extends Default {
 		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 {
 			// force all materials to use opaque blend
@@ -78,7 +78,7 @@ class HardwarePick extends Default {
 		ctx.engine.pushTarget(texOut);
 		ctx.engine.clear(0xFF000000, 1);
 		ctx.extraShaders = ctx.allocShaderList(fixedColor);
-		super.draw(passes);
+		super.draw(passes,sort);
 		ctx.extraShaders = null;
 		ctx.engine.popTarget();
 

+ 2 - 2
h3d/pass/PointShadowMap.hx

@@ -138,7 +138,7 @@ class PointShadowMap extends Shadows {
 		return tex;
 	}
 
-	override function draw( passes ) {
+	override function draw( passes, ?sort ) {
 		if( !filterPasses(passes) )
 			return;
 
@@ -177,7 +177,7 @@ class PointShadowMap extends Shadows {
 
 			var save = passes.save();
 			cullPasses(passes, function(col) return col.inFrustum(lightCamera.frustum));
-			super.draw(passes);
+			super.draw(passes,sort);
 			passes.load(save);
 			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 {
 		for( s in shaders ) {
-			var si = Std.instance(s, cl);
+			var si = hxd.impl.Api.downcast(s, cl);
 			if( si != null ) return si;
 		}
 		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;
 	}
 
-	override function draw( passes ) {
+	override function draw( passes, ?sort ) {
 		if( !filterPasses(passes) )
 			return;
 
@@ -118,7 +118,7 @@ class SpotShadowMap extends Shadows {
 
 		ctx.engine.pushTarget(texture);
 		ctx.engine.clear(0xFFFFFF, 1);
-		super.draw(passes);
+		super.draw(passes, sort);
 		if( border != null ) border.render();
 		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> ) {
 		if( out == null ) out = [];
-		var m = Std.instance(this, Mesh);
+		var m = hxd.impl.Api.downcast(this, Mesh);
 		if( m != null ) out.push(m);
 		for( c in children )
 			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.
 	**/
 	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) )
 			return;
 		if( !culled ) {
-			var m = Std.instance(this, Mesh);
+			var m = hxd.impl.Api.downcast(this, Mesh);
 			if( m != null ) callb(m);
 		}
 		for( o in children )
@@ -525,7 +525,7 @@ class Object implements hxd.impl.Serializable {
 	public function getScene() {
 		var p = this;
 		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.
 	**/
 	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.
 	**/
 	public function toMesh() : Mesh {
-		var m = Std.instance(this, Mesh);
+		var m = hxd.impl.Api.downcast(this, Mesh);
 		if( m != null )
 			return m;
 		throw this + " is not a Mesh";
@@ -567,7 +567,7 @@ class Object implements hxd.impl.Serializable {
 		for( obj in children ) {
 			var c = obj.getCollider();
 			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 ) {
 				for( c in cgrp.colliders )
 					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 ctx : RenderContext;
 	var hasSetTarget = false;
+	var frontToBack : h3d.pass.PassList -> Void;
+	var backToFront : h3d.pass.PassList -> Void;
 
 	public var effects : Array<h3d.impl.RendererFX> = [];
-	
+
 	public var renderMode : RenderMode = Default;
 
 	public function new() {
 		allPasses = [];
 		passObjects = new SMap();
 		props = getDefaultProps();
+		// pre allocate closures
+		frontToBack = depthSort.bind(true);
+		backToFront = depthSort.bind(false);
 	}
 
 	public function dispose() {
@@ -82,7 +87,7 @@ class Renderer extends hxd.impl.AnyProps {
 	}
 
 	@:access(h3d.scene.Object)
-	function depthSort( passes : h3d.pass.PassList, frontToBack = false ) {
+	function depthSort( frontToBack, passes : h3d.pass.PassList ) {
 		var cam = ctx.camera.m;
 		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;
@@ -90,9 +95,9 @@ class Renderer extends hxd.impl.AnyProps {
 			p.depth = z / w;
 		}
 		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
-			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 ) {
@@ -137,14 +142,6 @@ class Renderer extends hxd.impl.AnyProps {
 		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 ) {
 		defaultPass.draw(get(name));
 	}

+ 7 - 1
h3d/scene/World.hx

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

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

@@ -21,11 +21,11 @@ class LightSystem extends h3d.scene.LightSystem {
 	}
 
 	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) {
-		return Std.instance(ambientShader,h3d.shader.AmbientLight).additive = b;
+		return hxd.impl.Api.downcast(ambientShader,h3d.shader.AmbientLight).additive = b;
 	}
 
 	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"))];
 	}
 
-	override function draw( passes ) {
+	override function draw( passes, ?sort ) {
 		var texture = ctx.textures.allocTarget("depthMap", ctx.engine.width, ctx.engine.height, true);
 		ctx.engine.pushTarget(texture);
 		ctx.engine.clear(enableSky ? 0 : 0xFF0000, 1);
-		super.draw(passes);
+		super.draw(passes, sort);
 		ctx.engine.popTarget();
 		ctx.setGlobalID(depthMapId, { texture : texture });
 	}
@@ -40,11 +40,11 @@ class NormalPass extends h3d.pass.Default {
 		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);
 		ctx.engine.pushTarget(texture);
 		ctx.engine.clear(0x808080, 1);
-		super.draw(passes);
+		super.draw(passes, sort);
 		ctx.engine.popTarget();
 		ctx.setGlobalID(normalMapId, texture);
 	}
@@ -67,8 +67,8 @@ class Renderer extends h3d.scene.Renderer {
 	inline function get_def() return defaultPass;
 
 	// 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() {
@@ -81,8 +81,8 @@ class Renderer extends h3d.scene.Renderer {
 		if( has("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") );
 	}
 

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

@@ -4,7 +4,7 @@ package h3d.scene.pbr;
 class LightSystem extends h3d.scene.LightSystem {
 
 	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 ) {
 			shaders = ctx.allocShaderList(light.shader, shaders);
 			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 height = currentTarget == null ? ctx.engine.height : currentTarget.height;
 		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.shadows.shader != null ) lightPass.addShader(light.shadows.shader);
 				lightPass.addShader(light.shader);

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

@@ -180,15 +180,15 @@ class Renderer extends h3d.scene.Renderer {
 		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));
-		p.draw(passes);
+		p.draw(passes, sort);
 		passes.reset();
 	}
 
 	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"));
 	}
 
@@ -206,7 +206,7 @@ class Renderer extends h3d.scene.Renderer {
 		if( !shadows )
 			passes.clear();
 		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);
 			light = light.next;
 		}
@@ -222,7 +222,7 @@ class Renderer extends h3d.scene.Renderer {
 		var light = @:privateAccess ctx.lights;
 		var passes = get("shadow");
 		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 ) {
 				plight.shadows.setContext(ctx);
 				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("bloom",null);
 
-		var ls = Std.instance(getLightSystem(), LightSystem);
+		var ls = hxd.impl.Api.downcast(getLightSystem(), LightSystem);
 		var count = ctx.engine.drawCalls;
 		if( ls != null ) drawShadows(ls);
 		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]);
 		renderPass(decalsOutput, get("decal"));
 
-		setTarget(albedo);
-		draw("albedo");
-
 		if(renderMode == Default){
 			if( displayMode == Env )
 				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.
 	**/
 	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 )
 			sevents.removeScene(s2d);
 		if( new3D != null )
@@ -148,7 +148,7 @@ class App implements h3d.IDrawable {
 		                method when loading is complete
 	**/
 	@:dox(show)
-	function loadAssets( onLoaded ) {
+	function loadAssets( onLoaded : Void->Void ) {
 		onLoaded();
 	}
 
@@ -188,4 +188,4 @@ class App implements h3d.IDrawable {
 
 	static function staticHandler() {}
 
-}
+}

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

@@ -113,7 +113,7 @@ class Library extends BaseLibrary {
 			if( o.parent.isJoint )
 				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;
 			var rootJoints = [];
 			for( j in o.childs )
@@ -126,7 +126,7 @@ class Library extends BaseLibrary {
 				var m = o2.obj.toMesh();
 				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.material = skin.materials[0];
 				m.remove();
@@ -135,7 +135,7 @@ class Library extends BaseLibrary {
 			}
 			// set skin after materials
 			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();
 				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;
 		}
-		var tch = Std.instance(t, h3d.mat.TextureChannels);
+		var tch = hxd.impl.Api.downcast(t, h3d.mat.TextureChannels);
 		if( tch != null ) {
 			addInt(3);
 			var channels = @:privateAccess tch.channels;
@@ -161,7 +161,7 @@ class Serializer extends hxbit.Serializer {
 		} else
 			s = Type.createEmptyInstance(cl);
 		@: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 ) {
 			if( !canSerializeVar(v) ) continue;
 			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;
 		if( hxd.res.Loader.currentInstance == null )
 			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 )
 			throw "Can only use loader with PAK file system";
 		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() {
 		if( cachedResPath != null )
 			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 )
 			cachedResPath = lfs.baseDir;
 		else {

+ 3 - 3
hxd/inspect/ScenePanel.hx

@@ -200,15 +200,15 @@ private class CustomSceneProps extends SceneProps {
 
 	override function getObjectProps( o : h3d.scene.Object ) {
 		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;
 		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) )
 				world = 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) )
 				world = null;
 		}

+ 10 - 10
hxd/inspect/SceneProps.hx

@@ -81,7 +81,7 @@ class SceneProps {
 
 			var ls = scene.lightSystem;
 			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 )
 				props.push(PGroup("LightSystem",[
 					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(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));
-		var dl = Std.instance(l, h3d.scene.fwd.DirLight);
+		var dl = hxd.impl.Api.downcast(l, h3d.scene.fwd.DirLight);
 		if( dl != null )
 			props.push(PFloats("direction", function() {
 				var dir = dl.getDirection();
 				return [dl.x, dl.y, dl.z];
 			}, 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 )
 			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);
@@ -269,17 +269,17 @@ class SceneProps {
 		props.push(PBool("visible", function() return o.visible, function(v) o.visible = v));
 
 		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 ) {
 				for( m in multi.materials )
 					props.push(getMaterialProps(m));
 			} else
 				props.push(getMaterialProps(o.toMesh().material));
 		} else {
-			var c = Std.instance(o, h3d.scene.CustomObject);
+			var c = hxd.impl.Api.downcast(o, h3d.scene.CustomObject);
 			if( c != null )
 				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 )
 				props.push(getLightProps(l));
 		}
@@ -292,13 +292,13 @@ class SceneProps {
 			addDynamicProps(props, v);
 			return props;
 		}
-		var s = Std.instance(v, hxsl.Shader);
+		var s = hxd.impl.Api.downcast(v, hxsl.Shader);
 		if( s != null )
 			return [getShaderProps(s)];
-		var o = Std.instance(v, h3d.scene.Object);
+		var o = hxd.impl.Api.downcast(v, h3d.scene.Object);
 		if( o != null )
 			return getObjectProps(o);
-		var s = Std.instance(v, hxsl.Shader);
+		var s = hxd.impl.Api.downcast(v, hxsl.Shader);
 		if( s != null )
 			return [getShaderProps(s)];
 		return null;
@@ -366,7 +366,7 @@ class SceneProps {
 
 	function getPassProps( p : h3d.pass.Base ) {
 		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;
 
 		addDynamicProps(props, p);

+ 1 - 1
hxd/res/Loader.hx

@@ -46,7 +46,7 @@ class Loader {
 			currentInstance = old;
 			cache.set(path, res);
 		} 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;
 		}
 		return res;

+ 1 - 1
hxd/snd/ChannelBase.hx

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

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

@@ -7,7 +7,7 @@ import hxd.snd.effect.Pitch;
 class PitchDriver extends EffectDriver<Pitch> {
 
 	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 {

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

@@ -17,7 +17,7 @@ class ReverbDriver extends hxd.snd.Driver.EffectDriver<Reverb> {
 		this.driver = driver;
 		this.dryFilter = new LowPass();
 	}
-	
+
 	override function acquire() : Void {
 		// create effect
 		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.auxiliaryEffectSlotf(slot, EFX.EFFECTSLOT_GAIN, e.wetDryMix / 100.0);
 
-		@:privateAccess 
+		@:privateAccess
 		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 {
-		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);
 		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 {
-		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.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();
+	}
+
+}