2
0
Эх сурвалжийг харах

Merge remote-tracking branch 'heaps/master' into feature_float_tile

Yanrishatum 6 жил өмнө
parent
commit
832484ec37
100 өөрчлөгдсөн 2955 нэмэгдсэн , 1234 устгасан
  1. 2 3
      .travis.yml
  2. 1 1
      all.hxml
  3. 2 0
      h2d/CdbLevel.hx
  4. 1 0
      h2d/Dropdown.hx
  5. 192 69
      h2d/Flow.hx
  6. 5 3
      h2d/Interactive.hx
  7. 38 3
      h2d/Layers.hx
  8. 4 3
      h2d/Mask.hx
  9. 5 5
      h2d/Object.hx
  10. 2 0
      h2d/col/Polygon.hx
  11. 537 0
      h2d/domkit/BaseComponents.hx
  12. 32 0
      h2d/domkit/InitComponents.hx
  13. 7 0
      h2d/domkit/Object.hx
  14. 87 0
      h2d/domkit/Style.hx
  15. 1 1
      h2d/filter/AbstractMask.hx
  16. 15 2
      h3d/Buffer.hx
  17. 11 8
      h3d/Camera.hx
  18. 8 14
      h3d/col/Bounds.hx
  19. 117 0
      h3d/col/Capsule.hx
  20. 15 6
      h3d/col/Collider.hx
  21. 12 0
      h3d/col/Frustum.hx
  22. 6 0
      h3d/col/IPoint.hx
  23. 6 1
      h3d/col/ObjectCollider.hx
  24. 2 14
      h3d/col/Point.hx
  25. 12 2
      h3d/col/Polygon.hx
  26. 6 1
      h3d/col/PolygonBuffer.hx
  27. 5 0
      h3d/col/SkinCollider.hx
  28. 14 31
      h3d/col/Sphere.hx
  29. 6 5
      h3d/impl/DirectXDriver.hx
  30. 21 1
      h3d/impl/Driver.hx
  31. 12 10
      h3d/impl/GlDriver.hx
  32. 1 1
      h3d/impl/LogDriver.hx
  33. 2 2
      h3d/impl/NullDriver.hx
  34. 6 6
      h3d/impl/Stage3dDriver.hx
  35. 5 3
      h3d/impl/TextureCache.hx
  36. 1 1
      h3d/mat/BaseMaterial.hx
  37. 3 3
      h3d/mat/MaterialSetup.hx
  38. 7 4
      h3d/mat/Pass.hx
  39. 2 2
      h3d/mat/PbrMaterial.hx
  40. 4 0
      h3d/mat/PbrMaterialSetup.hx
  41. 37 3
      h3d/parts/GpuParticles.hx
  42. 1 2
      h3d/pass/Base.hx
  43. 1 1
      h3d/pass/Blur.hx
  44. 24 56
      h3d/pass/Default.hx
  45. 0 1
      h3d/pass/DefaultShadowMap.hx
  46. 9 24
      h3d/pass/DirShadowMap.hx
  47. 7 14
      h3d/pass/HardwarePick.hx
  48. 1 1
      h3d/pass/Outline.hx
  49. 143 0
      h3d/pass/PassList.hx
  50. 4 4
      h3d/pass/PassObject.hx
  51. 27 37
      h3d/pass/PointShadowMap.hx
  52. 39 15
      h3d/pass/ShaderManager.hx
  53. 50 37
      h3d/pass/Shadows.hx
  54. 6 22
      h3d/pass/SpotShadowMap.hx
  55. 5 5
      h3d/prim/Cylinder.hx
  56. 20 0
      h3d/prim/Instanced.hx
  57. 13 11
      h3d/prim/MeshPrimitive.hx
  58. 1 1
      h3d/prim/Plane2D.hx
  59. 4 1
      h3d/prim/Quads.hx
  60. 13 0
      h3d/scene/Box.hx
  61. 2 0
      h3d/scene/CameraController.hx
  62. 1 1
      h3d/scene/Light.hx
  63. 16 65
      h3d/scene/LightSystem.hx
  64. 245 0
      h3d/scene/MeshBatch.hx
  65. 10 6
      h3d/scene/Object.hx
  66. 27 18
      h3d/scene/RenderContext.hx
  67. 13 16
      h3d/scene/Renderer.hx
  68. 25 20
      h3d/scene/Scene.hx
  69. 1 1
      h3d/scene/World.hx
  70. 2 2
      h3d/scene/fwd/DirLight.hx
  71. 74 0
      h3d/scene/fwd/LightSystem.hx
  72. 1 1
      h3d/scene/fwd/PointLight.hx
  73. 4 6
      h3d/scene/fwd/Renderer.hx
  74. 24 0
      h3d/scene/pbr/Decal.hx
  75. 6 0
      h3d/scene/pbr/DirLight.hx
  76. 0 7
      h3d/scene/pbr/Light.hx
  77. 0 291
      h3d/scene/pbr/LightProbeBaker.hx
  78. 10 3
      h3d/scene/pbr/LightSystem.hx
  79. 24 1
      h3d/scene/pbr/PointLight.hx
  80. 89 24
      h3d/scene/pbr/Renderer.hx
  81. 28 2
      h3d/scene/pbr/SpotLight.hx
  82. 11 0
      h3d/scene/pbr/VolumetricLightmap.hx
  83. 15 0
      h3d/scene/pbr/terrain/Surface.hx
  84. 32 2
      h3d/scene/pbr/terrain/Terrain.hx
  85. 167 138
      h3d/scene/pbr/terrain/Tile.hx
  86. 44 0
      h3d/shader/DistanceFog.hx
  87. 6 1
      h3d/shader/SAO.hx
  88. 5 0
      h3d/shader/pbr/ComputeSH.hx
  89. 0 28
      h3d/shader/pbr/DistortionAcc.hx
  90. 112 133
      h3d/shader/pbr/Terrain.hx
  91. 24 1
      h3d/shader/pbr/ToneMapping.hx
  92. 251 0
      h3d/shader/pbr/VolumeDecal.hx
  93. 1 0
      hxd/Res.hx
  94. 8 0
      hxd/System.hl.hx
  95. 2 2
      hxd/UString.hx
  96. 4 1
      hxd/Window.hl.hx
  97. 39 14
      hxd/Window.js.hx
  98. 2 2
      hxd/inspect/ScenePanel.hx
  99. 9 7
      hxd/inspect/SceneProps.hx
  100. 1 1
      hxd/poly2tri/VisiblePolygon.hx

+ 2 - 3
.travis.yml

@@ -5,12 +5,11 @@ dist: trusty
 
 
 haxe:
 haxe:
   - development
   - development
-  - "3.4.7"
-  - "3.4.4"
+  - "4.0.0-preview.5"
 
 
 matrix:
 matrix:
   allow_failures:
   allow_failures:
-    - haxe: development
+    - haxe: "3.4.7"
 
 
 install:
 install:
   - yes | haxelib install all
   - yes | haxelib install all

+ 1 - 1
all.hxml

@@ -3,7 +3,7 @@
 -lib hxbit
 -lib hxbit
 -lib stb_ogg_sound
 -lib stb_ogg_sound
 --macro include('h3d')
 --macro include('h3d')
---macro include('h2d')
+--macro include('h2d',true,['h2d.domkit'])
 --macro include('hxsl',true,['hxsl.Macros','hxsl.CacheFile','hxsl.CacheFileBuilder'])
 --macro include('hxsl',true,['hxsl.Macros','hxsl.CacheFile','hxsl.CacheFileBuilder'])
 --macro include('hxd',true,['hxd.res.FileTree','hxd.Res','hxd.impl.BitsBuilder','hxd.impl.Air3File','hxd.fmt.pak.Build','hxd.impl.LimeStage','hxd.fs.LimeFileSystem','hxd.net.SteamHost','hxd.snd.efx','hxd.snd.openal','hxd.res.Config'])
 --macro include('hxd',true,['hxd.res.FileTree','hxd.Res','hxd.impl.BitsBuilder','hxd.impl.Air3File','hxd.fmt.pak.Build','hxd.impl.LimeStage','hxd.fs.LimeFileSystem','hxd.net.SteamHost','hxd.snd.efx','hxd.snd.openal','hxd.res.Config'])
 --no-output
 --no-output

+ 2 - 0
h2d/CdbLevel.hx

@@ -430,6 +430,8 @@ class CdbLevel extends Layers {
 	}
 	}
 
 
 	function loadLayer( ldat : LayerSpec ) : LevelLayer {
 	function loadLayer( ldat : LayerSpec ) : LevelLayer {
+		if( ldat.data == null )
+			return null;
 		var t = resolveTileset(ldat.data);
 		var t = resolveTileset(ldat.data);
 		if( t == null )
 		if( t == null )
 			return null;
 			return null;

+ 1 - 0
h2d/Dropdown.hx

@@ -53,6 +53,7 @@ class Dropdown extends Flow {
 		minHeight = maxHeight = 21;
 		minHeight = maxHeight = 21;
 		paddingLeft = 5;
 		paddingLeft = 5;
 		verticalAlign = Middle;
 		verticalAlign = Middle;
+		reverse = true;
 
 
 		tileOverItem = h2d.Tile.fromColor(0x303030, 1, 1);
 		tileOverItem = h2d.Tile.fromColor(0x303030, 1, 1);
 		tileArrow = tileArrowOpen = h2d.Tile.fromColor(0x404040, maxHeight - 2, maxHeight - 2);
 		tileArrow = tileArrowOpen = h2d.Tile.fromColor(0x404040, maxHeight - 2, maxHeight - 2);

+ 192 - 69
h2d/Flow.hx

@@ -8,6 +8,12 @@ enum FlowAlign {
 	Bottom;
 	Bottom;
 }
 }
 
 
+enum FlowLayout {
+	Horizontal;
+	Vertical;
+	Stack;
+}
+
 @:allow(h2d.Flow)
 @:allow(h2d.Flow)
 class FlowProperties {
 class FlowProperties {
 
 
@@ -150,10 +156,15 @@ class Flow extends Object {
 	public var outerHeight(get, never) : Int;
 	public var outerHeight(get, never) : Int;
 
 
 	/**
 	/**
-		By default, elements will be flowed horizontaly, then in several lines if maxWidth is reached.
-		You can instead flow them vertically, then to next column is maxHeight is reached by setting the isVertical flag to true.
+		When set to `Horizontal` (default), children are aligned horizontally from left to right (right to left using `reverse`).
+		When set to `Vertical`, children are aligned vertically from tom to bottom (bottom to top using `reverse`).
+		When set to `Stack`, children are aligned independently (`reverse` has no effect).
+		Both `Horizontal` and `Vertical` alignments can overflow to the next row/column if the available space is constrained.
 	**/
 	**/
-	public var isVertical(default, set) : Bool;
+	public var layout(default, set) : FlowLayout = Horizontal;
+
+	@:deprecated("isVertical is replaced by layout=Vertical")
+	public var isVertical(get, set) : Bool;
 
 
 	/**
 	/**
 		When isInline is set to false, the flow size will be reported based on its bounds instead of its calculated size.
 		When isInline is set to false, the flow size will be reported based on its bounds instead of its calculated size.
@@ -171,6 +182,11 @@ class Flow extends Object {
 	**/
 	**/
 	public var multiline(default,set) : Bool = false;
 	public var multiline(default,set) : Bool = false;
 
 
+	/**
+		When set to true, children are aligned in reverse order
+	**/
+	public var reverse(default,set) : Bool = false;
+
 	var background : h2d.ScaleGrid;
 	var background : h2d.ScaleGrid;
 	var debugGraphics : h2d.Graphics;
 	var debugGraphics : h2d.Graphics;
 	var properties : Array<FlowProperties> = [];
 	var properties : Array<FlowProperties> = [];
@@ -194,11 +210,20 @@ class Flow extends Object {
 		return properties[getChildIndex(e)];
 		return properties[getChildIndex(e)];
 	}
 	}
 
 
-	function set_isVertical(v) {
-		if( isVertical == v )
+	function set_layout(v) {
+		if(layout == v)
 			return v;
 			return v;
 		needReflow = true;
 		needReflow = true;
-		return isVertical = v;
+		return layout = v;
+	}
+
+	function get_isVertical() {
+		return layout == Vertical;
+	}
+
+	function set_isVertical(v) {
+		layout = v ? Vertical : Horizontal;
+		return v;
 	}
 	}
 
 
 	function set_horizontalAlign(v) {
 	function set_horizontalAlign(v) {
@@ -243,6 +268,13 @@ class Flow extends Object {
 		return multiline = v;
 		return multiline = v;
 	}
 	}
 
 
+	function set_reverse(v) {
+		if( reverse == v )
+			return v;
+		needReflow = true;
+		return reverse = v;
+	}
+
 	function set_needReflow(v) {
 	function set_needReflow(v) {
 		if( needReflow == v )
 		if( needReflow == v )
 			return v;
 			return v;
@@ -353,16 +385,19 @@ class Flow extends Object {
 		var last = properties.length - 1;
 		var last = properties.length - 1;
 		while( last >= 0 && properties[last].isAbsolute )
 		while( last >= 0 && properties[last].isAbsolute )
 			last--;
 			last--;
-		if( isVertical ) {
-			if( last >= 0 )
-				properties[last].paddingBottom += v;
-			else
-				paddingTop += v;
-		} else {
-			if( last >= 0 )
-				properties[last].paddingRight += v;
-			else
-				paddingLeft += v;
+		switch (layout) {
+			case Horizontal:
+				if( last >= 0 )
+					properties[last].paddingRight += v;
+				else
+					paddingLeft += v;
+
+			case Vertical:
+				if( last >= 0 )
+					properties[last].paddingBottom += v;
+				else
+					paddingTop += v;
+			case Stack:
 		}
 		}
 	}
 	}
 
 
@@ -371,8 +406,15 @@ class Flow extends Object {
 		if( forSize ) {
 		if( forSize ) {
 			if( !isInline )
 			if( !isInline )
 				super.getBoundsRec(relativeTo, out, true);
 				super.getBoundsRec(relativeTo, out, true);
-			if( calculatedWidth != 0 )
+			if( calculatedWidth != 0 ) {
+				if( posChanged ) {
+					calcAbsPos();
+					for( c in children )
+						c.posChanged = true;
+					posChanged = false;
+				}
 				addBounds(relativeTo, out, 0, 0, calculatedWidth, calculatedHeight);
 				addBounds(relativeTo, out, 0, 0, calculatedWidth, calculatedHeight);
+			}
 		} else
 		} else
 			super.getBoundsRec(relativeTo, out, forSize);
 			super.getBoundsRec(relativeTo, out, forSize);
 	}
 	}
@@ -412,7 +454,7 @@ class Flow extends Object {
 		var k = 0;
 		var k = 0;
 		while( numChildren>k ) {
 		while( numChildren>k ) {
 			var c = getChildAt(k);
 			var c = getChildAt(k);
-			if( c == background || c == interactive ) k++; else removeChild(c);
+			if( c == background || c == interactive || c == debugGraphics ) k++; else removeChild(c);
 		}
 		}
 	}
 	}
 
 
@@ -557,14 +599,22 @@ class Flow extends Object {
 		var maxTotWidth = realMaxWidth < 0 ? 100000000 : Math.floor(realMaxWidth);
 		var maxTotWidth = realMaxWidth < 0 ? 100000000 : Math.floor(realMaxWidth);
 		var maxTotHeight = realMaxHeight < 0 ? 100000000 : Math.floor(realMaxHeight);
 		var maxTotHeight = realMaxHeight < 0 ? 100000000 : Math.floor(realMaxHeight);
 		// inner size
 		// inner size
-		var maxWidth = maxTotWidth - (paddingLeft + paddingRight + borderWidth * 2);
-		var maxHeight = maxTotHeight - (paddingTop + paddingBottom + borderHeight * 2);
+		var maxInWidth = maxTotWidth - (paddingLeft + paddingRight + borderWidth * 2);
+		var maxInHeight = maxTotHeight - (paddingTop + paddingBottom + borderHeight * 2);
 
 
 		if( debug )
 		if( debug )
 			debugGraphics.clear();
 			debugGraphics.clear();
 
 
+		inline function childAt(i: Int) {
+			return children[ reverse ? children.length - i - 1 : i ];
+		}
+		inline function propAt(i: Int) {
+			return properties[ reverse ? children.length - i - 1 : i ];
+		}
+
 		var cw, ch;
 		var cw, ch;
-		if( !isVertical ) {
+		switch(layout) {
+		case Horizontal:
 			var halign = horizontalAlign == null ? Left : horizontalAlign;
 			var halign = horizontalAlign == null ? Left : horizontalAlign;
 			var valign = verticalAlign == null ? Bottom : verticalAlign;
 			var valign = verticalAlign == null ? Bottom : verticalAlign;
 
 
@@ -582,9 +632,9 @@ class Flow extends Object {
 				else if( overflow && minLineHeight != 0 )
 				else if( overflow && minLineHeight != 0 )
 					maxLineHeight = minLineHeight;
 					maxLineHeight = minLineHeight;
 				for( i in lastIndex...maxIndex ) {
 				for( i in lastIndex...maxIndex ) {
-					var p = properties[i];
+					var p = propAt(i);
 					if( p.isAbsolute ) continue;
 					if( p.isAbsolute ) continue;
-					var c = children[i];
+					var c = childAt(i);
 					if( !c.visible ) continue;
 					if( !c.visible ) continue;
 					var a = p.verticalAlign != null ? p.verticalAlign : valign;
 					var a = p.verticalAlign != null ? p.verticalAlign : valign;
 					c.y = y + p.offsetY + p.paddingTop;
 					c.y = y + p.offsetY + p.paddingTop;
@@ -599,15 +649,26 @@ class Flow extends Object {
 				lastIndex = maxIndex;
 				lastIndex = maxIndex;
 			}
 			}
 
 
+			inline function remSize(from: Int) {
+				var size = 0;
+				for( j in from...children.length ) {
+					var p = propAt(j);
+					if( p.isAbsolute || !childAt(j).visible ) continue;
+					if( p.isBreak ) break;
+					size += horizontalSpacing + p.calculatedWidth;
+				}
+				return size;
+			}
+
 			for( i in 0...children.length ) {
 			for( i in 0...children.length ) {
-				var p = properties[i];
+				var p = propAt(i);
 				if( p.isAbsolute ) continue;
 				if( p.isAbsolute ) continue;
-				var c = children[i];
+				var c = childAt(i);
 				if( !c.visible ) continue;
 				if( !c.visible ) continue;
 
 
 				c.constraintSize(
 				c.constraintSize(
-					isConstraintWidth && p.constraint ? (maxWidth - (p.paddingLeft + p.paddingRight)) / Math.abs(c.scaleX) : -1,
-					isConstraintHeight && p.constraint ? (maxHeight - (p.paddingTop + p.paddingBottom)) / Math.abs(c.scaleX) : -1
+					isConstraintWidth && p.constraint ? maxInWidth / Math.abs(c.scaleX) : -1,
+					isConstraintHeight && p.constraint ? maxInHeight / Math.abs(c.scaleX) : -1
 				);
 				);
 
 
 				var b = c.getSize(tmpBounds);
 				var b = c.getSize(tmpBounds);
@@ -616,7 +677,7 @@ class Flow extends Object {
 				p.calculatedHeight = Math.ceil(b.yMax) + p.paddingTop + p.paddingBottom;
 				p.calculatedHeight = Math.ceil(b.yMax) + p.paddingTop + p.paddingBottom;
 				if( p.minWidth != null && p.calculatedWidth < p.minWidth ) p.calculatedWidth = p.minWidth;
 				if( p.minWidth != null && p.calculatedWidth < p.minWidth ) p.calculatedWidth = p.minWidth;
 				if( p.minHeight != null && p.calculatedHeight < p.minHeight ) p.calculatedHeight = p.minHeight;
 				if( p.minHeight != null && p.calculatedHeight < p.minHeight ) p.calculatedHeight = p.minHeight;
-				if( multiline && x + p.calculatedWidth > maxWidth && x > startX ) {
+				if( multiline && x - startX + p.calculatedWidth > maxInWidth && x - startX > 0 ) {
 					br = true;
 					br = true;
 					alignLine(i);
 					alignLine(i);
 					y += maxLineHeight + verticalSpacing;
 					y += maxLineHeight + verticalSpacing;
@@ -639,8 +700,8 @@ class Flow extends Object {
 			var xmin = startX, xmax = endX;
 			var xmin = startX, xmax = endX;
 			var midSpace = 0;
 			var midSpace = 0;
 			for( i in 0...children.length ) {
 			for( i in 0...children.length ) {
-				var p = properties[i];
-				if( p.isAbsolute || !children[i].visible ) continue;
+				var p = propAt(i);
+				if( p.isAbsolute || !childAt(i).visible ) continue;
 				if( p.isBreak ) {
 				if( p.isBreak ) {
 					xmin = startX;
 					xmin = startX;
 					xmax = endX;
 					xmax = endX;
@@ -650,22 +711,16 @@ class Flow extends Object {
 				var align = p.horizontalAlign == null ? halign : p.horizontalAlign;
 				var align = p.horizontalAlign == null ? halign : p.horizontalAlign;
 				switch( align ) {
 				switch( align ) {
 				case Right:
 				case Right:
-					if( midSpace != 0 ) {
+					if( midSpace == 0 ) {
+						var remSize = p.calculatedWidth + remSize(i + 1);
+						midSpace = (xmax - xmin) - remSize;
 						xmin += midSpace;
 						xmin += midSpace;
-						midSpace = 0;
 					}
 					}
-					xmax -= p.calculatedWidth;
-					px = xmax;
-					xmax -= horizontalSpacing;
+					px = xmin;
+					xmin += p.calculatedWidth + horizontalSpacing;
 				case Middle:
 				case Middle:
 					if( midSpace == 0 ) {
 					if( midSpace == 0 ) {
-						var remSize = p.calculatedWidth;
-						for( j in i + 1...children.length ) {
-							var p = properties[j];
-							if( p.isAbsolute || !children[j].visible ) continue;
-							if( p.isBreak ) break;
-							remSize += horizontalSpacing + p.calculatedWidth;
-						}
+						var remSize = p.calculatedWidth + remSize(i + 1);
 						midSpace = Std.int(((xmax - xmin) - remSize) * 0.5);
 						midSpace = Std.int(((xmax - xmin) - remSize) * 0.5);
 						xmin += midSpace;
 						xmin += midSpace;
 					}
 					}
@@ -679,11 +734,10 @@ class Flow extends Object {
 					px = xmin;
 					px = xmin;
 					xmin += p.calculatedWidth + horizontalSpacing;
 					xmin += p.calculatedWidth + horizontalSpacing;
 				}
 				}
-				children[i].x = px + p.offsetX + p.paddingLeft;
+				childAt(i).x = px + p.offsetX + p.paddingLeft;
 			}
 			}
 
 
-		} else {
-
+		case Vertical:
 			var halign = horizontalAlign == null ? Left : horizontalAlign;
 			var halign = horizontalAlign == null ? Left : horizontalAlign;
 			var valign = verticalAlign == null ? Top : verticalAlign;
 			var valign = verticalAlign == null ? Top : verticalAlign;
 
 
@@ -701,9 +755,9 @@ class Flow extends Object {
 				else if( overflow && minColWidth != 0 )
 				else if( overflow && minColWidth != 0 )
 					maxColWidth = minColWidth;
 					maxColWidth = minColWidth;
 				for( i in lastIndex...maxIndex ) {
 				for( i in lastIndex...maxIndex ) {
-					var p = properties[i];
+					var p = propAt(i);
 					if( p.isAbsolute ) continue;
 					if( p.isAbsolute ) continue;
-					var c = children[i];
+					var c = childAt(i);
 					if( !c.visible ) continue;
 					if( !c.visible ) continue;
 					var a = p.horizontalAlign != null ? p.horizontalAlign : halign;
 					var a = p.horizontalAlign != null ? p.horizontalAlign : halign;
 					c.x = x + p.offsetX + p.paddingLeft;
 					c.x = x + p.offsetX + p.paddingLeft;
@@ -718,16 +772,27 @@ class Flow extends Object {
 				lastIndex = maxIndex;
 				lastIndex = maxIndex;
 			}
 			}
 
 
+			inline function remSize(from: Int) {
+				var size = 0;
+				for( j in from...children.length ) {
+					var p = propAt(j);
+					if( p.isAbsolute || !childAt(j).visible ) continue;
+					if( p.isBreak ) break;
+					size += verticalSpacing + p.calculatedHeight;
+				}
+				return size;
+			}
+
 			for( i in 0...children.length ) {
 			for( i in 0...children.length ) {
-				var p = properties[i];
+				var p = propAt(i);
 				if( p.isAbsolute ) continue;
 				if( p.isAbsolute ) continue;
 
 
-				var c = children[i];
+				var c = childAt(i);
 				if( !c.visible ) continue;
 				if( !c.visible ) continue;
 
 
 				c.constraintSize(
 				c.constraintSize(
-					isConstraintWidth && p.constraint ? (maxWidth - (p.paddingLeft + p.paddingRight)) / Math.abs(c.scaleX) : -1,
-					isConstraintHeight && p.constraint ? (maxHeight - (p.paddingTop + p.paddingBottom)) / Math.abs(c.scaleY) : -1
+					isConstraintWidth && p.constraint ? maxInWidth / Math.abs(c.scaleX) : -1,
+					isConstraintHeight && p.constraint ? maxInHeight / Math.abs(c.scaleY) : -1
 				);
 				);
 
 
 				var b = c.getSize(tmpBounds);
 				var b = c.getSize(tmpBounds);
@@ -738,7 +803,7 @@ class Flow extends Object {
 				if( p.minWidth != null && p.calculatedWidth < p.minWidth ) p.calculatedWidth = p.minWidth;
 				if( p.minWidth != null && p.calculatedWidth < p.minWidth ) p.calculatedWidth = p.minWidth;
 				if( p.minHeight != null && p.calculatedHeight < p.minHeight ) p.calculatedHeight = p.minHeight;
 				if( p.minHeight != null && p.calculatedHeight < p.minHeight ) p.calculatedHeight = p.minHeight;
 
 
-				if( multiline && y + p.calculatedHeight > maxHeight && y > startY ) {
+				if( multiline && y - startY + p.calculatedHeight > maxInHeight && y - startY > 0 ) {
 					br = true;
 					br = true;
 					alignLine(i);
 					alignLine(i);
 					x += maxColWidth + horizontalSpacing;
 					x += maxColWidth + horizontalSpacing;
@@ -763,8 +828,8 @@ class Flow extends Object {
 			var ymin = startY, ymax = endY;
 			var ymin = startY, ymax = endY;
 			var midSpace = 0;
 			var midSpace = 0;
 			for( i in 0...children.length ) {
 			for( i in 0...children.length ) {
-				var p = properties[i];
-				if( p.isAbsolute || !children[i].visible ) continue;
+				var p = propAt(i);
+				if( p.isAbsolute || !childAt(i).visible ) continue;
 				if( p.isBreak ) {
 				if( p.isBreak ) {
 					ymin = startY;
 					ymin = startY;
 					ymax = endY;
 					ymax = endY;
@@ -774,22 +839,16 @@ class Flow extends Object {
 				var align = p.verticalAlign == null ? valign : p.verticalAlign;
 				var align = p.verticalAlign == null ? valign : p.verticalAlign;
 				switch( align ) {
 				switch( align ) {
 				case Bottom:
 				case Bottom:
-					if( midSpace != 0 ) {
+					if( midSpace == 0 ) {
+						var remSize = p.calculatedHeight + remSize(i + 1);
+						midSpace = (ymax - ymin) - remSize;
 						ymin += midSpace;
 						ymin += midSpace;
-						midSpace = 0;
 					}
 					}
-					ymax -= p.calculatedHeight;
-					py = ymax;
-					ymax -= verticalSpacing;
+					py = ymin;
+					ymin += p.calculatedHeight + verticalSpacing;
 				case Middle:
 				case Middle:
 					if( midSpace == 0 ) {
 					if( midSpace == 0 ) {
-						var remSize = p.calculatedHeight;
-						for( j in i + 1...children.length ) {
-							var p = properties[j];
-							if( p.isAbsolute || !children[j].visible ) continue;
-							if( p.isBreak ) break;
-							remSize += verticalSpacing + p.calculatedHeight;
-						}
+						var remSize = p.calculatedHeight + remSize(i + 1);
 						midSpace = Std.int(((ymax - ymin) - remSize) * 0.5);
 						midSpace = Std.int(((ymax - ymin) - remSize) * 0.5);
 						ymin += midSpace;
 						ymin += midSpace;
 					}
 					}
@@ -803,7 +862,71 @@ class Flow extends Object {
 					py = ymin;
 					py = ymin;
 					ymin += p.calculatedHeight + verticalSpacing;
 					ymin += p.calculatedHeight + verticalSpacing;
 				}
 				}
-				children[i].y = py + p.offsetY + p.paddingTop;
+				childAt(i).y = py + p.offsetY + p.paddingTop;
+			}
+		case Stack:
+			var halign = horizontalAlign == null ? Left : horizontalAlign;
+			var valign = verticalAlign == null ? Top : verticalAlign;
+
+			var maxChildW = minWidth;
+			var maxChildH = minHeight;
+
+			for( i in 0...children.length ) {
+				var c = childAt(i);
+				if( !c.visible ) continue;
+				var p = propAt(i);
+				if( p.isAbsolute ) continue;
+
+				c.constraintSize(
+					isConstraintWidth && p.constraint ? maxInWidth / Math.abs(c.scaleX) : -1,
+					isConstraintHeight && p.constraint ? maxInHeight / Math.abs(c.scaleY) : -1
+				);
+
+				var b = c.getSize(tmpBounds);
+				p.calculatedWidth = Math.ceil(b.xMax) + p.paddingLeft + p.paddingRight;
+				p.calculatedHeight = Math.ceil(b.yMax) + p.paddingTop + p.paddingBottom;
+				if( p.minWidth != null && p.calculatedWidth < p.minWidth ) p.calculatedWidth = p.minWidth;
+				if( p.minHeight != null && p.calculatedHeight < p.minHeight ) p.calculatedHeight = p.minHeight;
+				if( p.calculatedWidth > maxChildW ) maxChildW = p.calculatedWidth;
+				if( p.calculatedHeight > maxChildH ) maxChildH = p.calculatedHeight;
+			}
+
+			var xmin = paddingLeft + borderWidth;
+			var xmax = xmin + maxChildW;
+			var ymin = paddingTop + borderWidth;
+			var ymax = ymin + maxChildH;
+			cw = xmax + paddingRight + borderWidth;
+			ch = ymax + paddingBottom + borderWidth;
+
+			for( i in 0...children.length ) {
+				var c = childAt(i);
+				if( !c.visible ) continue;
+				var p = propAt(i);
+				if( p.isAbsolute ) continue;
+
+				var valign = p.verticalAlign == null ? valign : p.verticalAlign;
+				var halign = p.horizontalAlign == null ? halign : p.horizontalAlign;
+
+				var px = switch( halign ) {
+				case Right:
+					xmax - p.calculatedWidth;
+				case Middle:
+					xmin + ((xmax - xmin) - p.calculatedWidth) * 0.5;
+				default:
+					xmin;
+				}
+
+				var py = switch( valign ) {
+				case Bottom:
+					ymax - p.calculatedHeight;
+				case Middle:
+					ymin + ((ymax - ymin) - p.calculatedHeight) * 0.5;
+				default:
+					ymin;
+				}
+
+				c.x = px + p.offsetX + p.paddingLeft;
+				c.y = py + p.offsetY + p.paddingTop;
 			}
 			}
 		}
 		}
 
 
@@ -839,8 +962,8 @@ class Flow extends Object {
 			}
 			}
 			debugGraphics.lineStyle(1, 0x0080FF);
 			debugGraphics.lineStyle(1, 0x0080FF);
 			for( i in 0...children.length ) {
 			for( i in 0...children.length ) {
-				var p = properties[i];
-				var c = children[i];
+				var p = propAt(i);
+				var c = childAt(i);
 				if( p.isAbsolute || !c.visible ) continue;
 				if( p.isAbsolute || !c.visible ) continue;
 				debugGraphics.drawRect(c.x, c.y, p.calculatedWidth, p.calculatedHeight);
 				debugGraphics.drawRect(c.x, c.y, p.calculatedWidth, p.calculatedHeight);
 			}
 			}

+ 5 - 3
h2d/Interactive.hx

@@ -72,13 +72,15 @@ class Interactive extends Drawable implements hxd.SceneEvents.Interactive {
 		if( backgroundColor != null || forSize ) addBounds(relativeTo, out, 0, 0, Std.int(width), Std.int(height));
 		if( backgroundColor != null || forSize ) addBounds(relativeTo, out, 0, 0, Std.int(width), Std.int(height));
 	}
 	}
 
 
-	override function onParentChanged() {
-		super.onParentChanged();
+	override private function onHierarchyMoved(parentChanged:Bool)
+	{
+		super.onHierarchyMoved(parentChanged);
 		if( scene != null ) {
 		if( scene != null ) {
 			scene.removeEventTarget(this);
 			scene.removeEventTarget(this);
 			scene.addEventTarget(this);
 			scene.addEventTarget(this);
 		}
 		}
-		updateMask();
+		if ( parentChanged )
+			updateMask();
 	}
 	}
 
 
 	function updateMask() {
 	function updateMask() {

+ 38 - 3
h2d/Layers.hx

@@ -54,6 +54,9 @@ class Layers extends Object {
 		}
 		}
 	}
 	}
 
 
+	/**
+		Moves Object to the bottom of its layer (rendered first, behind the other Objects in layer).
+	**/
 	public function under( s : Object ) {
 	public function under( s : Object ) {
 		for( i in 0...children.length )
 		for( i in 0...children.length )
 			if( children[i] == s ) {
 			if( children[i] == s ) {
@@ -69,10 +72,16 @@ class Layers extends Object {
 					p--;
 					p--;
 				}
 				}
 				children[pos] = s;
 				children[pos] = s;
-				break;
+				// Force Interactive to reattach to scene in order to keep interaction order.
+				if ( s.allocated )
+					s.onHierarchyMoved(false);
+				return;
 			}
 			}
 	}
 	}
 
 
+	/**
+		Moves Object to the top of its layer (rendered last, in front of other Objects in layer).
+	**/
 	public function over( s : Object ) {
 	public function over( s : Object ) {
 		for( i in 0...children.length )
 		for( i in 0...children.length )
 			if( children[i] == s ) {
 			if( children[i] == s ) {
@@ -81,12 +90,20 @@ class Layers extends Object {
 						for( p in i...l-1 )
 						for( p in i...l-1 )
 							children[p] = children[p + 1];
 							children[p] = children[p + 1];
 						children[l - 1] = s;
 						children[l - 1] = s;
-						break;
+						// Force Interactive to reattach to scene in order to keep interaction order.
+						if ( s.allocated )
+							s.onHierarchyMoved(false);
+						return;
 					}
 					}
-				break;
+				return;
 			}
 			}
 	}
 	}
 
 
+	/**
+		Returns Iterator of objects contained in specified layer.  
+		Returns empty iterator if layer does not exists.  
+		Objects added or removed from Layers during iteration are not added/removed from the Iterator.
+	**/
 	public function getLayer( layer : Int ) : Iterator<Object> {
 	public function getLayer( layer : Int ) : Iterator<Object> {
 		var a;
 		var a;
 		if( layer >= layerCount )
 		if( layer >= layerCount )
@@ -99,6 +116,19 @@ class Layers extends Object {
 		return new hxd.impl.ArrayIterator(a);
 		return new hxd.impl.ArrayIterator(a);
 	}
 	}
 
 
+	/**
+		Finds the layer on which child object resides.  
+		Always returns -1 if provided Object is not a child of Layers.
+	**/
+	public function getChildLayer( s : Object ) : Int {
+		if ( s.parent != this ) return -1;
+
+		var index = children.indexOf(s);
+		for ( i in 0...layerCount )
+			if ( layersIndexes[i] > index ) return i;
+		return -1;
+	}
+
 	function drawLayer( ctx : RenderContext, layer : Int ) {
 	function drawLayer( ctx : RenderContext, layer : Int ) {
 		if( layer >= layerCount )
 		if( layer >= layerCount )
 			return;
 			return;
@@ -114,6 +144,9 @@ class Layers extends Object {
 		ctx.globalAlpha = old;
 		ctx.globalAlpha = old;
 	}
 	}
 
 
+	/**
+		Sorts specified layer based on Y value of it's Objects.
+	**/
 	public function ysort( layer : Int ) {
 	public function ysort( layer : Int ) {
 		if( layer >= layerCount ) return;
 		if( layer >= layerCount ) return;
 		var start = layer == 0 ? 0 : layersIndexes[layer - 1];
 		var start = layer == 0 ? 0 : layersIndexes[layer - 1];
@@ -133,6 +166,8 @@ class Layers extends Object {
 					p--;
 					p--;
 				}
 				}
 				children[p + 1] = c;
 				children[p + 1] = c;
+				if ( c.allocated )
+					c.onHierarchyMoved(false);
 			} else
 			} else
 				ymax = c.y;
 				ymax = c.y;
 			pos++;
 			pos++;

+ 4 - 3
h2d/Mask.hx

@@ -12,9 +12,10 @@ class Mask extends Object {
 		this.height = height;
 		this.height = height;
 	}
 	}
 
 
-	override function onParentChanged() {
-		super.onParentChanged();
-		updateMask();
+	override private function onHierarchyMoved(parentChanged:Bool) {
+		super.onHierarchyMoved(parentChanged);
+		if ( parentChanged )
+			updateMask();
 	}
 	}
 
 
 	override function onAdd() {
 	override function onAdd() {

+ 5 - 5
h2d/Object.hx

@@ -347,7 +347,7 @@ class Object {
 			if( !s.allocated )
 			if( !s.allocated )
 				s.onAdd();
 				s.onAdd();
 			else
 			else
-				s.onParentChanged();
+				s.onHierarchyMoved(true);
 		}
 		}
 		onContentChanged();
 		onContentChanged();
 	}
 	}
@@ -357,9 +357,9 @@ class Object {
 	}
 	}
 
 
 	// called when we're allocated already but moved in hierarchy
 	// called when we're allocated already but moved in hierarchy
-	function onParentChanged() {
-		for( c in children )
-			c.onParentChanged();
+	function onHierarchyMoved( parentChanged : Bool ) {
+		for ( c in children )
+			c.onHierarchyMoved(parentChanged);
 	}
 	}
 
 
 	// kept for internal init
 	// kept for internal init
@@ -418,7 +418,7 @@ class Object {
 
 
 	/**
 	/**
 		Same as parent.removeChild(this), but does nothing if parent is null.
 		Same as parent.removeChild(this), but does nothing if parent is null.
-		In order to capture add/removal from scene, you can override onAdd/onRemove/onParentChanged
+		In order to capture add/removal from scene, you can override onAdd/onRemove/onHierarchyMoved
 	**/
 	**/
 	public inline function remove() {
 	public inline function remove() {
 		if( this != null && parent != null ) parent.removeChild(this);
 		if( this != null && parent != null ) parent.removeChild(this);

+ 2 - 0
h2d/col/Polygon.hx

@@ -135,6 +135,8 @@ abstract Polygon(Array<Point>) from Array<Point> to Array<Point> {
 	}
 	}
 
 
 	public function isConvex() {
 	public function isConvex() {
+		if(points.length < 3) return true;
+
 		var p1 = points[points.length - 2];
 		var p1 = points[points.length - 2];
 		var p2 = points[points.length - 1];
 		var p2 = points[points.length - 1];
 		var p3 = points[0];
 		var p3 = points[0];

+ 537 - 0
h2d/domkit/BaseComponents.hx

@@ -0,0 +1,537 @@
+package h2d.domkit;
+import domkit.Property;
+import domkit.CssValue;
+
+class CustomParser extends CssValue.ValueParser {
+
+	function parseScale( value ) {
+		switch( value ) {
+		case VGroup([x,y]):
+			return { x : parseFloat(x), y : parseFloat(y) };
+		default:
+			var s = parseFloat(value);
+			return { x : s, y : s };
+		}
+	}
+
+	function parseDimension( value ) {
+		switch( value ) {
+		case VGroup([x,y]):
+			return { x : parseFloat(x), y : parseFloat(y) };
+		case VIdent("none"):
+			return null;
+		default:
+			var s = parseFloat(value);
+			return { x : s, y : s };
+		}
+	}
+
+	function parsePosition( value ) {
+		switch( value ) {
+		case VIdent("auto"):
+			return false;
+		case VIdent("absolute"):
+			return true;
+		default:
+			return invalidProp();
+		}
+	}
+
+
+	function parseColorF( v : CssValue ) : h3d.Vector {
+		var c = parseColor(v);
+		var v = new h3d.Vector();
+		v.setColor(c);
+		return v;
+	}
+
+	function loadResource( path : String ) {
+		#if macro
+		// TODO : compile-time path check?
+		return true;
+		#else
+		return try hxd.res.Loader.currentInstance.load(path) catch( e : hxd.res.NotFound ) invalidProp("Resource not found "+path);
+		#end
+	}
+
+	public function parseTile( v : CssValue) {
+		try {
+			switch( v ) {
+			case VIdent("none"):
+				return null;
+			case VGroup([color,w,h]):
+				var c = parseColor(color);
+				var w = parseInt(w);
+				var h = parseInt(h);
+				return #if macro true #else h2d.Tile.fromColor(c,w,h,(c>>>24)/255) #end;
+			default:
+				var c = parseColor(v);
+				return #if macro true #else h2d.Tile.fromColor(c,1,1,(c>>>24)/255) #end;
+			}
+		} catch( e : InvalidProperty ) {
+			var path = parsePath(v);
+			var p = loadResource(path);
+			return #if macro p #else p.toTile() #end;
+		}
+	}
+
+	public function parseHAlign( value ) : #if macro Bool #else h2d.Flow.FlowAlign #end {
+		switch( parseIdent(value) ) {
+		case "auto":
+			return null;
+		case "middle":
+			return #if macro true #else Middle #end;
+		case "left":
+			return #if macro true #else Left #end;
+		case "right":
+			return #if macro true #else Right #end;
+		case x:
+			return invalidProp(x+" should be auto|left|middle|right");
+		}
+	}
+
+	public function parseVAlign( value ) : #if macro Bool #else h2d.Flow.FlowAlign #end {
+		switch( parseIdent(value) ) {
+		case "auto":
+			return null;
+		case "middle":
+			return #if macro true #else Middle #end;
+		case "top":
+			return #if macro true #else Top #end;
+		case "bottom":
+			return #if macro true #else Bottom #end;
+		case x:
+			return invalidProp(x+" should be auto|top|middle|bottom");
+		}
+	}
+
+	public function parseAlign( value : CssValue ) {
+		switch( value ) {
+		case VIdent("auto"):
+			return { h : null, v : null };
+		case VIdent(_):
+			try {
+				return { h : parseHAlign(value), v : null };
+			} catch( e : InvalidProperty ) {
+				return { h : null, v : parseVAlign(value) };
+			}
+		case VGroup([h,v]):
+			try {
+				return { h : parseHAlign(h), v : parseVAlign(v) };
+			} catch( e : InvalidProperty ) {
+				return { h : parseHAlign(v), v : parseVAlign(h) };
+			}
+		default:
+			return invalidProp();
+		}
+	}
+
+	public function parseFont( value : CssValue ) {
+		var path = null;
+		var sdf = null;
+		switch(value) {
+			case VGroup(args):
+				path = parsePath(args[0]);
+				sdf = {
+					size: parseInt(args[1]),
+					channel: args.length >= 3 ? switch(args[2]) {
+						case VIdent("red"): h2d.Font.SDFChannel.Red;
+						case VIdent("green"): h2d.Font.SDFChannel.Green;
+						case VIdent("blue"): h2d.Font.SDFChannel.Blue;
+						case VIdent("multi"): h2d.Font.SDFChannel.MultiChannel;
+						default: h2d.Font.SDFChannel.Alpha;
+					} : h2d.Font.SDFChannel.Alpha,
+					cutoff: args.length >= 4 ? parseFloat(args[3]) : 0.5,
+					smooth: args.length >= 5 ? parseFloat(args[4]) : 1.0/32.0
+				};
+			default:
+				path = parsePath(value);
+		}
+		var res = loadResource(path);
+		#if macro
+		return res;
+		#else
+		if(sdf != null)
+			return res.to(hxd.res.BitmapFont).toSdfFont(sdf.size, sdf.channel, sdf.cutoff, sdf.smooth);
+		else
+			return res.to(hxd.res.BitmapFont).toFont();
+		#end
+	}
+
+	public function parseTextShadow( value : CssValue ) {
+		return switch( value ) {
+		case VGroup(vl):
+			return { dx : parseFloat(vl[0]), dy : parseFloat(vl[1]), color : vl.length >= 3 ? parseColor(vl[2]) : 0, alpha : vl.length >= 4 ? parseFloat(vl[3]) : 1 };
+		default:
+			return { dx : 1, dy : 1, color : parseColor(value), alpha : 1 };
+		}
+	}
+
+	public function parseFlowBackground(value) {
+		return switch( value ) {
+		case VIdent("transparent"): null;
+		case VGroup([tile,VInt(x),VInt(y)]):
+			{ tile : parseTile(tile), borderW : x, borderH : y };
+		default:
+			{ tile : parseTile(value), borderW : 0, borderH : 0 };
+		}
+	}
+
+}
+
+#if !macro
+@:uiComp("object") @:parser(h2d.domkit.BaseComponents.CustomParser)
+class ObjectComp implements h2d.domkit.Object implements domkit.Component.ComponentDecl<h2d.Object> {
+
+	@:p var x : Float;
+	@:p var y : Float;
+	@:p var alpha : Float = 1;
+	@:p var rotation : Float;
+	@:p var visible : Bool;
+	@:p(scale) var scale : { x : Float, y : Float };
+	@:p var scaleX : Float;
+	@:p var scaleY : Float;
+	@:p var blend : h2d.BlendMode = Alpha;
+
+	// flow properties
+	@:p(box) var margin : { left : Int, top : Int, right : Int, bottom : Int };
+	@:p var marginLeft = 0;
+	@:p var marginRight = 0;
+	@:p var marginTop = 0;
+	@:p var marginBottom = 0;
+	@:p(align) var align : { v : h2d.Flow.FlowAlign, h : h2d.Flow.FlowAlign };
+	@:p(hAlign) var halign : h2d.Flow.FlowAlign;
+	@:p(vAlign) var valign : h2d.Flow.FlowAlign;
+	@:p(position) var position : Bool;
+	@:p(XY) var offset : { x : Float, y : Float };
+	@:p var offsetX : Int;
+	@:p var offsetY : Int;
+	@:p(none) var minWidth : Null<Int>;
+	@:p(none) var minHeight : Null<Int>;
+
+	static function set_rotation(o:h2d.Object, v:Float) {
+		o.rotation = v * Math.PI / 180;
+	}
+
+	static function set_scale(o:h2d.Object,v) {
+		if(v != null) {
+			o.scaleX = v.x;
+			o.scaleY = v.y;
+		}
+		else {
+			o.setScale(1);
+		}
+	}
+
+	static function set_blend(o:h2d.Object, b:h2d.BlendMode) {
+		o.blendMode = b;
+	}
+
+	static function getFlowProps( o : h2d.Object ) {
+		var p = Std.instance(o.parent, h2d.Flow);
+		return p == null ? null : p.getProperties(o);
+	}
+
+	static function set_margin(o:h2d.Object,v) {
+		var p = getFlowProps(o);
+		if( p != null ) {
+			if( v == null )
+				p.paddingLeft = p.paddingRight = p.paddingTop = p.paddingBottom = 0;
+			else {
+				p.paddingLeft = v.left;
+				p.paddingRight = v.right;
+				p.paddingTop = v.top;
+				p.paddingBottom = v.bottom;
+			}
+		}
+	}
+
+	static function set_marginLeft(o:h2d.Object,v) {
+		var p = getFlowProps(o);
+		if( p != null ) p.paddingLeft = v;
+	}
+
+	static function set_marginRight(o:h2d.Object,v) {
+		var p = getFlowProps(o);
+		if( p != null ) p.paddingRight = v;
+	}
+
+	static function set_marginTop(o:h2d.Object,v) {
+		var p = getFlowProps(o);
+		if( p != null ) p.paddingTop = v;
+	}
+
+	static function set_marginBottom(o:h2d.Object,v) {
+		var p = getFlowProps(o);
+		if( p != null ) p.paddingBottom = v;
+	}
+
+	static function set_align(o:h2d.Object,v) {
+		var p = getFlowProps(o);
+		if( p != null ) {
+			p.horizontalAlign = v == null ? null : v.h;
+			p.verticalAlign = v == null ? null : v.v;
+		}
+	}
+
+	static function set_halign(o:h2d.Object,v) {
+		var p = getFlowProps(o);
+		if( p != null ) p.horizontalAlign = v;
+	}
+
+	static function set_valign(o:h2d.Object,v) {
+		var p = getFlowProps(o);
+		if( p != null ) p.verticalAlign = v;
+	}
+
+	static function set_position(o:h2d.Object,v) {
+		var p = getFlowProps(o);
+		if( p != null ) p.isAbsolute = v;
+	}
+
+	static function set_offset(o:h2d.Object,v) {
+		var p = getFlowProps(o);
+		if( p != null ) {
+			p.offsetX = v == null ? 0 : Std.int(v.x);
+			p.offsetX = v == null ? 0 : Std.int(v.y);
+		}
+	}
+
+	static function set_offsetX(o:h2d.Object,v) {
+		var p = getFlowProps(o);
+		if( p != null ) p.offsetX = v;
+	}
+
+	static function set_offsetY(o:h2d.Object,v) {
+		var p = getFlowProps(o);
+		if( p != null ) p.offsetY = v;
+	}
+
+	static function set_minWidth(o:h2d.Object,v) {
+		var p = getFlowProps(o);
+		if( p != null ) p.minWidth = v;
+	}
+
+	static function set_minHeight(o:h2d.Object,v) {
+		var p = getFlowProps(o);
+		if( p != null ) p.minHeight = v;
+	}
+
+	@:keep static var init = {
+		domkit.Element.addElement = function(e, parent) (parent.obj : h2d.Object).addChild(e.obj);
+		domkit.Element.removeElement = function(e) (e.obj : h2d.Object).remove();
+		domkit.Element.getParent = function(e:h2d.Object) return e.parent;
+		true;
+	}
+
+}
+
+@:uiComp("drawable")
+class DrawableComp extends ObjectComp implements domkit.Component.ComponentDecl<h2d.Drawable> {
+
+	@:p(colorF) var color : h3d.Vector;
+	@:p(auto) var smooth : Null<Bool>;
+	@:p var tileWrap : Bool;
+
+	static function set_color( o : h2d.Drawable, v ) {
+		if(v != null)
+			o.color.load(v);
+		else
+			o.color.set(1,1,1);
+	}
+}
+
+@:uiComp("mask")
+class MaskComp extends ObjectComp implements domkit.Component.ComponentDecl<h2d.Mask> {
+	@:p var width : Int;
+	@:p var height : Int;
+
+	static function create( parent : h2d.Object ) {
+		return new h2d.Mask(0,0,parent);
+	}
+}
+
+@:uiComp("bitmap")
+class BitmapComp extends DrawableComp implements domkit.Component.ComponentDecl<h2d.Bitmap> {
+
+	@:p(tile) var src : h2d.Tile;
+
+	static function create( parent : h2d.Object ) {
+		return new h2d.Bitmap(h2d.Tile.fromColor(0xFF00FF,32,32,0.9),parent);
+	}
+
+	static function set_src( o : h2d.Bitmap, t ) {
+		o.tile = t == null ? h2d.Tile.fromColor(0xFF00FF,32,32,0.9) : t;
+	}
+
+}
+
+@:uiComp("text")
+class TextComp extends DrawableComp implements domkit.Component.ComponentDecl<h2d.Text> {
+
+	@:p var text : String = "";
+	@:p(font) var font : h2d.Font;
+	@:p var letterSpacing = 1;
+	@:p var lineSpacing = 0;
+	@:p(none) var maxWidth : Null<Int>;
+	@:p var textAlign : h2d.Text.Align = Left;
+	@:p(textShadow) var textShadow : { dx : Float, dy : Float, color : Int, alpha : Float };
+	@:p(color) var textColor: Int;
+
+	static function create( parent : h2d.Object ) {
+		return new h2d.Text(hxd.res.DefaultFont.get(),parent);
+	}
+
+	static function set_font( t : h2d.Text, v ) {
+		t.font = v == null ? hxd.res.DefaultFont.get() : v;
+	}
+
+	static function set_textShadow( t : h2d.Text, v ) {
+		t.dropShadow = v;
+	}
+
+}
+
+
+@:uiComp("flow")
+class FlowComp extends ObjectComp implements domkit.Component.ComponentDecl<h2d.Flow> {
+
+	@:p(auto) var width : Null<Int>;
+	@:p(auto) var height : Null<Int>;
+	@:p var maxWidth : Null<Int>;
+	@:p var maxHeight : Null<Int>;
+	@:p(flowBackground) var background : { tile : h2d.Tile, borderW : Int, borderH : Int };
+	@:p var backgroundAlpha : Float;
+	@:p var backgroundSmooth : Bool;
+	@:p(colorF) var backgroundColor : h3d.Vector;
+	@:p var debug : Bool;
+	@:p var layout : h2d.Flow.FlowLayout;
+	@:p var vertical : Bool;
+	@:p var horizontal : Bool;
+	@:p var stack : Bool;
+	@:p var multiline : Bool;
+	@:p(box) var padding : { left : Int, right : Int, top : Int, bottom : Int };
+	@:p var paddingLeft : Int;
+	@:p var paddingRight : Int;
+	@:p var paddingTop : Int;
+	@:p var paddingBottom : Int;
+	@:p var hspacing : Int;
+	@:p var vspacing : Int;
+	@:p(dimension) var spacing : { x: Float, y: Float };
+
+	@:p(align) var contentAlign : { h : h2d.Flow.FlowAlign, v : h2d.Flow.FlowAlign };
+	@:p(vAlign) var contentValign : h2d.Flow.FlowAlign;
+	@:p(hAlign) var contentHalign : h2d.Flow.FlowAlign;
+
+	static function set_minWidth( o : h2d.Flow, v ) {
+		o.minWidth = v;
+	}
+
+	static function set_minHeight( o : h2d.Flow, v ) {
+		o.minHeight = v;
+	}
+
+	static function set_width( o : h2d.Flow, v ) {
+		o.minWidth = o.maxWidth = v;
+	}
+
+	static function set_height( o : h2d.Flow, v ) {
+		o.minHeight = o.maxHeight = v;
+	}
+
+	static function set_contentValign( o : h2d.Flow, a ) {
+		o.verticalAlign = a;
+	}
+
+	static function set_contentHalign( o : h2d.Flow, a ) {
+		o.horizontalAlign = a;
+	}
+
+	static function set_contentAlign( o : h2d.Flow, v ) {
+		if( v == null ) {
+			o.horizontalAlign = o.verticalAlign = null;
+		} else {
+			o.horizontalAlign = v.h;
+			o.verticalAlign = v.v;
+		}
+	}
+
+	static function set_background( o : h2d.Flow, v ) {
+		if( v == null ) {
+			o.backgroundTile = null;
+			o.borderWidth = o.borderHeight = 0;
+		} else {
+			o.backgroundTile = v.tile;
+			o.borderWidth = v.borderW;
+			o.borderHeight = v.borderH;
+		}
+	}
+
+	static function set_backgroundAlpha( o : h2d.Flow, v ) {
+		var bg = @:privateAccess o.background;
+		if(bg == null)
+			return;
+		bg.alpha = v;
+	}
+
+	static function set_backgroundSmooth( o : h2d.Flow, v ) {
+		var bg = @:privateAccess o.background;
+		if(bg == null)
+			return;
+		bg.smooth = v;
+	}
+
+	static function set_backgroundColor( o : h2d.Flow, v ) {
+		var bg = @:privateAccess o.background;
+		if(bg == null)
+			return;
+		bg.color.load(v);
+	}
+
+	static function set_padding( o : h2d.Flow, v ) {
+		if( v == null ) {
+			o.padding = 0;
+		} else {
+			o.paddingLeft = v.left;
+			o.paddingRight = v.right;
+			o.paddingTop = v.top;
+			o.paddingBottom = v.bottom;
+		}
+	}
+
+	static function set_layout( o : h2d.Flow, v ) {
+		o.layout = v;
+	}
+
+	static function set_vertical( o : h2d.Flow, v ) {
+		o.layout = v ? Vertical : Horizontal;
+	}
+
+	static function set_horizontal( o : h2d.Flow, v ) {
+		o.layout = Horizontal;  // setting false resets to default
+	}
+
+	static function set_stack( o : h2d.Flow, v ) {
+		o.layout = v ? Stack : Horizontal;
+	}
+
+	static function set_hspacing( o : h2d.Flow, v ) {
+		o.horizontalSpacing = v;
+	}
+
+	static function set_vspacing( o : h2d.Flow, v) {
+		o.verticalSpacing = v;
+	}
+
+	static function set_spacing( o : h2d.Flow, v ) {
+		if(v == null) {
+			o.horizontalSpacing = o.verticalSpacing = 0;
+		}
+		else {
+			o.horizontalSpacing = Std.int(v.x);
+			o.verticalSpacing = Std.int(v.y);
+		}
+	}
+}
+
+#end

+ 32 - 0
h2d/domkit/InitComponents.hx

@@ -0,0 +1,32 @@
+package h2d.domkit;
+import h2d.domkit.BaseComponents.CustomParser;
+
+class InitComponents {
+
+	public static function init() {
+		domkit.Macros.registerComponentsPath("h2d.domkit.BaseComponents.$Comp");
+		domkit.Macros.registerComponentsPath("$Comp");
+		// force base components to be built before custom components
+		@:privateAccess domkit.Macros.preload = [
+			for( o in ["Object","Bitmap","Text","Flow","Mask"] )
+				'h2d.domkit.BaseComponents.${o}Comp'
+		];
+		return null;
+	}
+
+	public static function build() {
+		var fields = domkit.Macros.buildObject();
+		for( f in fields )
+			if( f.name == "document" ) {
+				fields = fields.concat((macro class {
+					override function onRemove() {
+						super.onRemove();
+						var style = Std.instance(document.style, h2d.domkit.Style);
+						if( style != null ) @:privateAccess style.remove(this);
+					}
+				}).fields);
+				break;
+			}
+		return fields;
+	}
+}

+ 7 - 0
h2d/domkit/Object.hx

@@ -0,0 +1,7 @@
+package h2d.domkit;
+import h2d.domkit.BaseComponents;
+
+@:build(h2d.domkit.InitComponents.init())
+@:autoBuild(h2d.domkit.InitComponents.build())
+interface Object {
+}

+ 87 - 0
h2d/domkit/Style.hx

@@ -0,0 +1,87 @@
+package h2d.domkit;
+
+class Style extends domkit.CssStyle {
+
+	var currentObjects : Array<h2d.domkit.Object> = [];
+	var resources : Array<hxd.res.Resource> = [];
+	var errors : Array<String>;
+	var errorsText : h2d.Text;
+
+	public function new() {
+		super();
+	}
+
+	public function load( r : hxd.res.Resource ) {
+		r.watch(function() onChange());
+		resources.push(r);
+		add(new domkit.CssParser().parseSheet(r.entry.getText()));
+		for( o in currentObjects )
+			getDocument(o).setStyle(this);
+	}
+
+	public function applyTo( obj ) {
+		currentObjects.remove(obj);
+		currentObjects.push(obj);
+		getDocument(obj).setStyle(this);
+	}
+
+	function remove(obj) {
+		currentObjects.remove(obj);
+	}
+
+	override function onInvalidProperty(e:domkit.Element<Dynamic>, s:domkit.CssStyle.RuleStyle, msg:String) {
+		if( errors != null ) {
+			if( msg == null ) msg = "Invalid property value '"+(domkit.CssParser.valueStr(s.value))+"'";
+			errors.push(msg+" for "+e.component.name+"."+s.p.name);
+		}
+	}
+
+	function onChange( ntry : Int = 0 ) {
+		if( ntry >= 10 ) return;
+		ntry++;
+		var oldRules = rules;
+		errors = [];
+		rules = [];
+		for( r in resources ) {
+			var txt = try r.entry.getText() catch( e : Dynamic ) { haxe.Timer.delay(onChange.bind(ntry),100); rules = oldRules; return; }
+			var parser = new domkit.CssParser();
+			try {
+				add(parser.parseSheet(txt));
+			} catch( e : domkit.Error ) {
+				parser.warnings.push({ msg : e.message, pmin : e.pmin, pmax : e.pmax });
+			}
+			for( w in parser.warnings ) {
+				var line = txt.substr(0,w.pmin).split("\n").length;
+				errors.push(r.entry.path+":"+line+": " + w.msg);
+		 	}
+		}
+		for( o in currentObjects )
+			getDocument(o).setStyle(this);
+		if( errors.length == 0 ) {
+			if( errorsText != null ) {
+				errorsText.parent.remove();
+				errorsText = null;
+			}
+		} else {
+			if( errorsText == null ) {
+				if( currentObjects.length == 0 ) return;
+				var scene = getDocument(currentObjects[0]).root.obj.getScene();
+				var fl = new h2d.Flow(scene);
+				fl.backgroundTile = h2d.Tile.fromColor(0x400000,0.9);
+				fl.padding = 10;
+				errorsText = new h2d.Text(hxd.res.DefaultFont.get(), fl);
+			}
+			var fl = Std.instance(errorsText.parent, h2d.Flow);
+			var sc = fl.getScene();
+			fl.maxWidth = sc.width;
+			errorsText.text = errors.join("\n");
+			var b = fl.getBounds();
+			fl.y = sc.height - Std.int(b.height);
+		}
+	}
+
+	function getDocument( o : h2d.domkit.Object ) : domkit.Document<h2d.Object> {
+		return (o : Dynamic).document;
+	}
+
+}

+ 1 - 1
h2d/filter/AbstractMask.hx

@@ -25,7 +25,7 @@ class AbstractMask extends Filter {
 	var maskMatrix : h2d.col.Matrix;
 	var maskMatrix : h2d.col.Matrix;
 	var tmpMatrix : h2d.col.Matrix;
 	var tmpMatrix : h2d.col.Matrix;
 	var obj : h2d.Object;
 	var obj : h2d.Object;
-	var bindCount : Int;
+	var bindCount : Int = 0;
 	public var mask(default, set) : h2d.Object;
 	public var mask(default, set) : h2d.Object;
 	public var maskVisible(default, set) : Bool;
 	public var maskVisible(default, set) : Bool;
 
 

+ 15 - 2
h3d/Buffer.hx

@@ -152,7 +152,11 @@ class Buffer {
 }
 }
 
 
 class BufferOffset {
 class BufferOffset {
+	#if flash
+	static var UID = 0;
 	public var id : Int;
 	public var id : Int;
+	#end
+
 	public var buffer : Buffer;
 	public var buffer : Buffer;
 	public var offset : Int;
 	public var offset : Int;
 
 
@@ -161,13 +165,22 @@ class BufferOffset {
 	*/
 	*/
 	public var next : BufferOffset;
 	public var next : BufferOffset;
 
 
-	static var UID = 0;
-
 	public function new(buffer, offset) {
 	public function new(buffer, offset) {
+		#if flash
 		this.id = UID++;
 		this.id = UID++;
+		#end
 		this.buffer = buffer;
 		this.buffer = buffer;
 		this.offset = offset;
 		this.offset = offset;
 	}
 	}
+
+	public inline function clone() {
+		var b = new BufferOffset(buffer,offset);
+		#if flash
+		b.id = id;
+		#end
+		return b;
+	}
+
 	public function dispose() {
 	public function dispose() {
 		if( buffer != null ) {
 		if( buffer != null ) {
 			buffer.dispose();
 			buffer.dispose();

+ 11 - 8
h3d/Camera.hx

@@ -41,7 +41,7 @@ class Camera {
 
 
 	public var follow : { pos : h3d.scene.Object, target : h3d.scene.Object };
 	public var follow : { pos : h3d.scene.Object, target : h3d.scene.Object };
 
 
-	public var frustum(default, null) = new h3d.col.Frustum();
+	public var frustum(default, null) : h3d.col.Frustum;
 
 
 	var minv : Matrix;
 	var minv : Matrix;
 	var miview : Matrix;
 	var miview : Matrix;
@@ -60,6 +60,7 @@ class Camera {
 		m = new Matrix();
 		m = new Matrix();
 		mcam = new Matrix();
 		mcam = new Matrix();
 		mproj = new Matrix();
 		mproj = new Matrix();
+		frustum = new h3d.col.Frustum();
 		update();
 		update();
 	}
 	}
 
 
@@ -118,7 +119,7 @@ class Camera {
 	/**
 	/**
 		Setup camera for cubemap rendering on the given face.
 		Setup camera for cubemap rendering on the given face.
 	**/
 	**/
-	public function setCubeMap( face : Int, position : h3d.Vector ) {
+	public function setCubeMap( face : Int, ?position : h3d.Vector ) {
 		var dx = 0, dy = 0, dz = 0;
 		var dx = 0, dy = 0, dz = 0;
 		switch( face ) {
 		switch( face ) {
 		case 0: dx = 1; up.set(0,1,0);
 		case 0: dx = 1; up.set(0,1,0);
@@ -128,7 +129,8 @@ class Camera {
 		case 4: dz = 1; up.set(0,1,0);
 		case 4: dz = 1; up.set(0,1,0);
 		case 5: dz = -1; up.set(0,1,0);
 		case 5: dz = -1; up.set(0,1,0);
 		}
 		}
-		pos.set(position.x,position.y,position.z);
+		if( position != null )
+			pos.load(position);
 		target.set(pos.x + dx,pos.y + dy,pos.z + dz);
 		target.set(pos.x + dx,pos.y + dy,pos.z + dz);
 	}
 	}
 
 
@@ -175,9 +177,9 @@ class Camera {
 		}
 		}
 		makeCameraMatrix(mcam);
 		makeCameraMatrix(mcam);
 		makeFrustumMatrix(mproj);
 		makeFrustumMatrix(mproj);
-		
+
 		m.multiply(mcam, mproj);
 		m.multiply(mcam, mproj);
-		
+
 		needInv = true;
 		needInv = true;
 		if( miview != null ) miview._44 = 0;
 		if( miview != null ) miview._44 = 0;
 
 
@@ -235,10 +237,11 @@ class Camera {
 		// in leftHanded the z axis is positive else it's negative
 		// in leftHanded the z axis is positive else it's negative
 		// this way we make sure that our [ax,ay,-az] matrix follow the same handness as our world
 		// this way we make sure that our [ax,ay,-az] matrix follow the same handness as our world
 		// We build a transposed version of Matrix.lookAt
 		// We build a transposed version of Matrix.lookAt
-		var az = rightHanded ? pos.sub(target) : target.sub(pos);
-		az.normalize();
+		var az = target.sub(pos);
+		if( rightHanded ) az.scale3(-1);
+		az.normalizeFast();
 		var ax = up.cross(az);
 		var ax = up.cross(az);
-		ax.normalize();
+		ax.normalizeFast();
 		if( ax.length() == 0 ) {
 		if( ax.length() == 0 ) {
 			ax.x = az.y;
 			ax.x = az.y;
 			ax.y = az.z;
 			ax.y = az.z;

+ 8 - 14
h3d/col/Bounds.hx

@@ -18,20 +18,14 @@ class Bounds implements Collider {
 		empty();
 		empty();
 	}
 	}
 
 
-	public function inFrustum( mvp : Matrix ) {
-		if( testPlane(Plane.frustumLeft(mvp)) < 0 )
-			return false;
-		if( testPlane(Plane.frustumRight(mvp)) < 0 )
-			return false;
-		if( testPlane(Plane.frustumBottom(mvp)) < 0 )
-			return false;
-		if( testPlane(Plane.frustumTop(mvp)) < 0 )
-			return false;
-		if( testPlane(Plane.frustumNear(mvp)) < 0 )
-			return false;
-		if( testPlane(Plane.frustumFar(mvp)) < 0 )
-			return false;
-		return true;
+	public inline function inFrustum( f : Frustum ) {
+		return f.hasBounds(this);
+	}
+
+	public inline function inSphere( s : Sphere ) {
+		var c = new Point(s.x,s.y,s.z);
+		var p = new Point(Math.max(xMin, Math.min(s.x, xMax)), Math.max(yMin, Math.min(s.y, yMax)), Math.max(zMin, Math.min(s.z, zMax)));
+		return c.distanceSq(p) < s.r*s.r;
 	}
 	}
 
 
 	inline function testPlane( p : Plane ) {
 	inline function testPlane( p : Plane ) {

+ 117 - 0
h3d/col/Capsule.hx

@@ -0,0 +1,117 @@
+package h3d.col;
+
+class Capsule implements Collider {
+
+	public var a : Point;
+	public var b : Point;
+	public var r : Float;
+	static var tmpSphere = new Sphere(0., 0., 0., 0.);
+
+	public inline function new(a : Point, b : Point, r : Float) {
+		this.a = a;
+		this.b = b;
+		this.r = r;
+	}
+
+	public inline function contains( p : Point ) {
+		return new Seg(a, b).distanceSq(p) < r*r;
+	}
+
+	public function rayIntersection( r : Ray, bestMatch : Bool ) : Float {
+		// computing  t'  =  (t * AB.RD + AB.AO) / AB.AB  =  t * m + n
+		var AB = new h3d.col.Point(b.x-a.x, b.y-a.y, b.z-a.z);
+		var o = r.getPos();
+		var AO = new h3d.col.Point(o.x-a.x, o.y-a.y, o.z-a.z);
+
+		var RD = r.getDir();
+
+		var ABdotAB = AB.dot(AB);
+		var m = AB.dot(RD) / ABdotAB;
+		var n = AB.dot(AO) / ABdotAB;
+
+		// using |PK| = r to solve t
+		var Q = new h3d.col.Point(RD.x-(AB.x*m), RD.y-(AB.y*m), RD.z-(AB.z*m)); // RD - (AB * m)
+		var R = new h3d.col.Point(AO.x-(AB.x*n), AO.y-(AB.y*n), AO.z-(AB.z*n)); // AO - (AB * n);
+
+		var coefA = Q.dot(Q);
+		var coefB = 2.0 * Q.dot(R);
+		var coefC = R.dot(R) - (this.r * this.r);
+
+		if (coefA == 0.0) { // if parallel
+			tmpSphere.load(this.a.x, this.a.y, this.a.z, this.r);
+			var intersectSphereA = tmpSphere.rayIntersection(r, bestMatch);
+			tmpSphere.load(this.b.x, this.b.y, this.b.z, this.r);
+			var intersectSphereB = tmpSphere.rayIntersection(r, bestMatch);
+
+			if (intersectSphereA < 0 && intersectSphereB < 0) {
+				return -1;
+			}
+
+			if (intersectSphereB < intersectSphereA) {
+				return intersectSphereB;
+			}
+
+			if (intersectSphereA < intersectSphereB) {
+				return intersectSphereA;
+			}
+		}
+
+		var discriminant = coefB * coefB - 4.0 * coefA * coefC;
+
+		if (discriminant < 0.0) {
+			return -1;
+		}
+		var discriminantSqrt = Math.sqrt(discriminant);
+
+		var t1 = (- coefB - discriminantSqrt) / (2.0 * coefA);
+		var t2 = (- coefB + discriminantSqrt) / (2.0 * coefA);
+
+		var tMin = (t1 < t2) ? t1 : t2;
+
+		var tPrimeMin = tMin * m + n;
+
+		if (tPrimeMin < 0.0) {
+			tmpSphere.load(this.a.x, this.a.y, this.a.z, this.r);
+			return tmpSphere.rayIntersection(r, bestMatch);
+		} else if (tPrimeMin > 1.0) {
+			tmpSphere.load(this.b.x, this.b.y, this.b.z, this.r);
+			return tmpSphere.rayIntersection(r, bestMatch);
+		} else {
+			var intersection = new Point(o.x + (RD.x * tMin), o.y + (RD.y * tMin), o.z + (RD.z * tMin));
+			return o.distance(intersection);
+		}
+		return -1;
+	}
+
+	public function inFrustum( f : Frustum ) {
+		tmpSphere.load(a.x + (b.x-a.x), a.y + (b.y-a.y), a.z + (b.z-a.z), (b.distance(a)/2 + r));
+		return tmpSphere.inFrustum(f);
+	}
+
+	public function inSphere( s : Sphere ) {
+		tmpSphere.load(a.x + (b.x-a.x), a.y + (b.y-a.y), a.z + (b.z-a.z), (b.distance(a)/2 + r));
+		return tmpSphere.inSphere(s);
+	}
+
+	public function toString() {
+		return "Capsule{" + a + "," + b + "," + hxd.Math.fmt(r) + "}";
+	}
+
+	#if (hxbit && !macro)
+	function customSerialize( ctx : hxbit.Serializer ) {
+		ctx.addFloat(a.x);
+		ctx.addFloat(a.y);
+		ctx.addFloat(a.z);
+		ctx.addFloat(b.x);
+		ctx.addFloat(b.y);
+		ctx.addFloat(b.z);
+		ctx.addFloat(r);
+	}
+	function customUnserialize( ctx : hxbit.Serializer ) {
+		a = new Point(ctx.getFloat(), ctx.getFloat(), ctx.getFloat());
+		b = new Point(ctx.getFloat(), ctx.getFloat(), ctx.getFloat());
+		r = ctx.getFloat();
+	}
+	#end
+
+}

+ 15 - 6
h3d/col/Collider.hx

@@ -7,8 +7,8 @@ interface Collider extends hxd.impl.Serializable.StructSerializable {
 	**/
 	**/
 	public function rayIntersection( r : Ray, bestMatch : Bool ) : Float;
 	public function rayIntersection( r : Ray, bestMatch : Bool ) : Float;
 	public function contains( p : Point ) : Bool;
 	public function contains( p : Point ) : Bool;
-	public function inFrustum( mvp : h3d.Matrix ) : Bool;
-
+	public function inFrustum( f : Frustum ) : Bool;
+	public function inSphere( s : Sphere ) : Bool;
 }
 }
 
 
 
 
@@ -32,10 +32,13 @@ class OptimizedCollider implements hxd.impl.Serializable implements Collider {
 		return a.contains(p) && b.contains(p);
 		return a.contains(p) && b.contains(p);
 	}
 	}
 
 
-	public function inFrustum( mvp : h3d.Matrix ) {
-		return a.inFrustum(mvp) && b.inFrustum(mvp);
+	public function inFrustum( f : Frustum ) {
+		return a.inFrustum(f) && b.inFrustum(f);
 	}
 	}
 
 
+	public function inSphere( s : Sphere ) {
+		return a.inSphere(s) && b.inSphere(s);
+	}
 
 
 	#if (hxbit && !macro)
 	#if (hxbit && !macro)
 	function customSerialize(ctx:hxbit.Serializer) {
 	function customSerialize(ctx:hxbit.Serializer) {
@@ -73,13 +76,19 @@ class GroupCollider implements Collider {
 		return false;
 		return false;
 	}
 	}
 
 
-	public function inFrustum( mvp : h3d.Matrix ) {
+	public function inFrustum( f : Frustum ) {
 		for( c in colliders )
 		for( c in colliders )
-			if( c.inFrustum(mvp) )
+			if( c.inFrustum(f) )
 				return true;
 				return true;
 		return false;
 		return false;
 	}
 	}
 
 
+	public function inSphere( s : Sphere ) {
+		for( c in colliders )
+			if( c.inSphere(s) )
+				return true;
+		return false;
+	}
 
 
 	#if (hxbit && !macro && heaps_enable_serialize)
 	#if (hxbit && !macro && heaps_enable_serialize)
 
 

+ 12 - 0
h3d/col/Frustum.hx

@@ -76,6 +76,18 @@ class Frustum {
 		pfar.normalize();
 		pfar.normalize();
 	}
 	}
 
 
+	public function hasPoint( p : Point ) {
+		if( pleft.distance(p) < 0 ) return false;
+		if( pright.distance(p) < 0 ) return false;
+		if( ptop.distance(p) < 0 ) return false;
+		if( pbottom.distance(p) < 0 ) return false;
+		if( checkNearFar ) {
+			if( pnear.distance(p) < 0 ) return false;
+			if( pfar.distance(p) < 0 ) return false;
+		}
+		return true;
+	}
+
 	public function hasSphere( s : Sphere ) {
 	public function hasSphere( s : Sphere ) {
 		var p = s.getCenter();
 		var p = s.getCenter();
 		if( pleft.distance(p) < -s.r ) return false;
 		if( pleft.distance(p) < -s.r ) return false;

+ 6 - 0
h3d/col/IPoint.hx

@@ -23,4 +23,10 @@ class IPoint {
 		this.z = z;
 		this.z = z;
 	}
 	}
 
 
+	public inline function load( p : IPoint ) {
+		this.x = p.x;
+		this.y = p.y;
+		this.z = p.z;
+	}
+
 }
 }

+ 6 - 1
h3d/col/ObjectCollider.hx

@@ -33,7 +33,12 @@ class ObjectCollider implements Collider implements hxd.impl.Serializable {
 		return b;
 		return b;
 	}
 	}
 
 
-	public function inFrustum( mvp : h3d.Matrix ) {
+	public function inFrustum( f : Frustum ) {
+		throw "Not implemented";
+		return false;
+	}
+
+	public function inSphere( s : Sphere ) {
 		throw "Not implemented";
 		throw "Not implemented";
 		return false;
 		return false;
 	}
 	}

+ 2 - 14
h3d/col/Point.hx

@@ -19,20 +19,8 @@ class Point {
 		z *= v;
 		z *= v;
 	}
 	}
 
 
-	public function inFrustum( mvp : Matrix ) {
-		if( !Plane.frustumLeft(mvp).side(this) )
-			return false;
-		if( !Plane.frustumRight(mvp).side(this) )
-			return false;
-		if( !Plane.frustumBottom(mvp).side(this) )
-			return false;
-		if( !Plane.frustumTop(mvp).side(this) )
-			return false;
-		if( !Plane.frustumNear(mvp).side(this) )
-			return false;
-		if( !Plane.frustumFar(mvp).side(this) )
-			return false;
-		return true;
+	public inline function inFrustum( f : Frustum ) {
+		return f.hasPoint(this);
 	}
 	}
 
 
 	public inline function set(x, y, z) {
 	public inline function set(x, y, z) {

+ 12 - 2
h3d/col/Polygon.hx

@@ -99,7 +99,12 @@ class TriPlane implements Collider {
 		return nx * p.x + ny * p.y + nz * p.z - d >= 0;
 		return nx * p.x + ny * p.y + nz * p.z - d >= 0;
 	}
 	}
 
 
-	public function inFrustum( m : h3d.Matrix ) {
+	public function inFrustum( f : Frustum ) {
+		throw "Not implemented";
+		return false;
+	}
+
+	public function inSphere( s : Sphere ) {
 		throw "Not implemented";
 		throw "Not implemented";
 		return false;
 		return false;
 	}
 	}
@@ -233,7 +238,12 @@ class Polygon implements Collider {
 		return best;
 		return best;
 	}
 	}
 
 
-	public function inFrustum( m : h3d.Matrix ) {
+	public function inFrustum( f : Frustum ) {
+		throw "Not implemented";
+		return false;
+	}
+
+	public function inSphere( s : Sphere ) {
 		throw "Not implemented";
 		throw "Not implemented";
 		return false;
 		return false;
 	}
 	}

+ 6 - 1
h3d/col/PolygonBuffer.hx

@@ -41,7 +41,12 @@ class PolygonBuffer implements Collider {
 		return true;
 		return true;
 	}
 	}
 
 
-	public function inFrustum( m : h3d.Matrix ) {
+	public function inFrustum( f : Frustum ) {
+		throw "Not implemented";
+		return false;
+	}
+
+	public function inSphere( s : Sphere ) {
 		throw "Not implemented";
 		throw "Not implemented";
 		return false;
 		return false;
 	}
 	}

+ 5 - 0
h3d/col/SkinCollider.hx

@@ -26,6 +26,11 @@ class SkinCollider implements hxd.impl.Serializable implements Collider {
 		return transform.inFrustum(p);
 		return transform.inFrustum(p);
 	}
 	}
 
 
+	public function inSphere( s : Sphere ) {
+		throw "Not implemented";
+		return false;
+	}
+
 	public function rayIntersection(r, bestMatch) {
 	public function rayIntersection(r, bestMatch) {
 		applyTransform();
 		applyTransform();
 		return transform.rayIntersection(r, bestMatch);
 		return transform.rayIntersection(r, bestMatch);

+ 14 - 31
h3d/col/Sphere.hx

@@ -8,10 +8,14 @@ class Sphere implements Collider {
 	public var r : Float;
 	public var r : Float;
 
 
 	public inline function new(x=0., y=0., z=0., r=0.) {
 	public inline function new(x=0., y=0., z=0., r=0.) {
-		this.x = x;
-		this.y = y;
-		this.z = z;
-		this.r = r;
+		load(x, y, z, r);
+	}
+
+	public inline function load(sx=0., sy=0., sz=0., sr=0.) {
+		this.x = sx;
+		this.y = sy;
+		this.z = sz;
+		this.r = sr;
 	}
 	}
 
 
 	public inline function getCenter() {
 	public inline function getCenter() {
@@ -43,33 +47,12 @@ class Sphere implements Collider {
 		return 1 - t;
 		return 1 - t;
 	}
 	}
 
 
-	public function inFrustum( mvp : Matrix ) {
-		var p = getCenter();
-		var pl = Plane.frustumLeft(mvp);
-		pl.normalize();
-		if( pl.distance(p) < -r )
-			return false;
-		var pl = Plane.frustumRight(mvp);
-		pl.normalize();
-		if( pl.distance(p) < -r )
-			return false;
-		var pl = Plane.frustumBottom(mvp);
-		pl.normalize();
-		if( pl.distance(p) < -r )
-			return false;
-		var pl = Plane.frustumTop(mvp);
-		pl.normalize();
-		if( pl.distance(p) < -r )
-			return false;
-		var pl = Plane.frustumNear(mvp);
-		pl.normalize();
-		if( pl.distance(p) < -r )
-			return false;
-		var pl = Plane.frustumNear(mvp);
-		pl.normalize();
-		if( pl.distance(p) < -r )
-			return false;
-		return true;
+	public inline function inFrustum( f : Frustum ) {
+		return f.hasSphere(this);
+	}
+
+	public inline function inSphere( s : Sphere ) {
+		return new Point(x,y,z).distanceSq(new Point(s.x,s.y,s.z)) < (s.r + r)*(s.r + r);
 	}
 	}
 
 
 	public function toString() {
 	public function toString() {

+ 6 - 5
h3d/impl/DirectXDriver.hx

@@ -38,7 +38,7 @@ private class CompiledShader {
 	public var vertex : ShaderContext;
 	public var vertex : ShaderContext;
 	public var fragment : ShaderContext;
 	public var fragment : ShaderContext;
 	public var layout : Layout;
 	public var layout : Layout;
-	public var inputs : Array<String>;
+	public var inputs : InputNames;
 	public var offsets : Array<Int>;
 	public var offsets : Array<Int>;
 	public function new() {
 	public function new() {
 	}
 	}
@@ -887,10 +887,10 @@ class DirectXDriver extends h3d.impl.Driver {
 			s = new CompiledShader();
 			s = new CompiledShader();
 			var vertex = compileShader(shader.vertex);
 			var vertex = compileShader(shader.vertex);
 			var fragment = compileShader(shader.fragment);
 			var fragment = compileShader(shader.fragment);
+			var inputs = [];
 			if( hasDeviceError ) return false;
 			if( hasDeviceError ) return false;
 			s.vertex = vertex.s;
 			s.vertex = vertex.s;
 			s.fragment = fragment.s;
 			s.fragment = fragment.s;
-			s.inputs = [];
 			s.offsets = [];
 			s.offsets = [];
 
 
 			var layout = [], offset = 0;
 			var layout = [], offset = 0;
@@ -923,7 +923,7 @@ class DirectXDriver extends h3d.impl.Driver {
 						e.inputSlotClass = PerVertexData;
 						e.inputSlotClass = PerVertexData;
 					layout.push(e);
 					layout.push(e);
 					s.offsets.push(offset);
 					s.offsets.push(offset);
-					s.inputs.push(v.name);
+					inputs.push(v.name);
 
 
 					var size = switch( v.type ) {
 					var size = switch( v.type ) {
 					case TVec(n, _): n;
 					case TVec(n, _): n;
@@ -938,6 +938,7 @@ class DirectXDriver extends h3d.impl.Driver {
 			var n = new hl.NativeArray(layout.length);
 			var n = new hl.NativeArray(layout.length);
 			for( i in 0...layout.length )
 			for( i in 0...layout.length )
 				n[i] = layout[i];
 				n[i] = layout[i];
+			s.inputs = InputNames.get(inputs);
 			s.layout = Driver.createInputLayout(n, vertex.bytes, vertex.bytes.length);
 			s.layout = Driver.createInputLayout(n, vertex.bytes, vertex.bytes.length);
 			if( s.layout == null )
 			if( s.layout == null )
 				throw "Failed to create input layout";
 				throw "Failed to create input layout";
@@ -952,7 +953,7 @@ class DirectXDriver extends h3d.impl.Driver {
 		return true;
 		return true;
 	}
 	}
 
 
-	override function getShaderInputNames():Array<String> {
+	override function getShaderInputNames() : InputNames {
 		return currentShader.inputs;
 		return currentShader.inputs;
 	}
 	}
 
 
@@ -960,7 +961,7 @@ class DirectXDriver extends h3d.impl.Driver {
 		if( hasDeviceError ) return;
 		if( hasDeviceError ) return;
 		var vbuf = @:privateAccess buffer.buffer.vbuf;
 		var vbuf = @:privateAccess buffer.buffer.vbuf;
 		var start = -1, max = -1, position = 0;
 		var start = -1, max = -1, position = 0;
-		for( i in 0...currentShader.inputs.length ) {
+		for( i in 0...currentShader.inputs.names.length ) {
 			if( currentVBuffers[i] != vbuf.res || offsets[i] != currentShader.offsets[i] << 2 ) {
 			if( currentVBuffers[i] != vbuf.res || offsets[i] != currentShader.offsets[i] << 2 ) {
 				currentVBuffers[i] = vbuf.res;
 				currentVBuffers[i] = vbuf.res;
 				strides[i] = buffer.buffer.stride << 2;
 				strides[i] = buffer.buffer.stride << 2;

+ 21 - 1
h3d/impl/Driver.hx

@@ -125,6 +125,26 @@ enum RenderFlag {
 	CameraHandness;
 	CameraHandness;
 }
 }
 
 
+class InputNames {
+	public var id(default,null) : Int;
+	public var names(default,null) : Array<String>;
+	function new(names) {
+		this.id = UID++;
+		this.names = names;
+	}
+	static var UID = 0;
+	static var CACHE = new Map<String,InputNames>();
+	public static function get( names : Array<String> ) {
+		var key = names.join("|");
+		var i = CACHE.get(key);
+		if( i == null ) {
+			i = new InputNames(names.copy());
+			CACHE.set(key,i);
+		}
+		return i;
+	}
+}
+
 class Driver {
 class Driver {
 
 
 	public var logEnable : Bool;
 	public var logEnable : Bool;
@@ -198,7 +218,7 @@ class Driver {
 	public function uploadShaderBuffers( buffers : h3d.shader.Buffers, which : h3d.shader.Buffers.BufferKind ) {
 	public function uploadShaderBuffers( buffers : h3d.shader.Buffers, which : h3d.shader.Buffers.BufferKind ) {
 	}
 	}
 
 
-	public function getShaderInputNames() : Array<String> {
+	public function getShaderInputNames() : InputNames {
 		return null;
 		return null;
 	}
 	}
 
 

+ 12 - 10
h3d/impl/GlDriver.hx

@@ -131,7 +131,7 @@ private class CompiledProgram {
 	public var vertex : CompiledShader;
 	public var vertex : CompiledShader;
 	public var fragment : CompiledShader;
 	public var fragment : CompiledShader;
 	public var stride : Int;
 	public var stride : Int;
-	public var attribNames : Array<String>;
+	public var inputs : InputNames;
 	public var attribs : Array<CompiledAttribute>;
 	public var attribs : Array<CompiledAttribute>;
 	public function new() {
 	public function new() {
 	}
 	}
@@ -283,7 +283,7 @@ class GlDriver extends Driver {
 	}
 	}
 
 
 	override function getShaderInputNames() {
 	override function getShaderInputNames() {
-		return curShader.attribNames;
+		return curShader.inputs;
 	}
 	}
 
 
 	override function getNativeShaderCode( shader : hxsl.RuntimeShader ) {
 	override function getNativeShaderCode( shader : hxsl.RuntimeShader ) {
@@ -410,12 +410,12 @@ class GlDriver extends Driver {
 					return selectShader(shader);
 					return selectShader(shader);
 				}
 				}
 				#end
 				#end
-				throw "Program linkage failure: "+log+"\nVertex=\n"+glout.run(shader.vertex.data)+"\n\nFragment=\n"+glout.run(shader.fragment.data);
+				throw "Program linkage failure: "+log+"\nVertex=\n"+shader.vertex.code+"\n\nFragment=\n"+shader.fragment.code;
 			}
 			}
 			firstShader = false;
 			firstShader = false;
 			initShader(p, p.vertex, shader.vertex);
 			initShader(p, p.vertex, shader.vertex);
 			initShader(p, p.fragment, shader.fragment);
 			initShader(p, p.fragment, shader.fragment);
-			p.attribNames = [];
+			var attribNames = [];
 			p.attribs = [];
 			p.attribs = [];
 			p.stride = 0;
 			p.stride = 0;
 			for( v in shader.vertex.data.vars )
 			for( v in shader.vertex.data.vars )
@@ -447,10 +447,11 @@ class GlDriver extends Driver {
 							}
 							}
 					}
 					}
 					p.attribs.push(a);
 					p.attribs.push(a);
-					p.attribNames.push(v.name);
+					attribNames.push(v.name);
 					p.stride += size;
 					p.stride += size;
 				default:
 				default:
 				}
 				}
+			p.inputs = InputNames.get(attribNames);
 			programs.set(shader.id, p);
 			programs.set(shader.id, p);
 		}
 		}
 		if( curShader == p ) return false;
 		if( curShader == p ) return false;
@@ -1097,10 +1098,11 @@ class GlDriver extends Driver {
 		#if hl
 		#if hl
 		var needed = streamPos + length;
 		var needed = streamPos + length;
 		var total = (needed + 7) & ~7; // align on 8 bytes
 		var total = (needed + 7) & ~7; // align on 8 bytes
+		var alen = total - streamPos;
 		if( total > streamLen ) expandStream(total);
 		if( total > streamLen ) expandStream(total);
 		streamBytes.blit(streamPos, data, pos, length);
 		streamBytes.blit(streamPos, data, pos, length);
 		data = streamBytes.offset(streamPos);
 		data = streamBytes.offset(streamPos);
-		streamPos = total;
+		streamPos += alen;
 		#end
 		#end
 		return data;
 		return data;
 	}
 	}
@@ -1232,7 +1234,7 @@ class GlDriver extends Driver {
 			for( i in 0...curShader.attribs.length ) {
 			for( i in 0...curShader.attribs.length ) {
 				var a = curShader.attribs[i];
 				var a = curShader.attribs[i];
 				var pos;
 				var pos;
-				switch( curShader.attribNames[i] ) {
+				switch( curShader.inputs.names[i] ) {
 				case "position":
 				case "position":
 					pos = 0;
 					pos = 0;
 				case "normal":
 				case "normal":
@@ -1383,7 +1385,7 @@ class GlDriver extends Driver {
 	}
 	}
 
 
 	override function capturePixels(tex:h3d.mat.Texture, layer:Int, mipLevel:Int, ?region:h2d.col.IBounds) {
 	override function capturePixels(tex:h3d.mat.Texture, layer:Int, mipLevel:Int, ?region:h2d.col.IBounds) {
-		
+
 		var pixels : hxd.Pixels;
 		var pixels : hxd.Pixels;
 		var x : Int, y : Int;
 		var x : Int, y : Int;
 		if (region != null) {
 		if (region != null) {
@@ -1399,7 +1401,7 @@ class GlDriver extends Driver {
 			x = 0;
 			x = 0;
 			y = 0;
 			y = 0;
 		}
 		}
-		
+
 		var old = curTarget;
 		var old = curTarget;
 		var oldCount = numTargets;
 		var oldCount = numTargets;
 		var oldLayer = curTargetLayer;
 		var oldLayer = curTargetLayer;
@@ -1435,7 +1437,7 @@ class GlDriver extends Driver {
 		if( tex.depthBuffer != null && (tex.depthBuffer.width != tex.width || tex.depthBuffer.height != tex.height) )
 		if( tex.depthBuffer != null && (tex.depthBuffer.width != tex.width || tex.depthBuffer.height != tex.height) )
 			throw "Invalid depth buffer size : does not match render target size";
 			throw "Invalid depth buffer size : does not match render target size";
 
 
-		if( glES == 1 && mipLevel > 0 ) throw "Cannot render to mipLevel in WebGL1, use upload() instead";
+		if( mipLevel > 0 && glES == 1 ) throw "Cannot render to mipLevel in WebGL1, use upload() instead";
 
 
 		if( tex.t == null )
 		if( tex.t == null )
 			tex.alloc();
 			tex.alloc();

+ 1 - 1
h3d/impl/LogDriver.hx

@@ -247,7 +247,7 @@ class LogDriver extends Driver {
 		return inf;
 		return inf;
 	}
 	}
 
 
-	override function getShaderInputNames() : Array<String> {
+	override function getShaderInputNames() {
 		return d.getShaderInputNames();
 		return d.getShaderInputNames();
 	}
 	}
 
 

+ 2 - 2
h3d/impl/NullDriver.hx

@@ -42,12 +42,12 @@ class NullDriver extends Driver {
 		return true;
 		return true;
 	}
 	}
 
 
-	override function getShaderInputNames() : Array<String> {
+	override function getShaderInputNames() : InputNames {
 		var names = [];
 		var names = [];
 		for( v in cur.vertex.data.vars )
 		for( v in cur.vertex.data.vars )
 			if( v.kind == Input )
 			if( v.kind == Input )
 				names.push(v.name);
 				names.push(v.name);
-		return names;
+		return InputNames.get(names);
 	}
 	}
 
 
 	override function allocTexture( t : h3d.mat.Texture ) : Texture {
 	override function allocTexture( t : h3d.mat.Texture ) : Texture {

+ 6 - 6
h3d/impl/Stage3dDriver.hx

@@ -39,13 +39,12 @@ private class CompiledShader {
 	public var s : hxsl.RuntimeShader;
 	public var s : hxsl.RuntimeShader;
 	public var stride : Int;
 	public var stride : Int;
 	public var bufferFormat : Int;
 	public var bufferFormat : Int;
-	public var inputNames : Array<String>;
+	public var inputs : InputNames;
 	public var usedTextures : Array<Bool>;
 	public var usedTextures : Array<Bool>;
 	public function new(s) {
 	public function new(s) {
 		this.s = s;
 		this.s = s;
 		stride = 0;
 		stride = 0;
 		bufferFormat = 0;
 		bufferFormat = 0;
-		inputNames = [];
 		usedTextures = [];
 		usedTextures = [];
 	}
 	}
 }
 }
@@ -579,6 +578,7 @@ class Stage3dDriver extends Driver {
 		fdata.endian = flash.utils.Endian.LITTLE_ENDIAN;
 		fdata.endian = flash.utils.Endian.LITTLE_ENDIAN;
 
 
 		var pos = 0;
 		var pos = 0;
+		var inputNames = [];
 		for( v in shader.vertex.data.vars )
 		for( v in shader.vertex.data.vars )
 			if( v.kind == Input ) {
 			if( v.kind == Input ) {
 				var size;
 				var size;
@@ -593,11 +593,11 @@ class Stage3dDriver extends Driver {
 				var idx = FORMAT.indexOf(fmt);
 				var idx = FORMAT.indexOf(fmt);
 				if( idx < 0 ) throw "assert " + fmt;
 				if( idx < 0 ) throw "assert " + fmt;
 				p.bufferFormat |= idx << (pos * 3);
 				p.bufferFormat |= idx << (pos * 3);
-				p.inputNames.push(v.name);
+				inputNames.push(v.name);
 				p.stride += size;
 				p.stride += size;
 				pos++;
 				pos++;
 			}
 			}
-
+		p.inputs = InputNames.get(inputNames);
 		p.p.upload(vdata, fdata);
 		p.p.upload(vdata, fdata);
 		return p;
 		return p;
 	}
 	}
@@ -711,7 +711,7 @@ class Stage3dDriver extends Driver {
 			}
 			}
 		} else {
 		} else {
 			offset = 8; // custom data starts after [position, normal, uv]
 			offset = 8; // custom data starts after [position, normal, uv]
-			for( s in curShader.inputNames ) {
+			for( s in curShader.inputs.names ) {
 				switch( s ) {
 				switch( s ) {
 				case "position":
 				case "position":
 					ctx.setVertexBufferAt(pos++, m.vbuf, 0, FORMAT[3]);
 					ctx.setVertexBufferAt(pos++, m.vbuf, 0, FORMAT[3]);
@@ -736,7 +736,7 @@ class Stage3dDriver extends Driver {
 	}
 	}
 
 
 	override function getShaderInputNames() {
 	override function getShaderInputNames() {
-		return curShader.inputNames;
+		return curShader.inputs;
 	}
 	}
 
 
 	override function selectMultiBuffers( buffers : Buffer.BufferOffset ) {
 	override function selectMultiBuffers( buffers : Buffer.BufferOffset ) {

+ 5 - 3
h3d/impl/TextureCache.hx

@@ -40,12 +40,14 @@ class TextureCache {
 		position = 0;
 		position = 0;
 	}
 	}
 
 
-	public function allocTarget( name : String, width : Int, height : Int, defaultDepth=true, ?format:hxd.PixelFormat, ?flags : Array<h3d.mat.Data.TextureFlags> ) {
+	public function allocTarget( name : String, width : Int, height : Int, defaultDepth=true, ?format:hxd.PixelFormat, isCube = false ) {
 		var t = cache[position];
 		var t = cache[position];
 		if( format == null ) format = defaultFormat;
 		if( format == null ) format = defaultFormat;
-		if( t == null || t.isDisposed() || t.width != width || t.height != height || t.format != format ) {
+		if( t == null || t.isDisposed() || t.width != width || t.height != height || t.format != format || isCube != t.flags.has(Cube) ) {
 			if( t != null ) t.dispose();
 			if( t != null ) t.dispose();
-			t = new h3d.mat.Texture(width, height, flags == null ? [Target] : flags, format);
+			var flags : Array<h3d.mat.Data.TextureFlags> = [Target];
+			if( isCube ) flags.push(Cube);
+			t = new h3d.mat.Texture(width, height, flags, format);
 			cache[position] = t;
 			cache[position] = t;
 		}
 		}
 		t.depthBuffer = defaultDepth ? defaultDepthBuffer : null;
 		t.depthBuffer = defaultDepth ? defaultDepthBuffer : null;

+ 1 - 1
h3d/mat/BaseMaterial.hx

@@ -55,7 +55,7 @@ class BaseMaterial extends hxd.impl.AnyProps implements hxd.impl.Serializable {
 			out.push(p);
 			out.push(p);
 			p = p.nextPass;
 			p = p.nextPass;
 		}
 		}
-		return out.iterator();
+		return out;
 	}
 	}
 
 
 	public function getPass( name : String ) : Pass {
 	public function getPass( name : String ) : Pass {

+ 3 - 3
h3d/mat/MaterialSetup.hx

@@ -13,11 +13,11 @@ class MaterialSetup {
 	}
 	}
 
 
 	public function createRenderer() : h3d.scene.Renderer {
 	public function createRenderer() : h3d.scene.Renderer {
-		return new h3d.scene.DefaultRenderer();
+		return new h3d.scene.fwd.Renderer();
 	}
 	}
 
 
-	public function createLightSystem() {
-		return new h3d.scene.LightSystem();
+	public function createLightSystem() : h3d.scene.LightSystem {
+		return new h3d.scene.fwd.LightSystem();
 	}
 	}
 
 
 	public function createMaterial() {
 	public function createMaterial() {

+ 7 - 4
h3d/mat/Pass.hx

@@ -8,6 +8,7 @@ import h3d.mat.Data;
 class Pass implements hxd.impl.Serializable {
 class Pass implements hxd.impl.Serializable {
 
 
 	@:s public var name(default, null) : String;
 	@:s public var name(default, null) : String;
+	var flags : Int;
 	var passId : Int;
 	var passId : Int;
 	@:s var bits : Int = 0;
 	@:s var bits : Int = 0;
 	@:s var parentPass : Pass;
 	@:s var parentPass : Pass;
@@ -15,18 +16,20 @@ class Pass implements hxd.impl.Serializable {
 	var shaders : hxsl.ShaderList;
 	var shaders : hxsl.ShaderList;
 	@:s var nextPass : Pass;
 	@:s var nextPass : Pass;
 
 
-	@:s public var enableLights : Bool;
+	@:bits(flags) public var enableLights : Bool;
 	/**
 	/**
 		Inform the pass system that the parameters will be modified in object draw() command,
 		Inform the pass system that the parameters will be modified in object draw() command,
 		so they will be manually uploaded by calling RenderContext.uploadParams.
 		so they will be manually uploaded by calling RenderContext.uploadParams.
 	**/
 	**/
-	@:s public var dynamicParameters : Bool;
+	@:bits(flags) public var dynamicParameters : Bool;
 
 
 	/**
 	/**
 		Mark the pass as static, this will allow some renderers or shadows to filter it
 		Mark the pass as static, this will allow some renderers or shadows to filter it
 		when rendering static/dynamic parts.
 		when rendering static/dynamic parts.
 	**/
 	**/
-	@:s public var isStatic : Bool;
+	@:bits(flags) public var isStatic : Bool;
+
+	@:bits(flags) var batchMode : Bool; // for MeshBatch
 
 
 	@:bits(bits) public var culling : Face;
 	@:bits(bits) public var culling : Face;
 	@:bits(bits) public var depthWrite : Bool;
 	@:bits(bits) public var depthWrite : Bool;
@@ -283,7 +286,7 @@ class Pass implements hxd.impl.Serializable {
 			return h3d.Engine.getCurrent().driver.getNativeShaderCode(shader);
 			return h3d.Engine.getCurrent().driver.getNativeShaderCode(shader);
 		}
 		}
 	}
 	}
-	
+
 	#if hxbit
 	#if hxbit
 
 
 	public function customSerialize( ctx : hxbit.Serializer ) {
 	public function customSerialize( ctx : hxbit.Serializer ) {

+ 2 - 2
h3d/mat/PbrMaterial.hx

@@ -96,12 +96,12 @@ class PbrMaterial extends Material {
 		case Albedo:
 		case Albedo:
 			mainPass.setPassName("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);
 			if( e == null ) e = mainPass.addShader(new h3d.shader.Emissive(props.emissive));
 			if( e == null ) e = mainPass.addShader(new h3d.shader.Emissive(props.emissive));
 			e.emissive = props.emissive;
 			e.emissive = props.emissive;
 		case Distortion:
 		case Distortion:
-			mainPass.setPassName("Distortion");
+			mainPass.setPassName("distortion");
 			mainPass.depthWrite = false;
 			mainPass.depthWrite = false;
 		case Overlay:
 		case Overlay:
 			mainPass.setPassName("overlay");
 			mainPass.setPassName("overlay");

+ 4 - 0
h3d/mat/PbrMaterialSetup.hx

@@ -38,4 +38,8 @@ class PbrMaterialSetup extends MaterialSetup {
 		return @:privateAccess new PbrMaterial();
 		return @:privateAccess new PbrMaterial();
 	}
 	}
 
 
+	public static function set() {
+		MaterialSetup.current = new PbrMaterialSetup();
+	}
+
 }
 }

+ 37 - 3
h3d/parts/GpuParticles.hx

@@ -29,6 +29,12 @@ enum GpuEmitMode {
 		A cone, parametrized with emitAngle and emitDistance
 		A cone, parametrized with emitAngle and emitDistance
 	**/
 	**/
 	Cone;
 	Cone;
+
+	/**
+		A disc, emit in one direction
+	**/
+	Disc;
+
 	/**
 	/**
 		The GpuParticles specified volumeBounds
 		The GpuParticles specified volumeBounds
 	**/
 	**/
@@ -125,6 +131,7 @@ class GpuPartGroup {
 	public var emitAngle(default,set) : Float 	= 1.5;
 	public var emitAngle(default,set) : Float 	= 1.5;
 	public var emitSync(default, set) : Float	= 0;
 	public var emitSync(default, set) : Float	= 0;
 	public var emitDelay(default, set) : Float	= 0;
 	public var emitDelay(default, set) : Float	= 0;
+	public var emitOnBorder(default, set) : Bool = false;
 
 
 
 
 	public var clipBounds : Bool				= false;
 	public var clipBounds : Bool				= false;
@@ -177,6 +184,7 @@ class GpuPartGroup {
 	inline function set_emitAngle(v) { needRebuild = true; return emitAngle = v; }
 	inline function set_emitAngle(v) { needRebuild = true; return emitAngle = v; }
 	inline function set_emitSync(v) { needRebuild = true; return emitSync = v; }
 	inline function set_emitSync(v) { needRebuild = true; return emitSync = v; }
 	inline function set_emitDelay(v) { needRebuild = true; return emitDelay = v; }
 	inline function set_emitDelay(v) { needRebuild = true; return emitDelay = v; }
+	inline function set_emitOnBorder(v) { needRebuild = true; return emitOnBorder = v; }
 	inline function set_rotInit(v) { needRebuild = true; return rotInit = v; }
 	inline function set_rotInit(v) { needRebuild = true; return rotInit = v; }
 	inline function set_rotSpeed(v) { needRebuild = true; return rotSpeed = v; }
 	inline function set_rotSpeed(v) { needRebuild = true; return rotSpeed = v; }
 	inline function set_rotSpeedRand(v) { needRebuild = true; return rotSpeedRand = v; }
 	inline function set_rotSpeedRand(v) { needRebuild = true; return rotSpeedRand = v; }
@@ -283,6 +291,17 @@ class GpuPartGroup {
 				bounds.addPos(d, d, -zmin);
 				bounds.addPos(d, d, -zmin);
 			}
 			}
 
 
+		case Disc:
+			var start = emitStartDist + emitDist;
+			var maxDist = speedMin * (1 + speedIncr * life) * life - gravity * life * life;
+			var phi = emitAngle + Math.PI/2;
+			var zMinMax = maxDist * Math.sin(phi);
+			var xyMinMax = Math.abs(maxDist * Math.cos(phi)) + start;
+
+			bounds.addPos(0, 0, zMinMax);
+			bounds.addPos(xyMinMax, xyMinMax, 0);
+			bounds.addPos(-xyMinMax, -xyMinMax, 0);
+
 		case ParentBounds, VolumeBounds, CameraBounds:
 		case ParentBounds, VolumeBounds, CameraBounds:
 			var d = speed * (1 + speedIncr * life) * life;
 			var d = speed * (1 + speedIncr * life) * life;
 			var max = (1 + emitDist) * 0.5;
 			var max = (1 + emitDist) * 0.5;
@@ -313,7 +332,6 @@ class GpuPartGroup {
 
 
 		switch( g.emitMode ) {
 		switch( g.emitMode ) {
 		case Point:
 		case Point:
-
 			v.x = srand();
 			v.x = srand();
 			v.y = srand();
 			v.y = srand();
 			v.z = srand();
 			v.z = srand();
@@ -335,6 +353,22 @@ class GpuPartGroup {
 			p.y = v.y * r;
 			p.y = v.y * r;
 			p.z = v.z * r;
 			p.z = v.z * r;
 
 
+		case Disc:
+
+			var phi = g.emitAngle;
+			var theta = rand() * Math.PI * 2;
+			if( g.emitAngle < 0 ) phi += Math.PI;
+
+			v.x = Math.sin(phi) * Math.cos(theta);
+			v.y = Math.sin(phi) * Math.sin(theta);
+			v.z = Math.cos(phi);
+
+			v.normalizeFast();
+			var r = g.emitStartDist + g.emitDist * (emitOnBorder ? 1 : Math.sqrt(rand()));
+			p.x = Math.cos(theta) * r;
+			p.y = Math.sin(theta) * r;
+			p.z = 0;
+
 		case ParentBounds, VolumeBounds, CameraBounds:
 		case ParentBounds, VolumeBounds, CameraBounds:
 
 
 			var max = 1 + g.emitDist;
 			var max = 1 + g.emitDist;
@@ -514,12 +548,12 @@ class GpuParticles extends h3d.scene.MultiMaterial {
 			material.mainPass.culling = None;
 			material.mainPass.culling = None;
 			material.mainPass.depthWrite = false;
 			material.mainPass.depthWrite = false;
 			material.blendMode = Alpha;
 			material.blendMode = Alpha;
-			if( this.material == null ) this.material = material;
 			if( g.material != null ) {
 			if( g.material != null ) {
 				material.props = g.getMaterialProps();
 				material.props = g.getMaterialProps();
 				material.name = g.name;
 				material.name = g.name;
 			}
 			}
 		}
 		}
+		if( this.material == null ) this.material = material;
 		material.mainPass.addShader(g.pshader);
 		material.mainPass.addShader(g.pshader);
 		if( index == null )
 		if( index == null )
 			index = groups.length;
 			index = groups.length;
@@ -613,7 +647,7 @@ class GpuParticles extends h3d.scene.MultiMaterial {
 				case VolumeBounds, CameraBounds:
 				case VolumeBounds, CameraBounds:
 					ebounds = volumeBounds;
 					ebounds = volumeBounds;
 					if( ebounds == null ) ebounds = volumeBounds = h3d.col.Bounds.fromValues( -1, -1, -1, 2, 2, 2 );
 					if( ebounds == null ) ebounds = volumeBounds = h3d.col.Bounds.fromValues( -1, -1, -1, 2, 2, 2 );
-				case Cone, Point:
+				case Cone, Point, Disc:
 					ebounds = null;
 					ebounds = null;
 				}
 				}
 			}
 			}

+ 1 - 2
h3d/pass/Base.hx

@@ -21,8 +21,7 @@ class Base {
 	public function dispose() {
 	public function dispose() {
 	}
 	}
 
 
-	public function draw( passes : Object ) {
-		return passes;
+	public function draw( passes : PassList ) {
 	}
 	}
 
 
 }
 }

+ 1 - 1
h3d/pass/Blur.hx

@@ -135,7 +135,7 @@ class Blur extends ScreenFx<h3d.shader.Blur> {
 
 
 		var isCube = src.flags.has(Cube);
 		var isCube = src.flags.has(Cube);
 		var faceCount = isCube ? 6 : 1;
 		var faceCount = isCube ? 6 : 1;
-		var tmp = ctx.textures.allocTarget(src.name+"BlurTmp", src.width, src.height, false, src.format, isCube ? [Target, Cube] : [Target]);
+		var tmp = ctx.textures.allocTarget(src.name+"BlurTmp", src.width, src.height, false, src.format, isCube);
 
 
 		shader.Quality = values.length;
 		shader.Quality = values.length;
 		shader.values = values;
 		shader.values = values;

+ 24 - 56
h3d/pass/Default.hx

@@ -6,13 +6,11 @@ class Default extends Base {
 
 
 	var manager : ShaderManager;
 	var manager : ShaderManager;
 	var globals(get, never) : hxsl.Globals;
 	var globals(get, never) : hxsl.Globals;
-	var cachedBuffer : h3d.shader.Buffers;
 	var shaderCount : Int = 1;
 	var shaderCount : Int = 1;
 	var textureCount : Int = 1;
 	var textureCount : Int = 1;
 	var shaderIdMap : Array<Int>;
 	var shaderIdMap : Array<Int>;
 	var textureIdMap : Array<Int>;
 	var textureIdMap : Array<Int>;
 	var sortPasses = true;
 	var sortPasses = true;
-	var logEnable(get, never) : Bool;
 
 
 	inline function get_globals() return manager.globals;
 	inline function get_globals() return manager.globals;
 
 
@@ -38,26 +36,18 @@ class Default extends Base {
 		initGlobals();
 		initGlobals();
 	}
 	}
 
 
-	inline function get_logEnable() {
-		#if debug
-		return ctx.engine.driver.logEnable;
-		#else
-		return false;
-		#end
-	}
-
 	function getOutputs() : Array<hxsl.Output> {
 	function getOutputs() : Array<hxsl.Output> {
 		return [Value("output.color")];
 		return [Value("output.color")];
 	}
 	}
 
 
 	override function compileShader( p : h3d.mat.Pass ) {
 	override function compileShader( p : h3d.mat.Pass ) {
-		var o = new Object();
+		var o = @:privateAccess new h3d.pass.PassObject();
 		o.pass = p;
 		o.pass = p;
-		setupShaders(o);
-		return manager.compileShaders(o.shaders);
+		setupShaders(new h3d.pass.PassList(o));
+		return manager.compileShaders(o.shaders, p.batchMode);
 	}
 	}
 
 
-	function processShaders( p : Object, shaders : hxsl.ShaderList ) {
+	function processShaders( p : h3d.pass.PassObject, shaders : hxsl.ShaderList ) {
 		var p = ctx.extraShaders;
 		var p = ctx.extraShaders;
 		while( p != null ) {
 		while( p != null ) {
 			shaders = ctx.allocShaderList(p.s, shaders);
 			shaders = ctx.allocShaderList(p.s, shaders);
@@ -67,10 +57,9 @@ class Default extends Base {
 	}
 	}
 
 
 	@:access(h3d.scene)
 	@:access(h3d.scene)
-	function setupShaders( passes : Object ) {
-		var p = passes;
+	function setupShaders( passes : h3d.pass.PassList ) {
 		var lightInit = false;
 		var lightInit = false;
-		while( p != null ) {
+		for( p in passes ) {
 			var shaders = p.pass.getShadersRec();
 			var shaders = p.pass.getShadersRec();
 			shaders = processShaders(p, shaders);
 			shaders = processShaders(p, shaders);
 			if( p.pass.enableLights && ctx.lightSystem != null ) {
 			if( p.pass.enableLights && ctx.lightSystem != null ) {
@@ -80,7 +69,7 @@ class Default extends Base {
 				}
 				}
 				shaders = ctx.lightSystem.computeLight(p.obj, shaders);
 				shaders = ctx.lightSystem.computeLight(p.obj, shaders);
 			}
 			}
-			p.shader = manager.compileShaders(shaders);
+			p.shader = manager.compileShaders(shaders, p.pass.batchMode);
 			p.shaders = shaders;
 			p.shaders = shaders;
 			var t = p.shader.fragment.textures;
 			var t = p.shader.fragment.textures;
 			if( t == null )
 			if( t == null )
@@ -89,59 +78,44 @@ class Default extends Base {
 				var t : h3d.mat.Texture = manager.getParamValue(t, shaders, true);
 				var t : h3d.mat.Texture = manager.getParamValue(t, shaders, true);
 				p.texture = t == null ? 0 : t.id;
 				p.texture = t == null ? 0 : t.id;
 			}
 			}
-			p = p.next;
 		}
 		}
 	}
 	}
 
 
-	function uploadParams() {
-		manager.fillParams(cachedBuffer, ctx.drawPass.shader, ctx.drawPass.shaders);
-		ctx.engine.uploadShaderBuffers(cachedBuffer, Params);
-		ctx.engine.uploadShaderBuffers(cachedBuffer, Textures);
-	}
-
 	inline function log( str : String ) {
 	inline function log( str : String ) {
 		ctx.engine.driver.log(str);
 		ctx.engine.driver.log(str);
 	}
 	}
 
 
-	function drawObject( p : Object ) {
+	function drawObject( p : h3d.pass.PassObject ) {
 		ctx.drawPass = p;
 		ctx.drawPass = p;
 		ctx.engine.selectMaterial(p.pass);
 		ctx.engine.selectMaterial(p.pass);
 		@:privateAccess p.obj.draw(ctx);
 		@:privateAccess p.obj.draw(ctx);
 	}
 	}
 
 
 	@:access(h3d.scene)
 	@:access(h3d.scene)
-	override function draw( passes : Object ) {
+	override function draw( passes : h3d.pass.PassList ) {
+		if( passes.isEmpty() )
+			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);
-		var p = passes;
-		var shaderStart = shaderCount, textureStart = textureCount;
-		while( p != null ) {
-			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++;
-			p = p.next;
-		}
-		if( sortPasses )
-			passes = haxe.ds.ListSort.sortSingleLinked(passes, function(o1:Object, o2:Object) {
+		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];
 				var d = shaderIdMap[o1.shader.id] - shaderIdMap[o2.shader.id];
 				if( d != 0 ) return d;
 				if( d != 0 ) return d;
 				return textureIdMap[o1.texture] - textureIdMap[o2.texture];
 				return textureIdMap[o1.texture] - textureIdMap[o2.texture];
 			});
 			});
-		ctx.uploadParams = uploadParams;
-		var p = passes;
-		var buf = cachedBuffer, prevShader = null;
-		var drawTri = 0, drawCalls = 0, shaderSwitches = 0;
-		if( ctx.engine.driver.logEnable ) {
-			if( logEnable ) log("Pass " + (passes == null ? "???" : passes.pass.name) + " start");
-			drawTri = ctx.engine.drawTriangles;
-			drawCalls = ctx.engine.drawCalls;
-			shaderSwitches = ctx.engine.shaderSwitches;
 		}
 		}
-		while( p != null ) {
-			if( logEnable ) log("Render " + p.obj + "." + p.pass.name);
+		ctx.currentManager = manager;
+		var buf = ctx.shaderBuffers, prevShader = null;
+		for( p in passes ) {
 			globalModelView = p.obj.absPos;
 			globalModelView = p.obj.absPos;
 			if( p.shader.hasGlobal(globalModelViewInverse_id.toInt()) )
 			if( p.shader.hasGlobal(globalModelViewInverse_id.toInt()) )
 				globalModelViewInverse = p.obj.getInvPos();
 				globalModelViewInverse = p.obj.getInvPos();
@@ -149,7 +123,7 @@ class Default extends Base {
 				prevShader = p.shader;
 				prevShader = p.shader;
 				ctx.engine.selectShader(p.shader);
 				ctx.engine.selectShader(p.shader);
 				if( buf == null )
 				if( buf == null )
-					buf = cachedBuffer = new h3d.shader.Buffers(p.shader);
+					buf = ctx.shaderBuffers = new h3d.shader.Buffers(p.shader);
 				else
 				else
 					buf.grow(p.shader);
 					buf.grow(p.shader);
 				manager.fillGlobals(buf, p.shader);
 				manager.fillGlobals(buf, p.shader);
@@ -162,14 +136,8 @@ class Default extends Base {
 				ctx.engine.uploadShaderBuffers(buf, Buffers);
 				ctx.engine.uploadShaderBuffers(buf, Buffers);
 			}
 			}
 			drawObject(p);
 			drawObject(p);
-			p = p.next;
-		}
-		if( logEnable ) {
-			log("Pass " + (passes == null ? "???" : passes.pass.name) + " end");
-			log("\t" + (ctx.engine.drawTriangles - drawTri) + " tri " + (ctx.engine.drawCalls - drawCalls) + " calls " + (ctx.engine.shaderSwitches - shaderSwitches) + " shaders");
 		}
 		}
 		ctx.nextPass();
 		ctx.nextPass();
-		return passes;
 	}
 	}
 
 
 }
 }

+ 0 - 1
h3d/pass/DefaultShadowMap.hx

@@ -31,7 +31,6 @@ class DefaultShadowMap extends DirShadowMap {
 		ctx.setGlobalID(shadowColorId, color);
 		ctx.setGlobalID(shadowColorId, color);
 		ctx.setGlobalID(shadowPowerId, power);
 		ctx.setGlobalID(shadowPowerId, power);
 		ctx.setGlobalID(shadowBiasId, bias);
 		ctx.setGlobalID(shadowBiasId, bias);
-		return passes;
 	}
 	}
 
 
 }
 }

+ 9 - 24
h3d/pass/DirShadowMap.hx

@@ -90,7 +90,7 @@ class DirShadowMap extends Shadows {
 			var cameraBounds = new h3d.col.Bounds();
 			var cameraBounds = new h3d.col.Bounds();
 			for( pt in ctx.camera.getFrustumCorners() ) {
 			for( pt in ctx.camera.getFrustumCorners() ) {
 				pt.transform(camera.mcam);
 				pt.transform(camera.mcam);
-				cameraBounds.addPos(pt.x, pt.y, pt.z);				
+				cameraBounds.addPos(pt.x, pt.y, pt.z);
 			}
 			}
 			cameraBounds.zMin = bounds.zMin;
 			cameraBounds.zMin = bounds.zMin;
 			bounds.intersection(bounds, cameraBounds);
 			bounds.intersection(bounds, cameraBounds);
@@ -109,7 +109,7 @@ class DirShadowMap extends Shadows {
 		cameraViewProj = getShadowProj();
 		cameraViewProj = getShadowProj();
 	}
 	}
 
 
-	function syncShader(texture) {
+	override function syncShader(texture) {
 		dshader.shadowMap = texture;
 		dshader.shadowMap = texture;
 		dshader.shadowMapChannel = format == h3d.mat.Texture.nativeFormat ? PackedFloat : R;
 		dshader.shadowMapChannel = format == h3d.mat.Texture.nativeFormat ? PackedFloat : R;
 		dshader.shadowBias = bias;
 		dshader.shadowBias = bias;
@@ -167,30 +167,16 @@ class DirShadowMap extends Shadows {
 		if( staticTexture != null ) staticTexture.dispose();
 		if( staticTexture != null ) staticTexture.dispose();
 		staticTexture = new h3d.mat.Texture(size, size, [Target], format);
 		staticTexture = new h3d.mat.Texture(size, size, [Target], format);
 		staticTexture.uploadPixels(pixels);
 		staticTexture.uploadPixels(pixels);
+		staticTexture.name = "defaultDirShadowMap";
 		syncShader(staticTexture);
 		syncShader(staticTexture);
 		return true;
 		return true;
 	}
 	}
 
 
 	override function draw( passes ) {
 	override function draw( passes ) {
+		if( !filterPasses(passes) )
+			return;
 
 
-		if( !ctx.computingStatic )
-			switch( mode ) {
-			case None:
-				return passes;
-			case Dynamic:
-				// nothing
-			case Static, Mixed:
-				if( staticTexture == null || staticTexture.isDisposed() )
-					staticTexture = h3d.mat.Texture.fromColor(0xFFFFFF);
-				if( mode == Static ) {
-					syncShader(staticTexture);
-					return passes;
-				}
-			}
-
-		passes = filterPasses(passes);
-
-		var texture = ctx.textures.allocTarget("shadowMap", size, size, false, format);
+		var texture = ctx.textures.allocTarget("dirShadowMap", size, size, false, format);
 		if( customDepth && (depth == null || depth.width != size || depth.height != size || depth.isDisposed()) ) {
 		if( customDepth && (depth == null || depth.width != size || depth.height != size || depth.isDisposed()) ) {
 			if( depth != null ) depth.dispose();
 			if( depth != null ) depth.dispose();
 			depth = new h3d.mat.DepthBuffer(size, size);
 			depth = new h3d.mat.DepthBuffer(size, size);
@@ -216,12 +202,12 @@ class DirShadowMap extends Shadows {
 
 
 		ctx.engine.pushTarget(texture);
 		ctx.engine.pushTarget(texture);
 		ctx.engine.clear(0xFFFFFF, 1);
 		ctx.engine.clear(0xFFFFFF, 1);
-		passes = super.draw(passes);
+		super.draw(passes);
 		if( border != null ) border.render();
 		if( border != null ) border.render();
 		ctx.engine.popTarget();
 		ctx.engine.popTarget();
 
 
 		if( mode == Mixed && !ctx.computingStatic ) {
 		if( mode == Mixed && !ctx.computingStatic ) {
-			var merge = ctx.textures.allocTarget("shadowMap", size, size, false, format);
+			var merge = ctx.textures.allocTarget("mergedDirShadowMap", size, size, false, format);
 			mergePass.shader.texA = texture;
 			mergePass.shader.texA = texture;
 			mergePass.shader.texB = staticTexture;
 			mergePass.shader.texB = staticTexture;
 			ctx.engine.pushTarget(merge);
 			ctx.engine.pushTarget(merge);
@@ -234,10 +220,9 @@ class DirShadowMap extends Shadows {
 			blur.apply(ctx, texture);
 			blur.apply(ctx, texture);
 
 
 		syncShader(texture);
 		syncShader(texture);
-		return passes;
 	}
 	}
 
 
-	override function computeStatic( passes : h3d.pass.Object ) {
+	override function computeStatic( passes : h3d.pass.PassList ) {
 		if( mode != Static && mode != Mixed )
 		if( mode != Static && mode != Mixed )
 			return;
 			return;
 		draw(passes);
 		draw(passes);

+ 7 - 14
h3d/pass/HardwarePick.hx

@@ -63,17 +63,13 @@ class HardwarePick extends Default {
 		fixedColor.colorID.setColor(0xFF000000 | (++colorID));
 		fixedColor.colorID.setColor(0xFF000000 | (++colorID));
 	}
 	}
 
 
-	override function draw(passes:Object) {
+	override function draw(passes:h3d.pass.PassList) {
 
 
-		var cur = passes;
-		while( cur != null ) {
+		for( cur in passes ) @:privateAccess {
 			// force all materials to use opaque blend
 			// force all materials to use opaque blend
-			@:privateAccess {
-				var mask = h3d.mat.Pass.blendSrc_mask | h3d.mat.Pass.blendDst_mask | h3d.mat.Pass.blendAlphaDst_mask | h3d.mat.Pass.blendAlphaSrc_mask | h3d.mat.Pass.blendOp_mask | h3d.mat.Pass.blendAlphaOp_mask;
-				cur.pass.bits &= ~mask;
-				cur.pass.bits |= material.bits & mask;
-			}
-			cur = cur.next;
+			var mask = h3d.mat.Pass.blendSrc_mask | h3d.mat.Pass.blendDst_mask | h3d.mat.Pass.blendAlphaDst_mask | h3d.mat.Pass.blendAlphaSrc_mask | h3d.mat.Pass.blendOp_mask | h3d.mat.Pass.blendAlphaOp_mask;
+			cur.pass.bits &= ~mask;
+			cur.pass.bits |= material.bits & mask;
 		}
 		}
 		colorID = 0;
 		colorID = 0;
 
 
@@ -82,12 +78,11 @@ 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);
-		var passes = super.draw(passes);
+		super.draw(passes);
 		ctx.extraShaders = null;
 		ctx.extraShaders = null;
 		ctx.engine.popTarget();
 		ctx.engine.popTarget();
 
 
-		var cur = passes;
-		while( cur != null ) {
+		for( cur in passes ) {
 			// will reset bits
 			// will reset bits
 			cur.pass.blendSrc = cur.pass.blendSrc;
 			cur.pass.blendSrc = cur.pass.blendSrc;
 			cur.pass.blendDst = cur.pass.blendDst;
 			cur.pass.blendDst = cur.pass.blendDst;
@@ -96,13 +91,11 @@ class HardwarePick extends Default {
 			cur.pass.blendAlphaDst = cur.pass.blendAlphaDst;
 			cur.pass.blendAlphaDst = cur.pass.blendAlphaDst;
 			cur.pass.blendAlphaOp = cur.pass.blendAlphaOp;
 			cur.pass.blendAlphaOp = cur.pass.blendAlphaOp;
 			cur.pass.colorMask = cur.pass.colorMask;
 			cur.pass.colorMask = cur.pass.colorMask;
-			cur = cur.next;
 		}
 		}
 
 
 		ctx.engine.clear(null, null, 0);
 		ctx.engine.clear(null, null, 0);
 		var pix = texOut.capturePixels();
 		var pix = texOut.capturePixels();
 		pickedIndex = (pix.getPixel(pix.width>>1, pix.height>>1) & 0xFFFFFF) - 1;
 		pickedIndex = (pix.getPixel(pix.width>>1, pix.height>>1) & 0xFFFFFF) - 1;
-		return passes;
 	}
 	}
 
 
 }
 }

+ 1 - 1
h3d/pass/Outline.hx

@@ -18,7 +18,7 @@ class Outline extends ScreenFx<h3d.shader.Outline2D> {
 	public function apply(ctx : h3d.impl.RenderContext, src : h3d.mat.Texture, ?output : h3d.mat.Texture) {
 	public function apply(ctx : h3d.impl.RenderContext, src : h3d.mat.Texture, ?output : h3d.mat.Texture) {
 		if (output == null)
 		if (output == null)
 			output = src;
 			output = src;
-		var tmp = ctx.textures.allocTarget(src.name + "OutlineTmp", src.width, src.height, false, src.format, [Target]);
+		var tmp = ctx.textures.allocTarget(src.name + "OutlineTmp", src.width, src.height, false, src.format);
 		shader.color.setColor(color);
 		shader.color.setColor(color);
 		shader.size.set(size / src.width, size / src.height);
 		shader.size.set(size / src.width, size / src.height);
 		shader.samples = Std.int(Math.max(quality * 100, 1));
 		shader.samples = Std.int(Math.max(quality * 100, 1));

+ 143 - 0
h3d/pass/PassList.hx

@@ -0,0 +1,143 @@
+package h3d.pass;
+
+class PassListIterator {
+	var o : PassObject;
+	public inline function new(o) {
+		this.o = o;
+	}
+	public inline function hasNext() {
+		return o != null;
+	}
+	public inline function next() {
+		var tmp = o;
+		o = @:privateAccess o.next;
+		return tmp;
+	}
+}
+
+@:access(h3d.pass.PassObject)
+class PassList {
+
+	var current : PassObject;
+	var discarded : PassObject;
+	var lastDisc : PassObject;
+
+	public function new(?current) {
+		init(current);
+	}
+
+	/**
+		Set the passes and empty the discarded list
+	**/
+	public inline function init(pass) {
+		current = pass;
+		discarded = lastDisc = null;
+	}
+
+	/**
+		Put back discarded passes into the pass list
+	**/
+	public inline function reset() {
+		if( discarded != null ) {
+			lastDisc.next = current;
+			current = discarded;
+			discarded = lastDisc = null;
+		}
+	}
+
+	/**
+		Save the discarded list, allow to perfom some filters, then call "load" to restore passes
+	**/
+	public inline function save() {
+		return lastDisc;
+	}
+
+	/**
+		load state that was save() before
+	**/
+	public inline function load( p : PassObject ) {
+		if( lastDisc != p ) {
+			lastDisc.next = current;
+			if( p == null ) {
+				current = discarded;
+				discarded = null;
+			} else {
+				current = p.next;
+				p.next = null;
+			}
+			lastDisc = p;
+		}
+	}
+
+	public inline function isEmpty() {
+		return current == null;
+	}
+
+	/**
+		Put all passes into discarded list
+	**/
+	public function clear() {
+		if( current == null )
+			return;
+		if( discarded == null )
+			discarded = current;
+		else
+			lastDisc.next = current;
+		var p = current;
+		while( p.next != null ) p = p.next;
+		lastDisc = p;
+		current = null;
+	}
+
+	public inline function sort( f : PassObject -> PassObject -> Int ) {
+		current = haxe.ds.ListSort.sortSingleLinked(current, f);
+	}
+
+	/**
+		Filter current passes, add results to discarded list
+	**/
+	public inline function filter( f : PassObject -> Bool ) {
+		var head = null;
+		var prev = null;
+		var disc = discarded;
+		var discQueue = lastDisc;
+		var cur = current;
+		while( cur != null ) {
+			if( f(cur) ) {
+				if( head == null )
+					head = prev = cur;
+				else {
+					prev.next = cur;
+					prev = cur;
+				}
+			} else {
+				if( disc == null )
+					disc = discQueue = cur;
+				else {
+					discQueue.next = cur;
+					discQueue = cur;
+				}
+			}
+			cur = cur.next;
+		}
+		if( prev != null )
+			prev.next = null;
+		if( discQueue != null )
+			discQueue.next = null;
+		current = head;
+		discarded = disc;
+		lastDisc = discQueue;
+	}
+
+	public inline function iterator() {
+		return new PassListIterator(current);
+	}
+
+	/**
+		Iterate on all discarded elements, if any
+	**/
+	public inline function getFiltered() {
+		return new PassListIterator(discarded);
+	}
+
+}

+ 4 - 4
h3d/pass/Object.hx → h3d/pass/PassObject.hx

@@ -1,11 +1,11 @@
 package h3d.pass;
 package h3d.pass;
 
 
-class Object {
+class PassObject {
+	@:noCompletion public var next : PassObject;
+	var nextAlloc : PassObject;
 	public var pass : h3d.mat.Pass;
 	public var pass : h3d.mat.Pass;
 	public var obj : h3d.scene.Object;
 	public var obj : h3d.scene.Object;
 	public var index : Int;
 	public var index : Int;
-	public var next : Object;
-	public var nextAlloc : Object;
 
 
 	// cache
 	// cache
 	public var shaders : hxsl.ShaderList;
 	public var shaders : hxsl.ShaderList;
@@ -13,6 +13,6 @@ class Object {
 	public var depth : Float;
 	public var depth : Float;
 	public var texture : Int = 0;
 	public var texture : Int = 0;
 
 
-	public function new() {
+	function new() {
 	}
 	}
 }
 }

+ 27 - 37
h3d/pass/PointShadowMap.hx

@@ -53,7 +53,7 @@ class PointShadowMap extends Shadows {
 		cameraPos = lightCamera.pos;
 		cameraPos = lightCamera.pos;
 	}
 	}
 
 
-	function syncShader(texture) {
+	override function syncShader(texture) {
 		var absPos = light.getAbsPos();
 		var absPos = light.getAbsPos();
 		var pointLight = cast(light, h3d.scene.pbr.PointLight);
 		var pointLight = cast(light, h3d.scene.pbr.PointLight);
 		pshader.shadowMap = texture;
 		pshader.shadowMap = texture;
@@ -90,7 +90,8 @@ class PointShadowMap extends Shadows {
 			return false;
 			return false;
 
 
 		if( staticTexture != null ) staticTexture.dispose();
 		if( staticTexture != null ) staticTexture.dispose();
-			staticTexture = new h3d.mat.Texture(size, size, [Target, Cube], format);
+		staticTexture = new h3d.mat.Texture(size, size, [Target, Cube], format);
+		staticTexture.name = "staticTexture";
 
 
 		for(i in 0 ... 6){
 		for(i in 0 ... 6){
 			var len = buffer.readInt32();
 			var len = buffer.readInt32();
@@ -101,59 +102,49 @@ class PointShadowMap extends Shadows {
 		return true;
 		return true;
 	}
 	}
 
 
-	function createDefaultShadowMap(){
-		var tex = new h3d.mat.Texture(1,1, [Cube,Target], format);
-		for(i in 0 ... 6){
+	override function createDefaultShadowMap() {
+		var tex = new h3d.mat.Texture(1,1, [Target,Cube], format);
+		tex.name = "defaultCubeShadowMap";
+		for(i in 0 ... 6)
 			tex.clear(0xFFFFFF, i);
 			tex.clear(0xFFFFFF, i);
-		}
 		return tex;
 		return tex;
 	}
 	}
 
 
 	override function draw( passes ) {
 	override function draw( passes ) {
+		if( !filterPasses(passes) )
+			return;
 
 
-		if( !ctx.computingStatic ){
-			switch( mode ) {
-			case None:
-				return passes;
-			case Dynamic:
-				// nothing
-			case Mixed:
-				if( staticTexture == null || staticTexture.isDisposed() )
-					staticTexture = createDefaultShadowMap();
-			case Static:
-				if( staticTexture == null || staticTexture.isDisposed() )
-					staticTexture = createDefaultShadowMap();
-				updateCamera();
-				syncShader(staticTexture);
-				return passes;
-			}
-		}
-
-		var passes = filterPasses(passes);
+		var pointLight = cast(light, h3d.scene.pbr.PointLight);
+		var absPos = light.getAbsPos();
+		var sp = new h3d.col.Sphere(absPos.tx, absPos.ty, absPos.tz, pointLight.range);
+		cullPasses(passes,function(col) return col.inSphere(sp));
 
 
-		var texture = ctx.textures.allocTarget("pointShadowMap", size, size, false, format, [Target, Cube]);
+		var texture = ctx.textures.allocTarget("pointShadowMap", size, size, false, format, true);
 		if(depth == null || depth.width != size || depth.height != size || depth.isDisposed() ) {
 		if(depth == null || depth.width != size || depth.height != size || depth.isDisposed() ) {
-				if( depth != null ) depth.dispose();
-				depth = new h3d.mat.DepthBuffer(size, size);
+			if( depth != null ) depth.dispose();
+			depth = new h3d.mat.DepthBuffer(size, size);
 		}
 		}
 		texture.depthBuffer = depth;
 		texture.depthBuffer = depth;
 
 
 		var validBakedTexture = (staticTexture != null && staticTexture.width == texture.width);
 		var validBakedTexture = (staticTexture != null && staticTexture.width == texture.width);
 		var merge : h3d.mat.Texture = null;
 		var merge : h3d.mat.Texture = null;
 		if( mode == Mixed && !ctx.computingStatic && validBakedTexture)
 		if( mode == Mixed && !ctx.computingStatic && validBakedTexture)
-			merge = ctx.textures.allocTarget("pointShadowMap", size, size, false, format, [Target, Cube]);
+			merge = ctx.textures.allocTarget("mergedPointShadowMap", size, size, false, format, true);
 
 
-		for(i in 0 ... 6){
-			var pointLight = cast(light, h3d.scene.pbr.PointLight);
+		lightCamera.pos.set(absPos.tx, absPos.ty, absPos.tz);
+		lightCamera.zFar = pointLight.range;
+		lightCamera.zNear = pointLight.zNear;
 
 
-			var absPos = light.getAbsPos();
-			lightCamera.setCubeMap(i, new h3d.Vector(absPos.tx, absPos.ty, absPos.tz));
-			lightCamera.zFar = pointLight.range;
+		for( i in 0...6 ) {
+			lightCamera.setCubeMap(i);
 			lightCamera.update();
 			lightCamera.update();
 
 
 			ctx.engine.pushTarget(texture, i);
 			ctx.engine.pushTarget(texture, i);
 			ctx.engine.clear(0xFFFFFF, 1);
 			ctx.engine.clear(0xFFFFFF, 1);
-			passes = super.draw(passes);
+			var save = passes.save();
+			cullPasses(passes, function(col) return col.inFrustum(lightCamera.frustum));
+			super.draw(passes);
+			passes.load(save);
 			ctx.engine.popTarget();
 			ctx.engine.popTarget();
 		}
 		}
 
 
@@ -173,7 +164,6 @@ class PointShadowMap extends Shadows {
 		}
 		}
 
 
 		syncShader(texture);
 		syncShader(texture);
-		return passes;
 	}
 	}
 
 
 	function updateCamera(){
 	function updateCamera(){
@@ -182,7 +172,7 @@ class PointShadowMap extends Shadows {
 		lightCamera.update();
 		lightCamera.update();
 	}
 	}
 
 
-	override function computeStatic( passes : h3d.pass.Object ) {
+	override function computeStatic( passes : h3d.pass.PassList ) {
 		if( mode != Static && mode != Mixed )
 		if( mode != Static && mode != Mixed )
 			return;
 			return;
 		draw(passes);
 		draw(passes);

+ 39 - 15
h3d/pass/ShaderManager.hx

@@ -24,7 +24,7 @@ class ShaderManager {
 	}
 	}
 
 
 	@:noDebug
 	@:noDebug
-	function fillRec( v : Dynamic, type : hxsl.Ast.Type, out : h3d.shader.Buffers.ShaderBufferData, pos : Int ) {
+	function fillRec( v : Dynamic, type : hxsl.Ast.Type, out : #if hl hl.BytesAccess<hl.F32> #else h3d.shader.Buffers.ShaderBufferData #end, pos : Int ) {
 		switch( type ) {
 		switch( type ) {
 		case TInt:
 		case TInt:
 			out[pos] = v;
 			out[pos] = v;
@@ -163,6 +163,14 @@ class ShaderManager {
 		return "(not found)";
 		return "(not found)";
 	}
 	}
 
 
+	inline function getPtr( data : h3d.shader.Buffers.ShaderBufferData ) {
+		#if hl
+		return (hl.Bytes.getArray((cast data : Array<Single>)) : hl.BytesAccess<hl.F32>);
+		#else
+		return data;
+		#end
+	}
+
 	public inline function getParamValue( p : hxsl.RuntimeShader.AllocParam, shaders : hxsl.ShaderList, opt = false ) : Dynamic {
 	public inline function getParamValue( p : hxsl.RuntimeShader.AllocParam, shaders : hxsl.ShaderList, opt = false ) : Dynamic {
 		if( p.perObjectGlobal != null ) {
 		if( p.perObjectGlobal != null ) {
 			var v : Dynamic = globals.fastGet(p.perObjectGlobal.gid);
 			var v : Dynamic = globals.fastGet(p.perObjectGlobal.gid);
@@ -182,17 +190,18 @@ class ShaderManager {
 	public function fillGlobals( buf : h3d.shader.Buffers, s : hxsl.RuntimeShader ) {
 	public function fillGlobals( buf : h3d.shader.Buffers, s : hxsl.RuntimeShader ) {
 		inline function fill(buf:h3d.shader.Buffers.ShaderBuffers, s:hxsl.RuntimeShader.RuntimeShaderData) {
 		inline function fill(buf:h3d.shader.Buffers.ShaderBuffers, s:hxsl.RuntimeShader.RuntimeShaderData) {
 			var g = s.globals;
 			var g = s.globals;
+			var ptr = getPtr(buf.globals);
 			while( g != null ) {
 			while( g != null ) {
 				var v = globals.fastGet(g.gid);
 				var v = globals.fastGet(g.gid);
 				if( v == null ) {
 				if( v == null ) {
 					if( g.path == "__consts__" ) {
 					if( g.path == "__consts__" ) {
-						fillRec(s.consts, g.type, buf.globals, g.pos);
+						fillRec(s.consts, g.type, ptr, g.pos);
 						g = g.next;
 						g = g.next;
 						continue;
 						continue;
 					}
 					}
 					throw "Missing global value " + g.path;
 					throw "Missing global value " + g.path;
 				}
 				}
-				fillRec(v, g.type, buf.globals, g.pos);
+				fillRec(v, g.type, ptr, g.pos);
 				g = g.next;
 				g = g.next;
 			}
 			}
 		}
 		}
@@ -201,19 +210,34 @@ class ShaderManager {
 	}
 	}
 
 
 	public function fillParams( buf : h3d.shader.Buffers, s : hxsl.RuntimeShader, shaders : hxsl.ShaderList ) {
 	public function fillParams( buf : h3d.shader.Buffers, s : hxsl.RuntimeShader, shaders : hxsl.ShaderList ) {
+		var curInstance = -1;
+		var curInstanceValue = null;
+		inline function getInstance( index : Int ) {
+			if( curInstance == index )
+				return curInstanceValue;
+			var si = shaders;
+			curInstance = index;
+			while( --index > 0 ) si = si.next;
+			curInstanceValue = si.s;
+			return curInstanceValue;
+		}
 		inline function fill(buf:h3d.shader.Buffers.ShaderBuffers, s:hxsl.RuntimeShader.RuntimeShaderData) {
 		inline function fill(buf:h3d.shader.Buffers.ShaderBuffers, s:hxsl.RuntimeShader.RuntimeShaderData) {
 			var p = s.params;
 			var p = s.params;
+			var ptr = getPtr(buf.params);
 			while( p != null ) {
 			while( p != null ) {
-				if( p.type == TFloat && p.perObjectGlobal == null ) {
-					var si = shaders;
-					var n = p.instance;
-					while( --n > 0 ) si = si.next;
-					buf.params[p.pos] = si.s.getParamFloatValue(p.index);
-					p = p.next;
-					continue;
-				}
-				var v : Dynamic = getParamValue(p, shaders);
-				fillRec(v, p.type, buf.params, p.pos);
+				var v : Dynamic;
+				if( p.perObjectGlobal == null ) {
+					if( p.type == TFloat ) {
+						var i = getInstance(p.instance);
+						ptr[p.pos] = i.getParamFloatValue(p.index);
+						p = p.next;
+						continue;
+					}
+					v = getInstance(p.instance).getParamValue(p.index);
+					if( v == null ) throw "Missing param value " + curInstanceValue + "." + p.name;
+				} else
+					v = getParamValue(p, shaders);
+				fillRec(v, p.type, ptr, p.pos);
 				p = p.next;
 				p = p.next;
 			}
 			}
 			var tid = 0;
 			var tid = 0;
@@ -234,11 +258,11 @@ class ShaderManager {
 		fill(buf.fragment, s.fragment);
 		fill(buf.fragment, s.fragment);
 	}
 	}
 
 
-	public function compileShaders( shaders : hxsl.ShaderList ) {
+	public function compileShaders( shaders : hxsl.ShaderList, batchMode : Bool = false ) {
 		globals.resetChannels();
 		globals.resetChannels();
 		for( s in shaders ) s.updateConstants(globals);
 		for( s in shaders ) s.updateConstants(globals);
 		currentOutput.next = shaders;
 		currentOutput.next = shaders;
-		var s = shaderCache.link(currentOutput);
+		var s = shaderCache.link(currentOutput, batchMode);
 		currentOutput.next = null;
 		currentOutput.next = null;
 		return s;
 		return s;
 	}
 	}

+ 50 - 37
h3d/pass/Shadows.hx

@@ -24,7 +24,7 @@ class Shadows extends Default {
 	public function new(light) {
 	public function new(light) {
 		if( format == null ) format = R16F;
 		if( format == null ) format = R16F;
 		if( !h3d.Engine.getCurrent().driver.isSupportedFormat(format) ) format = h3d.mat.Texture.nativeFormat;
 		if( !h3d.Engine.getCurrent().driver.isSupportedFormat(format) ) format = h3d.mat.Texture.nativeFormat;
-		super("shadows");
+		super("shadow");
 		this.light = light;
 		this.light = light;
 		blur = new Blur(5);
 		blur = new Blur(5);
 		blur.quality = 0.5;
 		blur.quality = 0.5;
@@ -80,52 +80,65 @@ class Shadows extends Default {
 		return null;
 		return null;
 	}
 	}
 
 
-	public function computeStatic( passes : h3d.pass.Object ) {
+	public function computeStatic( passes : h3d.pass.PassList ) {
 		throw "Not implemented";
 		throw "Not implemented";
 	}
 	}
 
 
-	function filterPasses( passes : h3d.pass.Object ) : h3d.pass.Object {
-		var isStatic : Bool;
+	function createDefaultShadowMap() {
+		var tex = h3d.mat.Texture.fromColor(0xFFFFFF);
+		tex.name = "defaultShadowMap";
+		return tex;
+	}
+
+	function syncShader( texture : h3d.mat.Texture ) {
+	}
+
+	function filterPasses( passes : h3d.pass.PassList ) {
+		if( !ctx.computingStatic ){
+			switch( mode ) {
+			case None:
+				return false;
+			case Dynamic:
+				// nothing
+			case Mixed:
+				if( staticTexture == null || staticTexture.isDisposed() )
+					staticTexture = createDefaultShadowMap();
+			case Static:
+				if( staticTexture == null || staticTexture.isDisposed() )
+					staticTexture = createDefaultShadowMap();
+				syncShader(staticTexture);
+				return false;
+			}
+		}
 		switch( mode ) {
 		switch( mode ) {
 		case None:
 		case None:
-			return null;
+			passes.clear();
 		case Dynamic:
 		case Dynamic:
-			if( ctx.computingStatic ) return null;
-			return passes;
+			if( ctx.computingStatic ) passes.clear();
 		case Mixed:
 		case Mixed:
-			isStatic = ctx.computingStatic;
+			passes.filter(function(p) return p.pass.isStatic == ctx.computingStatic);
 		case Static:
 		case Static:
-			if( !ctx.computingStatic ) return null;
-			isStatic = true;
+			if( ctx.computingStatic )
+				passes.filter(function(p) return p.pass.isStatic == true);
+			else
+				passes.clear();
 		}
 		}
-		var head = null;
-		var prev = null;
-		var last = null;
-
-		var cur = passes;
-		while( cur != null ) {
-			if( cur.pass.isStatic == isStatic ) {
-				if( head == null )
-					head = prev = cur;
-				else {
-					prev.next = cur;
-					prev = cur;
-				}
-			} else {
-				if( last == null )
-					last = cur;
-				else {
-					last.next = cur;
-					last = cur;
-				}
+		return true;
+	}
+
+	inline function cullPasses( passes : h3d.pass.PassList, f : h3d.col.Collider -> Bool ) {
+		var prevCollider = null;
+		var prevResult = true;
+		passes.filter(function(p) {
+			var col = p.obj.cullingCollider;
+			if( col == null )
+				return true;
+			if( col != prevCollider ) {
+				prevCollider = col;
+				prevResult = f(col);
 			}
 			}
-			cur = cur.next;
-		}
-		if( last != null )
-			last.next = head;
-		if( prev != null )
-			prev.next = null;
-		return head;
+			return prevResult;
+		});
 	}
 	}
 
 
 }
 }

+ 6 - 22
h3d/pass/SpotShadowMap.hx

@@ -50,7 +50,7 @@ class SpotShadowMap extends Shadows {
 		cameraViewProj = getShadowProj();
 		cameraViewProj = getShadowProj();
 	}
 	}
 
 
-	function syncShader(texture) {
+	override function syncShader(texture) {
 		sshader.shadowMap = texture;
 		sshader.shadowMap = texture;
 		sshader.shadowMapChannel = format == h3d.mat.Texture.nativeFormat ? PackedFloat : R;
 		sshader.shadowMapChannel = format == h3d.mat.Texture.nativeFormat ? PackedFloat : R;
 		sshader.shadowBias = bias;
 		sshader.shadowBias = bias;
@@ -89,26 +89,11 @@ class SpotShadowMap extends Shadows {
 	}
 	}
 
 
 	override function draw( passes ) {
 	override function draw( passes ) {
+		if( !filterPasses(passes) )
+			return;
 
 
-		if( !ctx.computingStatic )
-			switch( mode ) {
-			case None:
-				return passes;
-			case Dynamic:
-				// nothing
-			case Mixed:
-				if( staticTexture == null || staticTexture.isDisposed() )
-					staticTexture = h3d.mat.Texture.fromColor(0xFFFFFF);
-			case Static:
-				if( staticTexture == null || staticTexture.isDisposed() )
-					staticTexture = h3d.mat.Texture.fromColor(0xFFFFFF);
-				updateCamera();
-				syncShader(staticTexture);
-				return passes;
-			}
-
-		passes = filterPasses(passes);
 		updateCamera();
 		updateCamera();
+		cullPasses(passes, function(col) return col.inFrustum(lightCamera.frustum));
 
 
 		var texture = ctx.textures.allocTarget("shadowMap", size, size, false, format);
 		var texture = ctx.textures.allocTarget("shadowMap", size, size, false, format);
 		if( customDepth && (depth == null || depth.width != size || depth.height != size || depth.isDisposed()) ) {
 		if( customDepth && (depth == null || depth.width != size || depth.height != size || depth.isDisposed()) ) {
@@ -119,7 +104,7 @@ class SpotShadowMap extends Shadows {
 
 
 		ctx.engine.pushTarget(texture);
 		ctx.engine.pushTarget(texture);
 		ctx.engine.clear(0xFFFFFF, 1);
 		ctx.engine.clear(0xFFFFFF, 1);
-		passes = super.draw(passes);
+		super.draw(passes);
 		if( border != null ) border.render();
 		if( border != null ) border.render();
 		ctx.engine.popTarget();
 		ctx.engine.popTarget();
 
 
@@ -138,7 +123,6 @@ class SpotShadowMap extends Shadows {
 		}
 		}
 
 
 		syncShader(texture);
 		syncShader(texture);
-		return passes;
 	}
 	}
 
 
 	function updateCamera(){
 	function updateCamera(){
@@ -152,7 +136,7 @@ class SpotShadowMap extends Shadows {
 		lightCamera.update();
 		lightCamera.update();
 	}
 	}
 
 
-	override function computeStatic( passes : h3d.pass.Object ) {
+	override function computeStatic( passes : h3d.pass.PassList ) {
 		if( mode != Static && mode != Mixed )
 		if( mode != Static && mode != Mixed )
 			return;
 			return;
 		draw(passes);
 		draw(passes);

+ 5 - 5
h3d/prim/Cylinder.hx

@@ -22,15 +22,15 @@ class Cylinder extends Quads {
 		super(pts);
 		super(pts);
 	}
 	}
 
 
-	override public function addTCoords() {
+	override function addUVs() {
 		uvs = new Array();
 		uvs = new Array();
 		for( s in 0...segs ) {
 		for( s in 0...segs ) {
 			var u = s / segs;
 			var u = s / segs;
 			var u2 = (s + 1) / segs;
 			var u2 = (s + 1) / segs;
-			uvs.push(new UV(u, 1));
-			uvs.push(new UV(u2, 1));
-			uvs.push(new UV(u, 0));
-			uvs.push(new UV(u2, 0));
+			uvs.push(new UV(1-u, 1));
+			uvs.push(new UV(1-u2, 1));
+			uvs.push(new UV(1-u, 0));
+			uvs.push(new UV(1-u2, 0));
 		}
 		}
 	}
 	}
 
 

+ 20 - 0
h3d/prim/Instanced.hx

@@ -3,8 +3,13 @@ package h3d.prim;
 class Instanced extends MeshPrimitive {
 class Instanced extends MeshPrimitive {
 
 
 	public var commands : h3d.impl.InstanceBuffer;
 	public var commands : h3d.impl.InstanceBuffer;
+	public var offset : h3d.col.Sphere;
+	var baseBounds : h3d.col.Bounds;
+	var tmpBounds : h3d.col.Bounds;
 
 
 	public function new() {
 	public function new() {
+		offset = new h3d.col.Sphere();
+		tmpBounds = new h3d.col.Bounds();
 	}
 	}
 
 
 	public function setMesh( m : MeshPrimitive ) {
 	public function setMesh( m : MeshPrimitive ) {
@@ -12,6 +17,7 @@ class Instanced extends MeshPrimitive {
 		if( m.buffer == null ) m.alloc(engine);
 		if( m.buffer == null ) m.alloc(engine);
 		buffer = m.buffer;
 		buffer = m.buffer;
 		indexes = m.indexes;
 		indexes = m.indexes;
+		baseBounds = m.getBounds();
 		if( indexes == null ) indexes = engine.mem.triIndexes;
 		if( indexes == null ) indexes = engine.mem.triIndexes;
 		for( bid in m.bufferCache.keys() ) {
 		for( bid in m.bufferCache.keys() ) {
 			var b = m.bufferCache.get(bid);
 			var b = m.bufferCache.get(bid);
@@ -19,6 +25,20 @@ class Instanced extends MeshPrimitive {
 		}
 		}
 	}
 	}
 
 
+	override function getBounds():h3d.col.Bounds {
+		tmpBounds.load(baseBounds);
+		var r = offset.r;
+		tmpBounds.offset(offset.x, offset.y, offset.z);
+		tmpBounds.xMin -= r;
+		tmpBounds.yMin -= r;
+		tmpBounds.zMin -= r;
+		tmpBounds.xMax += r;
+		tmpBounds.yMax += r;
+		tmpBounds.zMax += r;
+		return tmpBounds;
+	}
+
+	// make public
 	public override function addBuffer( name, buffer, offset = 0 ) {
 	public override function addBuffer( name, buffer, offset = 0 ) {
 		super.addBuffer(name, buffer, offset);
 		super.addBuffer(name, buffer, offset);
 	}
 	}

+ 13 - 11
h3d/prim/MeshPrimitive.hx

@@ -3,8 +3,7 @@ package h3d.prim;
 class MeshPrimitive extends Primitive {
 class MeshPrimitive extends Primitive {
 
 
 	var bufferCache : Map<Int,h3d.Buffer.BufferOffset>;
 	var bufferCache : Map<Int,h3d.Buffer.BufferOffset>;
-	var prevNames : Array<String>;
-	var prevBuffers : h3d.Buffer.BufferOffset;
+	var layouts : Map<Int,h3d.Buffer.BufferOffset>;
 
 
 	function allocBuffer( engine : h3d.Engine, name : String ) {
 	function allocBuffer( engine : h3d.Engine, name : String ) {
 		return null;
 		return null;
@@ -31,17 +30,20 @@ class MeshPrimitive extends Primitive {
 			for( b in bufferCache )
 			for( b in bufferCache )
 				b.dispose();
 				b.dispose();
 		bufferCache = null;
 		bufferCache = null;
-		prevNames = null;
+		layouts = null;
 	}
 	}
 
 
 	function getBuffers( engine : h3d.Engine ) {
 	function getBuffers( engine : h3d.Engine ) {
 		if( bufferCache == null )
 		if( bufferCache == null )
 			bufferCache = new Map();
 			bufferCache = new Map();
-		var names = @:privateAccess engine.driver.getShaderInputNames();
-		if( names == prevNames )
-			return prevBuffers;
-		var buffers = null, prev = null;
-		for( name in names ) {
+		if( layouts == null )
+			layouts = new Map();
+		var inputs = @:privateAccess engine.driver.getShaderInputNames();
+		var buffers = layouts.get(inputs.id);
+		if( buffers != null )
+			return buffers;
+		var prev = null;
+		for( name in inputs.names ) {
 			var id = hxsl.Globals.allocID(name);
 			var id = hxsl.Globals.allocID(name);
 			var b = bufferCache.get(id);
 			var b = bufferCache.get(id);
 			if( b == null ) {
 			if( b == null ) {
@@ -49,7 +51,7 @@ class MeshPrimitive extends Primitive {
 				if( b == null ) throw "Buffer " + name + " is not available";
 				if( b == null ) throw "Buffer " + name + " is not available";
 				bufferCache.set(id, b);
 				bufferCache.set(id, b);
 			}
 			}
-			b.next = null;
+			b = b.clone();
 			if( prev == null ) {
 			if( prev == null ) {
 				buffers = prev = b;
 				buffers = prev = b;
 			} else {
 			} else {
@@ -57,8 +59,8 @@ class MeshPrimitive extends Primitive {
 				prev = b;
 				prev = b;
 			}
 			}
 		}
 		}
-		prevNames = names;
-		return prevBuffers = buffers;
+		layouts.set(inputs.id, buffers);
+		return buffers;
 	}
 	}
 
 
 	override function render( engine : h3d.Engine ) {
 	override function render( engine : h3d.Engine ) {

+ 1 - 1
h3d/prim/Plane2D.hx

@@ -2,7 +2,7 @@ package h3d.prim;
 
 
 class Plane2D extends Primitive {
 class Plane2D extends Primitive {
 
 
-	function new() {
+	public function new() {
 	}
 	}
 
 
 	override function triCount() {
 	override function triCount() {

+ 4 - 1
h3d/prim/Quads.hx

@@ -7,6 +7,9 @@ class Quads extends Primitive {
 	var uvs : Array<UV>;
 	var uvs : Array<UV>;
 	var normals : Array<Point>;
 	var normals : Array<Point>;
 
 
+	/**
+	* You have to pass vertices in this order: top left, top right, bottom left, bottom right
+	*/
 	public function new( pts, ?uvs, ?normals ) {
 	public function new( pts, ?uvs, ?normals ) {
 		this.pts = pts;
 		this.pts = pts;
 		this.uvs = uvs;
 		this.uvs = uvs;
@@ -39,7 +42,7 @@ class Quads extends Primitive {
 	/**
 	/**
 	* Warning : This will splice four basic uv value but can provoke aliasing problems.
 	* Warning : This will splice four basic uv value but can provoke aliasing problems.
 	*/
 	*/
-	public function addTCoords() {
+	public function addUVs() {
 		uvs = [];
 		uvs = [];
 		var a = new UV(0, 1);
 		var a = new UV(0, 1);
 		var b = new UV(1, 1);
 		var b = new UV(1, 1);

+ 13 - 0
h3d/scene/Box.hx

@@ -18,6 +18,19 @@ class Box extends Graphics {
 		if( !depth ) material.mainPass.depth(true, Always);
 		if( !depth ) material.mainPass.depth(true, Always);
 	}
 	}
 
 
+	public override function clone( ?o : h3d.scene.Object ) : h3d.scene.Object {
+		var b = o == null ? new Box(color, bounds.clone(), material.mainPass.depthWrite, null) : cast o;
+		super.clone(b);
+		b.bounds = bounds.clone();
+		b.prevXMin = prevXMin;
+		b.prevYMin = prevYMin;
+		b.prevZMin = prevZMin;
+		b.prevXMax = prevXMax;
+		b.prevYMax = prevYMax;
+		b.prevZMax = prevZMax;
+		return b;
+	}
+
 	override function getLocalCollider() {
 	override function getLocalCollider() {
 		return null;
 		return null;
 	}
 	}

+ 2 - 0
h3d/scene/CameraController.hx

@@ -76,6 +76,8 @@ class CameraController extends h3d.scene.Object {
 		targetPos.set(r, Math.atan2(pos.y, pos.x), Math.acos(pos.z / r));
 		targetPos.set(r, Math.atan2(pos.y, pos.x), Math.acos(pos.z / r));
 		targetPos.x *= targetOffset.w;
 		targetPos.x *= targetOffset.w;
 
 
+		curOffset.w = scene.camera.fovY;
+
 		if( !animate )
 		if( !animate )
 			toTarget();
 			toTarget();
 		else
 		else

+ 1 - 1
h3d/scene/Light.hx

@@ -6,7 +6,7 @@ class Light extends Object {
 	var objectDistance : Float; // used internaly
 	var objectDistance : Float; // used internaly
 	@:noCompletion public var next : Light; // used internaly (public to allow sorting)
 	@:noCompletion public var next : Light; // used internaly (public to allow sorting)
 
 
-	@:s var cullingDistance : Float = 1e10;
+	@:s var cullingDistance : Float = -1;
 	@:s public var priority : Int = 0;
 	@:s public var priority : Int = 0;
 	public var color(get, set) : h3d.Vector;
 	public var color(get, set) : h3d.Vector;
 	public var enableSpecular(get, set) : Bool;
 	public var enableSpecular(get, set) : Bool;

+ 16 - 65
h3d/scene/LightSystem.hx

@@ -1,44 +1,24 @@
 package h3d.scene;
 package h3d.scene;
 
 
-@:access(h3d.scene.Light)
-@:access(h3d.scene.Object.absPos)
-@:access(h3d.scene.RenderContext.lights)
 class LightSystem {
 class LightSystem {
 
 
-	public var maxLightsPerObject = 6;
-	var globals : hxsl.Globals;
-	var ambientShader : hxsl.Shader;
-	var lightCount : Int;
-	var ctx : h3d.scene.RenderContext;
+	public var drawPasses : Int = 0;
+	public var ambientLight(default,null) : h3d.Vector;
 	public var shadowLight : h3d.scene.Light;
 	public var shadowLight : h3d.scene.Light;
-	public var ambientLight : h3d.Vector;
-	public var perPixelLighting : Bool = true;
 
 
-	/**
-		In the additive lighting model (by default), the lights are added after the ambient.
-		In the new non additive ligthning model, the lights will be modulated against the ambient, so an ambient of 1 will reduce lights intensities to 0.
-	**/
-	public var additiveLighting(get, set) : Bool;
+	var lightCount : Int;
+	var ctx : RenderContext;
 
 
 	public function new() {
 	public function new() {
-		ambientLight = new h3d.Vector(0.5, 0.5, 0.5);
-		ambientShader = new h3d.shader.AmbientLight();
-		additiveLighting = true;
-	}
-
-	function get_additiveLighting() {
-		return Std.instance(ambientShader,h3d.shader.AmbientLight).additive;
+		ambientLight = new h3d.Vector(1,1,1);
 	}
 	}
 
 
-	function set_additiveLighting(b) {
-		return Std.instance(ambientShader,h3d.shader.AmbientLight).additive = b;
+	public function initGlobals( globals : hxsl.Globals ) {
 	}
 	}
 
 
-	public function initLights( ctx : h3d.scene.RenderContext ) {
-		lightCount = 0;
-		this.ctx = ctx;
+	function cullLights() @:privateAccess  {
+		// remove lights which cullingDistance is outside of frustum
 		var l = ctx.lights, prev : h3d.scene.Light = null;
 		var l = ctx.lights, prev : h3d.scene.Light = null;
-		var frustum = new h3d.col.Frustum(ctx.camera.m);
 		var s = new h3d.col.Sphere();
 		var s = new h3d.col.Sphere();
 		while( l != null ) {
 		while( l != null ) {
 			s.x = l.absPos._41;
 			s.x = l.absPos._41;
@@ -46,7 +26,7 @@ class LightSystem {
 			s.z = l.absPos._43;
 			s.z = l.absPos._43;
 			s.r = l.cullingDistance;
 			s.r = l.cullingDistance;
 
 
-			if(!ctx.computingStatic && !frustum.hasSphere(s) ) {
+			if( l.cullingDistance > 0 && !ctx.computingStatic && !ctx.camera.frustum.hasSphere(s) ) {
 				if( prev == null )
 				if( prev == null )
 					ctx.lights = l.next;
 					ctx.lights = l.next;
 				else
 				else
@@ -60,12 +40,16 @@ class LightSystem {
 			prev = l;
 			prev = l;
 			l = l.next;
 			l = l.next;
 		}
 		}
-		if( lightCount <= maxLightsPerObject )
-			ctx.lights = haxe.ds.ListSort.sortSingleLinked(ctx.lights, sortLight);
+	}
+
+	public function initLights( ctx : h3d.scene.RenderContext ) @:privateAccess {
+		lightCount = 0;
+		this.ctx = ctx;
+		cullLights();
 		if( shadowLight == null || !shadowLight.allocated) {
 		if( shadowLight == null || !shadowLight.allocated) {
 			var l = ctx.lights;
 			var l = ctx.lights;
 			while( l != null ) {
 			while( l != null ) {
-				var dir = @:privateAccess l.getShadowDirection();
+				var dir = l.getShadowDirection();
 				if( dir != null ) {
 				if( dir != null ) {
 					shadowLight = l;
 					shadowLight = l;
 					break;
 					break;
@@ -75,40 +59,7 @@ class LightSystem {
 		}
 		}
 	}
 	}
 
 
-	public function initGlobals( globals : hxsl.Globals ) {
-		globals.set("global.ambientLight", ambientLight);
-		globals.set("global.perPixelLighting", perPixelLighting);
-	}
-
-	function sortLight( l1 : h3d.scene.Light, l2 : h3d.scene.Light ) {
-		var p = l1.priority - l2.priority;
-		if( p != 0 ) return -p;
-		return l1.objectDistance < l2.objectDistance ? -1 : 1;
-	}
-
 	public function computeLight( obj : h3d.scene.Object, shaders : hxsl.ShaderList ) : hxsl.ShaderList {
 	public function computeLight( obj : h3d.scene.Object, shaders : hxsl.ShaderList ) : hxsl.ShaderList {
-		if( lightCount > maxLightsPerObject ) {
-			var l = ctx.lights;
-			while( l != null ) {
-				if( obj.lightCameraCenter )
-					l.objectDistance = hxd.Math.distanceSq(l.absPos._41 - ctx.camera.target.x, l.absPos._42 - ctx.camera.target.y, l.absPos._43 - ctx.camera.target.z);
-				else
-					l.objectDistance = hxd.Math.distanceSq(l.absPos._41 - obj.absPos._41, l.absPos._42 - obj.absPos._42, l.absPos._43 - obj.absPos._43);
-				l = l.next;
-			}
-			ctx.lights = haxe.ds.ListSort.sortSingleLinked(ctx.lights, sortLight);
-		}
-		inline function add( s : hxsl.Shader ) {
-			shaders = ctx.allocShaderList(s, shaders);
-		}
-		add(ambientShader);
-		var l = ctx.lights;
-		var i = 0;
-		while( l != null ) {
-			if( i++ == maxLightsPerObject ) break;
-			add(l.shader);
-			l = l.next;
-		}
 		return shaders;
 		return shaders;
 	}
 	}
 
 

+ 245 - 0
h3d/scene/MeshBatch.hx

@@ -0,0 +1,245 @@
+package h3d.scene;
+
+private class BatchData {
+
+	public var count : Int;
+	public var buffer : h3d.Buffer;
+	public var data : hxd.FloatBuffer;
+	public var params : hxsl.RuntimeShader.AllocParam;
+	public var shader : hxsl.BatchShader;
+	public var shaders : Array<hxsl.Shader>;
+	public var pass : h3d.mat.Pass;
+	public var next : BatchData;
+
+	public function new() {
+	}
+
+}
+
+/**
+	h3d.scene.MeshBatch allows to draw multiple meshed in a single draw call.
+	See samples/MeshBatch.hx for an example.
+**/
+class MeshBatch extends Mesh {
+
+	var instanced : h3d.prim.Instanced;
+	var curInstances : Int = 0;
+	var maxInstances : Int = 0;
+	var lastInstances : Int = 0;
+	var shaderInstances : Int = 0;
+	var commandBytes = haxe.io.Bytes.alloc(20);
+	var dataBuffer : h3d.Buffer;
+	var dataPasses : BatchData;
+	var modelViewID = hxsl.Globals.allocID("global.modelView");
+	var modelViewInverseID = hxsl.Globals.allocID("global.modelViewInverse");
+
+	/**
+		Set if shader list or shader constants has changed, before calling begin()
+	**/
+	public var shadersChanged = true;
+
+	public function new( primitive, ?material, ?parent ) {
+		instanced = new h3d.prim.Instanced();
+		instanced.setMesh(primitive);
+		super(instanced, material, parent);
+		for( p in this.material.getPasses() )
+			@:privateAccess p.batchMode = true;
+		commandBytes.setInt32(0, primitive.indexes == null ? primitive.triCount() * 3 : primitive.indexes.count);
+	}
+
+	override function onRemove() {
+		super.onRemove();
+		cleanPasses();
+	}
+
+	override function dispose() {
+		super.dispose();
+		cleanPasses();
+	}
+
+	function cleanPasses() {
+		while( dataPasses != null ) {
+			dataPasses.pass.removeShader(dataPasses.shader);
+			dataPasses.buffer.dispose();
+			dataPasses = dataPasses.next;
+		}
+		if( instanced.commands != null ) {
+			instanced.commands.dispose();
+			instanced.commands = null;
+			lastInstances = 0;
+		}
+		shaderInstances = 0;
+		shadersChanged = true;
+	}
+
+	function initShadersMapping() {
+		var scene = getScene();
+		cleanPasses();
+		shaderInstances = maxInstances;
+		for( p in material.getPasses() ) @:privateAccess {
+			var ctx = scene.renderer.getPassByName(p.name);
+			if( ctx == null ) throw "Could't find renderer pass "+p.name;
+
+			var manager = cast(ctx,h3d.pass.Default).manager;
+			var shaders = p.getShadersRec();
+			var rt = manager.compileShaders(shaders,false);
+
+			var shader = manager.shaderCache.makeBatchShader(rt);
+
+			var b = new BatchData();
+			b.count = rt.vertex.paramsSize + rt.fragment.paramsSize;
+			b.params = rt.fragment.params == null ? null : rt.fragment.params.clone();
+
+			var hd = b.params;
+			while( hd != null ) {
+				hd.pos += rt.vertex.paramsSize << 2;
+				hd = hd.next;
+			}
+
+			if( b.params == null )
+				b.params = rt.vertex.params;
+			else if( rt.vertex != null ) {
+				var vl = rt.vertex.params.clone();
+				var hd = vl;
+				while( vl.next != null ) vl = vl.next;
+				vl.next = b.params;
+				b.params = hd;
+			}
+
+			var tot = b.count * shaderInstances;
+			b.shader = shader;
+			b.pass = p;
+			b.shaders = [null/*link shader*/];
+			b.buffer = new h3d.Buffer(tot,4,[UniformBuffer,Dynamic]);
+			b.data = new hxd.FloatBuffer(tot * 4);
+			b.next = dataPasses;
+			dataPasses = b;
+
+			var sl = shaders;
+			while( sl != null ) {
+				b.shaders.push(sl.s);
+				sl = sl.next;
+			}
+
+			shader.Batch_Count = tot;
+			shader.Batch_Buffer = b.buffer;
+			shader.constBits = tot;
+			shader.updateConstants(null);
+		}
+		// add batch shaders
+		var p = dataPasses;
+		while( p != null ) {
+			p.pass.addShader(p.shader);
+			p = p.next;
+		}
+	}
+
+	public function begin( maxCount : Int ) {
+		if( maxCount > shaderInstances )
+			shadersChanged = true;
+		curInstances = 0;
+		maxInstances = maxCount;
+		if( shadersChanged ) {
+			initShadersMapping();
+			shadersChanged = false;
+		}
+	}
+
+	function syncData( data : BatchData ) {
+		var p = data.params;
+		var buf = data.data;
+		var shaders = data.shaders;
+		var startPos = data.count * curInstances * 4;
+		while( p != null ) {
+			var pos = startPos + p.pos;
+			inline function addMatrix(m:h3d.Matrix) {
+				buf[pos++] = m._11;
+				buf[pos++] = m._21;
+				buf[pos++] = m._31;
+				buf[pos++] = m._41;
+				buf[pos++] = m._12;
+				buf[pos++] = m._22;
+				buf[pos++] = m._32;
+				buf[pos++] = m._42;
+				buf[pos++] = m._13;
+				buf[pos++] = m._23;
+				buf[pos++] = m._33;
+				buf[pos++] = m._43;
+				buf[pos++] = m._14;
+				buf[pos++] = m._24;
+				buf[pos++] = m._34;
+				buf[pos++] = m._44;
+			}
+			if( p.perObjectGlobal != null ) {
+				if( p.perObjectGlobal.gid == modelViewID ) {
+					addMatrix(absPos);
+				} else if( p.perObjectGlobal.gid == modelViewInverseID ) {
+					addMatrix(getInvPos());
+				} else
+					throw "Unsupported global param "+p.perObjectGlobal.path;
+				p = p.next;
+				continue;
+			}
+			var curShader = shaders[p.instance];
+			switch( p.type ) {
+			case TVec(size, _):
+				var v : h3d.Vector = curShader.getParamValue(p.index);
+				switch( size ) {
+				case 2:
+					buf[pos++] = v.x;
+					buf[pos++] = v.y;
+				case 3:
+					buf[pos++] = v.x;
+					buf[pos++] = v.y;
+					buf[pos++] = v.z;
+				default:
+					buf[pos++] = v.x;
+					buf[pos++] = v.y;
+					buf[pos++] = v.z;
+					buf[pos++] = v.w;
+				}
+			case TFloat:
+				buf[pos++] = curShader.getParamFloatValue(p.index);
+			case TMat4:
+				var m : h3d.Matrix = curShader.getParamValue(p.index);
+				addMatrix(m);
+			default:
+				throw "Unsupported batch type "+p.type;
+			}
+			p = p.next;
+		}
+	}
+
+	public function emitInstance() {
+		if( curInstances == maxInstances ) throw "Too many instances";
+		syncPos();
+		var p = dataPasses;
+		while( p != null ) {
+			syncData(p);
+			p = p.next;
+		}
+		curInstances++;
+	}
+
+	override function sync(ctx:RenderContext) {
+		super.sync(ctx);
+		if( curInstances == 0 ) return;
+		var p = dataPasses;
+		while( p != null ) {
+			p.buffer.uploadVector(p.data,0,curInstances * p.count);
+			p = p.next;
+		}
+		if( curInstances != lastInstances ) {
+			if( instanced.commands != null ) instanced.commands.dispose();
+			commandBytes.setInt32(4,curInstances);
+			instanced.commands = new h3d.impl.InstanceBuffer(1,commandBytes);
+			lastInstances = curInstances;
+		}
+	}
+
+	override function emit(ctx:RenderContext) {
+		if( curInstances == 0 ) return;
+		super.emit(ctx);
+	}
+
+}

+ 10 - 6
h3d/scene/Object.hx

@@ -13,8 +13,8 @@ package h3d.scene;
 	public var FIgnoreBounds = 0x200;
 	public var FIgnoreBounds = 0x200;
 	public var FIgnoreCollide = 0x400;
 	public var FIgnoreCollide = 0x400;
 	public var FIgnoreParentTransform = 0x800;
 	public var FIgnoreParentTransform = 0x800;
-	public inline function new() {
-		this = 0;
+	public inline function new(value) {
+		this = value;
 	}
 	}
 	public inline function toInt() return this;
 	public inline function toInt() return this;
 	public inline function has(f:ObjectFlags) return this & f.toInt() != 0;
 	public inline function has(f:ObjectFlags) return this & f.toInt() != 0;
@@ -147,6 +147,9 @@ class Object implements hxd.impl.Serializable {
 	**/
 	**/
 	public var lightCameraCenter(get, set) : Bool;
 	public var lightCameraCenter(get, set) : Bool;
 
 
+
+	public var cullingCollider : h3d.col.Collider;
+
 	var absPos : h3d.Matrix;
 	var absPos : h3d.Matrix;
 	var invPos : h3d.Matrix;
 	var invPos : h3d.Matrix;
 	var qRot : h3d.Quat;
 	var qRot : h3d.Quat;
@@ -157,7 +160,7 @@ class Object implements hxd.impl.Serializable {
 		Create a new empty object, and adds it to the parent object if not null.
 		Create a new empty object, and adds it to the parent object if not null.
 	**/
 	**/
 	public function new( ?parent : Object ) {
 	public function new( ?parent : Object ) {
-		flags = new ObjectFlags();
+		flags = new ObjectFlags(0);
 		absPos = new h3d.Matrix();
 		absPos = new h3d.Matrix();
 		absPos.identity();
 		absPos.identity();
 		x = 0; y = 0; z = 0; scaleX = 1; scaleY = 1; scaleZ = 1;
 		x = 0; y = 0; z = 0; scaleX = 1; scaleY = 1; scaleZ = 1;
@@ -622,9 +625,10 @@ class Object implements hxd.impl.Serializable {
 		if( follow != null ) {
 		if( follow != null ) {
 			follow.syncPos();
 			follow.syncPos();
 			if( followPositionOnly ) {
 			if( followPositionOnly ) {
-				absPos.tx += follow.absPos.tx;
-				absPos.ty += follow.absPos.ty;
-				absPos.tz += follow.absPos.tz;
+				absPos.multiply3x4inline(absPos, parent.absPos);
+				absPos.tx = x + follow.absPos.tx;
+				absPos.ty = y + follow.absPos.ty;
+				absPos.tz = z + follow.absPos.tz;
 			} else
 			} else
 				absPos.multiply3x4(absPos, follow.absPos);
 				absPos.multiply3x4(absPos, follow.absPos);
 		} else if( parent != null && !ignoreParentTransform )
 		} else if( parent != null && !ignoreParentTransform )

+ 27 - 18
h3d/scene/RenderContext.hx

@@ -1,5 +1,4 @@
 package h3d.scene;
 package h3d.scene;
-import h3d.pass.Object in ObjectPass;
 
 
 private class SharedGlobal {
 private class SharedGlobal {
 	public var gid : Int;
 	public var gid : Int;
@@ -14,26 +13,29 @@ class RenderContext extends h3d.impl.RenderContext {
 
 
 	public var camera : h3d.Camera;
 	public var camera : h3d.Camera;
 	public var scene : Scene;
 	public var scene : Scene;
-	public var drawPass : ObjectPass;
+	public var drawPass : h3d.pass.PassObject;
 	public var pbrLightPass : h3d.mat.Pass;
 	public var pbrLightPass : h3d.mat.Pass;
 	public var computingStatic : Bool;
 	public var computingStatic : Bool;
 
 
 	var sharedGlobals : Array<SharedGlobal>;
 	var sharedGlobals : Array<SharedGlobal>;
 	public var lightSystem : h3d.scene.LightSystem;
 	public var lightSystem : h3d.scene.LightSystem;
-	public var uploadParams : Void -> Void;
 	public var extraShaders : hxsl.ShaderList;
 	public var extraShaders : hxsl.ShaderList;
 	public var visibleFlag : Bool;
 	public var visibleFlag : Bool;
+	public var shaderBuffers : h3d.shader.Buffers;
 
 
-	var pool : ObjectPass;
-	var firstAlloc : ObjectPass;
+	var allocPool : h3d.pass.PassObject;
+	var allocFirst : h3d.pass.PassObject;
 	var cachedShaderList : Array<hxsl.ShaderList>;
 	var cachedShaderList : Array<hxsl.ShaderList>;
+	var cachedPassObjects : Array<Renderer.PassObjects>;
 	var cachedPos : Int;
 	var cachedPos : Int;
-	var passes : ObjectPass;
+	var passes : h3d.pass.PassObject;
 	var lights : Light;
 	var lights : Light;
+	var currentManager : h3d.pass.ShaderManager;
 
 
 	public function new() {
 	public function new() {
 		super();
 		super();
 		cachedShaderList = [];
 		cachedShaderList = [];
+		cachedPassObjects = [];
 	}
 	}
 
 
 	@:access(h3d.mat.Pass)
 	@:access(h3d.mat.Pass)
@@ -51,7 +53,6 @@ class RenderContext extends h3d.impl.RenderContext {
 		drawPass = null;
 		drawPass = null;
 		passes = null;
 		passes = null;
 		lights = null;
 		lights = null;
-		uploadParams = null;
 		cachedPos = 0;
 		cachedPos = 0;
 		visibleFlag = true;
 		visibleFlag = true;
 		time += elapsedTime;
 		time += elapsedTime;
@@ -84,14 +85,14 @@ class RenderContext extends h3d.impl.RenderContext {
 		sharedGlobals.push(new SharedGlobal(gid, value));
 		sharedGlobals.push(new SharedGlobal(gid, value));
 	}
 	}
 
 
-	public function emitPass( pass : h3d.mat.Pass, obj : h3d.scene.Object ) {
-		var o = pool;
+	public function emitPass( pass : h3d.mat.Pass, obj : h3d.scene.Object ) @:privateAccess {
+		var o = allocPool;
 		if( o == null ) {
 		if( o == null ) {
-			o = new ObjectPass();
-			o.nextAlloc = firstAlloc;
-			firstAlloc = o;
+			o = new h3d.pass.PassObject();
+			o.nextAlloc = allocFirst;
+			allocFirst = o;
 		} else
 		} else
-			pool = o.nextAlloc;
+			allocPool = o.nextAlloc;
 		o.pass = pass;
 		o.pass = pass;
 		o.obj = obj;
 		o.obj = obj;
 		o.next = passes;
 		o.next = passes;
@@ -115,12 +116,17 @@ class RenderContext extends h3d.impl.RenderContext {
 		lights = l;
 		lights = l;
 	}
 	}
 
 
+	public function uploadParams() {
+		currentManager.fillParams(shaderBuffers, drawPass.shader, drawPass.shaders);
+		engine.uploadShaderBuffers(shaderBuffers, Params);
+		engine.uploadShaderBuffers(shaderBuffers, Textures);
+	}
+
 	public function done() {
 	public function done() {
 		drawPass = null;
 		drawPass = null;
-		uploadParams = null;
 		// move passes to pool, and erase data
 		// move passes to pool, and erase data
-		var p = firstAlloc;
-		while( p != null ) {
+		var p = allocFirst;
+		while( p != null && p != allocPool ) {
 			p.obj = null;
 			p.obj = null;
 			p.pass = null;
 			p.pass = null;
 			p.shader = null;
 			p.shader = null;
@@ -128,9 +134,12 @@ class RenderContext extends h3d.impl.RenderContext {
 			p.next = null;
 			p.next = null;
 			p.index = 0;
 			p.index = 0;
 			p.texture = 0;
 			p.texture = 0;
-			p = p.nextAlloc;
+			p = @:privateAccess p.nextAlloc;
 		}
 		}
-		pool = firstAlloc;
+		// one pooled object was not used this frame, let's gc unused one by one
+		if( allocPool != null )
+			allocFirst = @:privateAccess allocFirst.nextAlloc;
+		allocPool = allocFirst;
 		for( c in cachedShaderList ) {
 		for( c in cachedShaderList ) {
 			c.s = null;
 			c.s = null;
 			c.next = null;
 			c.next = null;

+ 13 - 16
h3d/scene/Renderer.hx

@@ -2,11 +2,10 @@ package h3d.scene;
 
 
 class PassObjects {
 class PassObjects {
 	public var name : String;
 	public var name : String;
-	public var passes : h3d.pass.Object;
+	public var passes : h3d.pass.PassList;
 	public var rendered : Bool;
 	public var rendered : Bool;
-	public function new(name, passes) {
-		this.name = name;
-		this.passes = passes;
+	public function new() {
+		passes = new h3d.pass.PassList();
 	}
 	}
 }
 }
 
 
@@ -24,6 +23,7 @@ class Renderer extends hxd.impl.AnyProps {
 	var defaultPass : h3d.pass.Base;
 	var defaultPass : h3d.pass.Base;
 	var passObjects : SMap<PassObjects>;
 	var passObjects : SMap<PassObjects>;
 	var allPasses : Array<h3d.pass.Base>;
 	var allPasses : Array<h3d.pass.Base>;
+	var emptyPasses = new h3d.pass.PassList();
 	var ctx : RenderContext;
 	var ctx : RenderContext;
 	var hasSetTarget = false;
 	var hasSetTarget = false;
 
 
@@ -81,20 +81,17 @@ class Renderer extends hxd.impl.AnyProps {
 	}
 	}
 
 
 	@:access(h3d.scene.Object)
 	@:access(h3d.scene.Object)
-	function depthSort( passes : h3d.pass.Object, frontToBack = false ) {
-		var p = passes;
+	function depthSort( passes : h3d.pass.PassList, frontToBack = false ) {
 		var cam = ctx.camera.m;
 		var cam = ctx.camera.m;
-		while( p != null ) {
+		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;
 			var w = p.obj.absPos._41 * cam._14 + p.obj.absPos._42 * cam._24 + p.obj.absPos._43 * cam._34 + cam._44;
 			var w = p.obj.absPos._41 * cam._14 + p.obj.absPos._42 * cam._24 + p.obj.absPos._43 * cam._34 + cam._44;
 			p.depth = z / w;
 			p.depth = z / w;
-			p = p.next;
-		}
-		if( frontToBack ) {
-			return haxe.ds.ListSort.sortSingleLinked(passes, function(p1, p2) return p1.depth > p2.depth ? 1 : -1);
-		} else {
-			return haxe.ds.ListSort.sortSingleLinked(passes, function(p1, p2) return p1.depth > p2.depth ? -1 : 1);
 		}
 		}
+		if( frontToBack )
+			passes.sort(function(p1, p2) return p1.depth > p2.depth ? 1 : -1);
+		else
+			passes.sort(function(p1, p2) return p1.depth > p2.depth ? -1 : 1);
 	}
 	}
 
 
 	inline function clear( ?color, ?depth, ?stencil ) {
 	inline function clear( ?color, ?depth, ?stencil ) {
@@ -134,15 +131,15 @@ class Renderer extends hxd.impl.AnyProps {
 
 
 	function get( name : String ) {
 	function get( name : String ) {
 		var p = passObjects.get(name);
 		var p = passObjects.get(name);
-		if( p == null ) return null;
+		if( p == null ) return emptyPasses;
 		p.rendered = true;
 		p.rendered = true;
 		return p.passes;
 		return p.passes;
 	}
 	}
 
 
 	function getSort( name : String, front2Back = false ) {
 	function getSort( name : String, front2Back = false ) {
 		var p = passObjects.get(name);
 		var p = passObjects.get(name);
-		if( p == null ) return null;
-		p.passes = depthSort(p.passes, front2Back);
+		if( p == null ) return emptyPasses;
+		depthSort(p.passes, front2Back);
 		p.rendered = true;
 		p.rendered = true;
 		return p.passes;
 		return p.passes;
 	}
 	}

+ 25 - 20
h3d/scene/Scene.hx

@@ -296,9 +296,9 @@ class Scene extends Object implements h3d.IDrawable implements hxd.SceneEvents.I
 		ctx.lightSystem = null;
 		ctx.lightSystem = null;
 
 
 		var found = null;
 		var found = null;
-		var passes = @:privateAccess ctx.passes;
+		var passes = new h3d.pass.PassList(@:privateAccess ctx.passes);
 
 
-		if( passes != null ) {
+		if( !passes.isEmpty() ) {
 			var p = hardwarePass;
 			var p = hardwarePass;
 			if( p == null )
 			if( p == null )
 				hardwarePass = p = new h3d.pass.HardwarePick();
 				hardwarePass = p = new h3d.pass.HardwarePick();
@@ -306,14 +306,13 @@ class Scene extends Object implements h3d.IDrawable implements hxd.SceneEvents.I
 			p.pickX = pixelX;
 			p.pickX = pixelX;
 			p.pickY = pixelY;
 			p.pickY = pixelY;
 			p.setContext(ctx);
 			p.setContext(ctx);
-			@:privateAccess ctx.passes = passes = p.draw(passes);
-			if( p.pickedIndex >= 0 ) {
-				while( p.pickedIndex > 0 ) {
-					p.pickedIndex--;
-					passes = passes.next;
-				}
-				found = passes.obj;
-			}
+			p.draw(passes);
+			if( p.pickedIndex >= 0 )
+				for( po in passes )
+					if( p.pickedIndex-- == 0 ) {
+						found = po.obj;
+						break;
+					}
 		}
 		}
 
 
 		ctx.done();
 		ctx.done();
@@ -393,6 +392,7 @@ class Scene extends Object implements h3d.IDrawable implements hxd.SceneEvents.I
 		// group by pass implementation
 		// group by pass implementation
 		var curPass = ctx.passes;
 		var curPass = ctx.passes;
 		var passes = [];
 		var passes = [];
+		var passIndex = -1;
 		while( curPass != null ) {
 		while( curPass != null ) {
 			var passId = curPass.pass.passId;
 			var passId = curPass.pass.passId;
 			var p = curPass, prev = null;
 			var p = curPass, prev = null;
@@ -401,7 +401,14 @@ class Scene extends Object implements h3d.IDrawable implements hxd.SceneEvents.I
 				p = p.next;
 				p = p.next;
 			}
 			}
 			prev.next = null;
 			prev.next = null;
-			passes.push(new Renderer.PassObjects(curPass.pass.name,curPass));
+			var pobjs = ctx.cachedPassObjects[++passIndex];
+			if( pobjs == null ) {
+				pobjs = new Renderer.PassObjects();
+				ctx.cachedPassObjects[passIndex] = pobjs;
+			}
+			pobjs.name = curPass.pass.name;
+			pobjs.passes.init(curPass);
+			passes.push(pobjs);
 			curPass = p;
 			curPass = p;
 		}
 		}
 
 
@@ -413,16 +420,9 @@ class Scene extends Object implements h3d.IDrawable implements hxd.SceneEvents.I
 		// check that passes have been rendered
 		// check that passes have been rendered
 		#if debug
 		#if debug
 		if( !ctx.computingStatic && checkPasses)
 		if( !ctx.computingStatic && checkPasses)
-			for( p in passes ) {
-				if( !p.rendered ) {
+			for( p in passes )
+				if( !p.rendered )
 					trace("Pass " + p.name+" has not been rendered : don't know how to handle.");
 					trace("Pass " + p.name+" has not been rendered : don't know how to handle.");
-					var o = p.passes;
-					while( o != null ) {
-						trace(" used by " + o.obj.name == null ? "" + o.obj : o.obj.name);
-						o = o.next;
-					}
-				}
-			}
 		#end
 		#end
 
 
 		if( camera.rightHanded )
 		if( camera.rightHanded )
@@ -432,6 +432,11 @@ class Scene extends Object implements h3d.IDrawable implements hxd.SceneEvents.I
 		ctx.scene = null;
 		ctx.scene = null;
 		ctx.camera = null;
 		ctx.camera = null;
 		ctx.engine = null;
 		ctx.engine = null;
+		for( i in 0...passIndex ) {
+			var p = ctx.cachedPassObjects[i];
+			p.name = null;
+			p.passes.init(null);
+		}
 	}
 	}
 
 
 	/**
 	/**

+ 1 - 1
h3d/scene/World.hx

@@ -562,7 +562,7 @@ class World extends Object {
 		super.syncRec(ctx);
 		super.syncRec(ctx);
 		// don't do in sync() since animations in our world might affect our chunks
 		// don't do in sync() since animations in our world might affect our chunks
 		for( c in allChunks ) {
 		for( c in allChunks ) {
-			c.root.visible = c.bounds.inFrustum(ctx.camera.m);
+			c.root.visible = c.bounds.inFrustum(ctx.camera.frustum);
 			if( c.root.visible ) {
 			if( c.root.visible ) {
 				c.lastFrame = ctx.frame;
 				c.lastFrame = ctx.frame;
 				initChunk(c);
 				initChunk(c);

+ 2 - 2
h3d/scene/DirLight.hx → h3d/scene/fwd/DirLight.hx

@@ -1,4 +1,4 @@
-package h3d.scene;
+package h3d.scene.fwd;
 
 
 class DirLight extends Light {
 class DirLight extends Light {
 
 
@@ -28,7 +28,7 @@ class DirLight extends Light {
 	}
 	}
 
 
 	override function getShadowDirection() : h3d.Vector {
 	override function getShadowDirection() : h3d.Vector {
-		return absPos.front();	
+		return absPos.front();
 	}
 	}
 
 
 	override function emit(ctx) {
 	override function emit(ctx) {

+ 74 - 0
h3d/scene/fwd/LightSystem.hx

@@ -0,0 +1,74 @@
+package h3d.scene.fwd;
+
+class LightSystem extends h3d.scene.LightSystem {
+
+	public var maxLightsPerObject = 6;
+	var globals : hxsl.Globals;
+	var ambientShader : hxsl.Shader;
+	public var perPixelLighting : Bool = true;
+
+	/**
+		In the additive lighting model (by default), the lights are added after the ambient.
+		In the new non additive ligthning model, the lights will be modulated against the ambient, so an ambient of 1 will reduce lights intensities to 0.
+	**/
+	public var additiveLighting(get, set) : Bool;
+
+	public function new() {
+		super();
+		ambientLight.set(0.5, 0.5, 0.5);
+		ambientShader = new h3d.shader.AmbientLight();
+		additiveLighting = true;
+	}
+
+	function get_additiveLighting() {
+		return Std.instance(ambientShader,h3d.shader.AmbientLight).additive;
+	}
+
+	function set_additiveLighting(b) {
+		return Std.instance(ambientShader,h3d.shader.AmbientLight).additive = b;
+	}
+
+	override function initLights(ctx) {
+		super.initLights(ctx);
+		if( lightCount <= maxLightsPerObject )
+			@:privateAccess ctx.lights = haxe.ds.ListSort.sortSingleLinked(ctx.lights, sortLight);
+	}
+
+	override function initGlobals( globals : hxsl.Globals ) {
+		globals.set("global.ambientLight", ambientLight);
+		globals.set("global.perPixelLighting", perPixelLighting);
+	}
+
+	function sortLight( l1 : h3d.scene.Light, l2 : h3d.scene.Light ) {
+		var p = l1.priority - l2.priority;
+		if( p != 0 ) return -p;
+		return @:privateAccess (l1.objectDistance < l2.objectDistance ? -1 : 1);
+	}
+
+	override function computeLight( obj : h3d.scene.Object, shaders : hxsl.ShaderList ) : hxsl.ShaderList @:privateAccess {
+		if( lightCount > maxLightsPerObject ) {
+			var l = ctx.lights;
+			while( l != null ) {
+				if( obj.lightCameraCenter )
+					l.objectDistance = hxd.Math.distanceSq(l.absPos._41 - ctx.camera.target.x, l.absPos._42 - ctx.camera.target.y, l.absPos._43 - ctx.camera.target.z);
+				else
+					l.objectDistance = hxd.Math.distanceSq(l.absPos._41 - obj.absPos._41, l.absPos._42 - obj.absPos._42, l.absPos._43 - obj.absPos._43);
+				l = l.next;
+			}
+			ctx.lights = haxe.ds.ListSort.sortSingleLinked(ctx.lights, sortLight);
+		}
+		inline function add( s : hxsl.Shader ) {
+			shaders = ctx.allocShaderList(s, shaders);
+		}
+		add(ambientShader);
+		var l = ctx.lights;
+		var i = 0;
+		while( l != null ) {
+			if( i++ == maxLightsPerObject ) break;
+			add(l.shader);
+			l = l.next;
+		}
+		return shaders;
+	}
+
+}

+ 1 - 1
h3d/scene/PointLight.hx → h3d/scene/fwd/PointLight.hx

@@ -1,4 +1,4 @@
-package h3d.scene;
+package h3d.scene.fwd;
 
 
 class PointLight extends Light {
 class PointLight extends Light {
 
 

+ 4 - 6
h3d/scene/DefaultRenderer.hx → h3d/scene/fwd/Renderer.hx

@@ -1,4 +1,4 @@
-package h3d.scene;
+package h3d.scene.fwd;
 
 
 private typedef SMap<T> = #if flash haxe.ds.UnsafeStringMap<T> #else Map<String,T> #end
 private typedef SMap<T> = #if flash haxe.ds.UnsafeStringMap<T> #else Map<String,T> #end
 
 
@@ -20,10 +20,9 @@ class DepthPass extends h3d.pass.Default {
 		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);
-		passes = super.draw(passes);
+		super.draw(passes);
 		ctx.engine.popTarget();
 		ctx.engine.popTarget();
 		ctx.setGlobalID(depthMapId, { texture : texture });
 		ctx.setGlobalID(depthMapId, { texture : texture });
-		return passes;
 	}
 	}
 
 
 }
 }
@@ -45,15 +44,14 @@ class NormalPass extends h3d.pass.Default {
 		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);
-		passes = super.draw(passes);
+		super.draw(passes);
 		ctx.engine.popTarget();
 		ctx.engine.popTarget();
 		ctx.setGlobalID(normalMapId, texture);
 		ctx.setGlobalID(normalMapId, texture);
-		return passes;
 	}
 	}
 
 
 }
 }
 
 
-class DefaultRenderer extends Renderer {
+class Renderer extends h3d.scene.Renderer {
 
 
 	var def(get, never) : h3d.pass.Base;
 	var def(get, never) : h3d.pass.Base;
 	public var depth : h3d.pass.Base = new DepthPass();
 	public var depth : h3d.pass.Base = new DepthPass();

+ 24 - 0
h3d/scene/pbr/Decal.hx

@@ -0,0 +1,24 @@
+package h3d.scene.pbr;
+
+class Decal extends Mesh {
+
+	public function new( primitive, ?material, ?parent ) {
+		super(primitive, material, parent);
+	}
+
+	override function sync( ctx : RenderContext ) {
+		super.sync(ctx);
+
+		var shader = material.mainPass.getShader(h3d.shader.pbr.VolumeDecal.DecalPBR);
+		if( shader != null )
+			syncDecalPBR(shader);
+	}
+
+	function syncDecalPBR( shader : h3d.shader.pbr.VolumeDecal.DecalPBR ) {
+		shader.normal = getAbsPos().up();
+		shader.tangent = getAbsPos().right();
+	}
+
+
+
+}

+ 6 - 0
h3d/scene/pbr/DirLight.hx

@@ -11,6 +11,12 @@ class DirLight extends Light {
 		if( dir != null ) setDirection(dir);
 		if( dir != null ) setDirection(dir);
 	}
 	}
 
 
+	public override function clone( ?o : h3d.scene.Object ) : h3d.scene.Object {
+		var dl = o == null ? new DirLight(null) : cast o;
+		super.clone(dl);
+		return dl;
+	}
+
 	override function getShadowDirection() : h3d.Vector {
 	override function getShadowDirection() : h3d.Vector {
 		return absPos.front();
 		return absPos.front();
 	}
 	}

+ 0 - 7
h3d/scene/pbr/Light.hx

@@ -1,12 +1,5 @@
 package h3d.scene.pbr;
 package h3d.scene.pbr;
 
 
-enum ShadowMode {
-	None;
-	Dynamic;
-	Static;
-	Mixed;
-}
-
 class Light extends h3d.scene.Light {
 class Light extends h3d.scene.Light {
 
 
 	var _color : h3d.Vector;
 	var _color : h3d.Vector;

+ 0 - 291
h3d/scene/pbr/LightProbeBaker.hx

@@ -1,291 +0,0 @@
-package h3d.scene.pbr;
-
-import h3d.scene.pbr.Renderer;
-
-class LightProbeBaker {
-
-	public var useGPU = false;
-	public var environment : h3d.scene.pbr.Environment;
-
-	var envMap : h3d.mat.Texture;
-	var customCamera : h3d.Camera;
-	var cubeDir = [ h3d.Matrix.L([0,0,-1,0, 0,1,0,0, -1,-1,1,0]),
-					h3d.Matrix.L([0,0,1,0, 0,1,0,0, 1,-1,-1,0]),
-	 				h3d.Matrix.L([-1,0,0,0, 0,0,1,0, 1,-1,-1,0]),
-	 				h3d.Matrix.L([-1,0,0,0, 0,0,-1,0, 1,1,1,0]),
-				 	h3d.Matrix.L([-1,0,0,0, 0,1,0,0, 1,-1,1,0]),
-				 	h3d.Matrix.L([1,0,0,0, 0,1,0,0, -1,-1,-1,0]) ];
-
-	function getSwiz(name,comp) : hxsl.Output { return Swiz(Value(name,3),[comp]); }
-
-	var computeSH : h3d.pass.ScreenFx<h3d.shader.pbr.ComputeSH>;
-	var output0 = new h3d.mat.Texture(1, 1, [Target], RGBA32F);
-	var output1 = new h3d.mat.Texture(1, 1, [Target], RGBA32F);
-	var output2 = new h3d.mat.Texture(1, 1, [Target], RGBA32F);
-	var output3 = new h3d.mat.Texture(1, 1, [Target], RGBA32F);
-	var output4 = new h3d.mat.Texture(1, 1, [Target], RGBA32F);
-	var output5 = new h3d.mat.Texture(1, 1, [Target], RGBA32F);
-	var output6 = new h3d.mat.Texture(1, 1, [Target], RGBA32F);
-	var output7 = new h3d.mat.Texture(1, 1, [Target], RGBA32F);
-	var textureArray: Array<h3d.mat.Texture>;
-
-	public function new(){
-		customCamera = new h3d.Camera();
-		customCamera.screenRatio = 1.0;
-		customCamera.fovY = 90;
-	}
-
-	public function bake(s3d : Scene, volumetricLightMap : VolumetricLightmap, resolution : Int, ?time :Float) {
-
-		var timer = haxe.Timer.stamp();
-		var timeElapsed = 0.0;
-
-		var index = volumetricLightMap.lastBakedProbeIndex + 1;
-		if(index > volumetricLightMap.getProbeCount() - 1) return time;
-
-		setupEnvMap(resolution);
-		setupShaderOutput(volumetricLightMap.shOrder);
-		var pbrRenderer = Std.instance(s3d.renderer, h3d.scene.pbr.Renderer);
-
-		// Save Scene Config
-		var oldCamera = s3d.camera;
-		var oldRenderMode = s3d.renderer.renderMode;
-		s3d.renderer.renderMode = LightProbe;
-		s3d.camera = customCamera;
-		var engine = h3d.Engine.getCurrent();
-
-		var coefCount = volumetricLightMap.getCoefCount();
-		var sizeX = volumetricLightMap.probeCount.x * coefCount;
-		var sizeY = volumetricLightMap.probeCount.y * volumetricLightMap.probeCount.z;
-		if(volumetricLightMap.lightProbeTexture == null || volumetricLightMap.lightProbeTexture.width != sizeX || volumetricLightMap.lightProbeTexture.height != sizeY){
-			if( volumetricLightMap.lightProbeTexture != null ) volumetricLightMap.lightProbeTexture.dispose();
-			volumetricLightMap.lightProbeTexture = new h3d.mat.Texture(sizeX, sizeY, [Dynamic], RGBA32F);
-			volumetricLightMap.lightProbeTexture.filter = Nearest;
-		}
-
-		var pixels : hxd.Pixels.PixelsFloat = volumetricLightMap.lightProbeTexture.capturePixels();
-
-		while( (time != null && timeElapsed < time) || time == null){
-			var coords = volumetricLightMap.getProbeCoords(index);
-
-			// Bake a Probe
-			for( f in 0...6 ) {
-				engine.begin();
-				customCamera.setCubeMap(f, volumetricLightMap.getProbePosition(coords));
-				customCamera.update();
-				engine.pushTarget(envMap, f);
-				engine.clear(0,1,0);
-				s3d.render(engine);
-				engine.popTarget();
-			}
-			volumetricLightMap.lastBakedProbeIndex = index;
-
-			var sh : SphericalHarmonic = useGPU ? convertEnvIntoSH_GPU(pbrRenderer, envMap, volumetricLightMap.shOrder) : convertEnvIntoSH_CPU(envMap, volumetricLightMap.shOrder);
-			for(coef in 0... coefCount){
-				var u = coords.x + volumetricLightMap.probeCount.x * coef;
-				var v = coords.y + coords.z * volumetricLightMap.probeCount.y;
-				pixels.setPixelF(u, v, new h3d.Vector(sh.coefR[coef], sh.coefG[coef], sh.coefB[coef], 0));
-			}
-
-			index = volumetricLightMap.lastBakedProbeIndex + 1;
-			if(index > volumetricLightMap.getProbeCount() - 1) break;
-
-			timeElapsed = haxe.Timer.stamp() - timer;
-		}
-
-		volumetricLightMap.lightProbeTexture.uploadPixels(pixels, 0, 0);
-
-		// Restore Scene Config
-		s3d.camera = oldCamera;
-		s3d.renderer.renderMode = oldRenderMode;
-
-		return time - timeElapsed;
-	}
-
-	function setupEnvMap(resolution : Int){
-		if(envMap == null || resolution != envMap.width ) {
-			if( envMap != null ) envMap.dispose();
-			envMap = new h3d.mat.Texture(resolution, resolution, [Cube, Target], RGBA32F);
-		}
-	}
-
-	public function dispose() {
-		if( envMap != null ) {
-			envMap.dispose();
-			envMap = null;
-		}
-		output0.dispose();
-		output1.dispose();
-		output2.dispose();
-		output3.dispose();
-		output4.dispose();
-		output5.dispose();
-		output6.dispose();
-		output7.dispose();
-	}
-
-	function setupShaderOutput(order : Int){
-
-		if(order > 3 || order <= 0){ throw "Not Supported"; return; }
-
-		switch(order){
-			case 1:
-				textureArray = [output0];
-				computeSH = new h3d.pass.ScreenFx(new h3d.shader.pbr.ComputeSH(),[
-								Vec4([Value("out.coefL00", 3), Const(0)])]);
-			case 2:
-				textureArray = [output0, output1, output2, output3];
-				computeSH = new h3d.pass.ScreenFx(new h3d.shader.pbr.ComputeSH(),[
-							Vec4([Value("out.coefL00", 3), Const(0)]),
-							Vec4([Value("out.coefL1n1", 3), Const(0)]),
-							Vec4([Value("out.coefL10", 3), Const(0)]),
-							Vec4([Value("out.coefL11", 3), Const(0)])]);
-
-			case 3:
-				textureArray = [output0, output1, output2, output3, output4, output5, output6, output7];
-				computeSH = new h3d.pass.ScreenFx(new h3d.shader.pbr.ComputeSH(),[
-							Vec4([Value("out.coefL00", 3), getSwiz("out.coefL22",X) ]),
-							Vec4([Value("out.coefL1n1", 3), getSwiz("out.coefL22",Y) ]),
-							Vec4([Value("out.coefL10", 3), getSwiz("out.coefL22",Z) ]),
-							Vec4([Value("out.coefL11", 3), Const(0)]),
-							Vec4([Value("out.coefL2n2", 3), Const(0)]),
-							Vec4([Value("out.coefL2n1", 3), Const(0)]),
-							Vec4([Value("out.coefL20", 3), Const(0)]),
-							Vec4([Value("out.coefL21", 3), Const(0)])]);
-		}
-	}
-
-	function convertEnvIntoSH_GPU(renderer : h3d.scene.Renderer, env : h3d.mat.Texture, order : Int) : SphericalHarmonic {
-		var t0 = haxe.Timer.stamp();
-
-		@:privateAccess renderer.ctx.engine = h3d.Engine.getCurrent();
-		@:privateAccess renderer.setTargets(textureArray);
-		computeSH.shader.ORDER = order;
-		computeSH.shader.width = env.width;
-		computeSH.shader.environment = env;
-		computeSH.shader.cubeDir = cubeDir;
-		computeSH.render();
-		@:privateAccess renderer.resetTarget();
-		@:privateAccess renderer.ctx.engine.flushTarget();
-
-		var sphericalHarmonic = new SphericalHarmonic(order);
-		var coefCount = order * order;
-		var maxCoef : Int = Std.int(Math.min(8, coefCount));
-
-		for(i in 0...maxCoef){
-			var pixel : hxd.Pixels.PixelsFloat = textureArray[i].capturePixels();
-			var coefs : h3d.Vector = pixel.getPixelF(0, 0);
-			sphericalHarmonic.coefR[i] = coefs.r;
-			sphericalHarmonic.coefG[i] = coefs.g;
-			sphericalHarmonic.coefB[i] = coefs.b;
-			// Last coefs is inside the alpha channel
-			if( order == 3 ){
-				if( i == 0 ){ sphericalHarmonic.coefR[8] = coefs.a; }
-				if( i == 1 ){ sphericalHarmonic.coefG[8] = coefs.a; }
-				if( i == 2 ){ sphericalHarmonic.coefB[8] = coefs.a; }
-			}
-		}
-		//trace("SH compute time GPU", haxe.Timer.stamp() - t0);
-		return sphericalHarmonic;
-	}
-
-	function convertEnvIntoSH_CPU(env : h3d.mat.Texture, order : Int) : SphericalHarmonic {
-		var coefCount = order * order;
-		var sphericalHarmonic = new SphericalHarmonic(order);
-		var face : hxd.Pixels.PixelsFloat;
-		var weightSum = 0.0;
-		var invWidth = 1.0 / env.width;
-		var shData : Array<Float> = [for (value in 0...coefCount) 0];
-
-		for(f in 0...6){
-			face = env.capturePixels(f, 0);
-			for (u in 0...face.width) {
-				var fU : Float = (u / face.width ) * 2 - 1;// Texture coordinate U in range [-1 to 1]
-				fU *= fU;
-				var uCoord = 2.0 * u * invWidth + invWidth;
-				for (v in 0...face.width) {
-        			var fV : Float = (v / face.height ) * 2 - 1;// Texture coordinate V in range [-1 to 1]
-					fV *= fV;
-					var vCoord = 2.0 * v * invWidth + invWidth;
-					var dir = getDir(uCoord, vCoord, f);// Get direction from center of cube texture to current texel
-           			var diffSolid = 4.0 / ((1.0 + fU + fV) * Math.sqrt(1.0 + fU + fV));	// Scale factor depending on distance from center of the face
-					weightSum += diffSolid;
-					var color = face.getPixelF(u,v);// Get color from the current face
-					evalSH(order, dir, shData);// Calculate coefficients of spherical harmonics for current direction
-					for(i in 0...coefCount){
-						sphericalHarmonic.coefR[i] += shData[i] * color.r * diffSolid;
-						sphericalHarmonic.coefG[i] += shData[i] * color.g * diffSolid;
-						sphericalHarmonic.coefB[i] += shData[i] * color.b * diffSolid;
-					}
-				}
-			}
-		}
-		// Final scale for coefficients
-		var normProj = (4.0 * Math.PI) / weightSum;
-		for(i in 0...coefCount){
-			sphericalHarmonic.coefR[i] *= normProj;
-			sphericalHarmonic.coefG[i] *= normProj;
-			sphericalHarmonic.coefB[i] *= normProj;
-		}
-		return sphericalHarmonic;
-	}
-
-	inline function evalSH(order:Int, dir:h3d.Vector, shData : Array<Float>) {
-		for (l in 0...order) {
-       		for (m in -l...l+1) {
-				shData[getIndex(l, m)] = evalCoef(l, m, dir);
-			}
-		}
-	}
-
-	inline function getIndex(l : Int, m :Int) : Int {
-		return l * (l + 1) + m;
-	}
-
-	inline function getDir(u: Float, v:Float, face:Int) : h3d.Vector {
-		var dir = new h3d.Vector();
-		switch(face) {
-			case 0: dir.x = -1.0; dir.y = -1.0 + v; dir.z = 1.0 - u;
-			case 1: dir.x = 1.0; dir.y = -1.0 + v; dir.z = -1.0 + u;
-			case 2: dir.x = 1.0 - u; dir.y = -1.0; dir.z = -1.0 + v;
-			case 3: dir.x = 1.0 - u; dir.y = 1.0; dir.z = 1.0 - v;
-			case 4: dir.x = 1.0 - u;  dir.y = -1.0 + v; dir.z = 1.0;
-			case 5: dir.x = -1.0 + u; dir.y = -1.0 + v;  dir.z = -1.0;
-			default:
-		}
-		dir.normalizeFast();
-		return dir;
-	}
-
-	inline function evalCoef(l : Int, m : Int, dir: h3d.Vector) : Float{
-		// Coef from Stupid Spherical Harmonics (SH) Peter-Pike Sloan Microsoft Corporation
-		return switch [l,m] {
-			case[0,0]:	0.282095; // 0.5 * sqrt(1/pi)
-			case[1,-1]:	-0.488603 * dir.y;  // -sqrt(3/(4pi)) * y
-			case[1,0]:	0.488603 * dir.z; // sqrt(3/(4pi)) * z
-			case[1,1]:	-0.488603 * dir.x; // -sqrt(3/(4pi)) * x
-			case[2,-2]:	1.092548 * dir.y * dir.x;// 0.5 * sqrt(15/pi) * y * x
-			case[2,-1]:	-1.092548 * dir.y * dir.z; // -0.5 * sqrt(15/pi) * y * z
-			case[2,0]:	0.315392 * (-dir.x * dir.x - dir.y * dir.y + 2.0 * dir.z * dir.z);// 0.25 * sqrt(5/pi) * (-x^2-y^2+2z^2)
-			case[2,1]:  -1.092548 * dir.x * dir.z; // -0.5 * sqrt(15/pi) * x * z
-			case[2,2]:	0.546274 * (dir.x * dir.x - dir.y * dir.y); // 0.25 * sqrt(15/pi) * (x^2 - y^2)
-			case[3,-3]:	-0.590044 * dir.y * (3.0 * dir.x * dir.x - dir.y * dir.y);// -0.25 * sqrt(35/(2pi)) * y * (3x^2 - y^2)
-			case[3,-2]: 2.890611 * dir.x * dir.y * dir.z; // 0.5 * sqrt(105/pi) * x * y * z
-			case[3,-1]: -0.457046 * dir.y * (4.0 * dir.z * dir.z - dir.x * dir.x - dir.y * dir.y); // -0.25 * sqrt(21/(2pi)) * y * (4z^2-x^2-y^2)
-			case[3,0]:  0.373176 * dir.z * (2.0 * dir.z * dir.z - 3.0 * dir.x * dir.x - 3.0 * dir.y * dir.y);  // 0.25 * sqrt(7/pi) * z * (2z^2 - 3x^2 - 3y^2)
-			case[3,1]:	-0.457046 * dir.x * (4.0 * dir.z * dir.z - dir.x * dir.x - dir.y * dir.y); // -0.25 * sqrt(21/(2pi)) * x * (4z^2-x^2-y^2)
-			case[3,2]:  1.445306 * dir.z * (dir.x * dir.x - dir.y * dir.y); // 0.25 * sqrt(105/pi) * z * (x^2 - y^2)
-			case[3,3]:	-0.590044 * dir.x * (dir.x * dir.x - 3.0 * dir.y * dir.y); // -0.25 * sqrt(35/(2pi)) * x * (x^2-3y^2)
-			case[4,-4]: 2.503343 * dir.x * dir.y * (dir.x * dir.x - dir.y * dir.y);// 0.75 * sqrt(35/pi) * x * y * (x^2-y^2)
-			case[4,-3]: -1.770131 * dir.y * dir.z * (3.0 * dir.x * dir.x - dir.y * dir.y); // -0.75 * sqrt(35/(2pi)) * y * z * (3x^2-y^2)
-			case[4,-2]: 0.946175 * dir.x * dir.y * (7.0 * dir.z * dir.z - 1.0);// 0.75 * sqrt(5/pi) * x * y * (7z^2-1)
-			case[4,-1]: -0.669047 * dir.y * dir.z * (7.0 * dir.z * dir.z - 3.0);// -0.75 * sqrt(5/(2pi)) * y * z * (7z^2-3)
-			case[4,0]:  0.105786 * (35.0 * dir.z * dir.z * dir.z * dir.z - 30.0 * dir.z * dir.z + 3.0);// 3/16 * sqrt(1/pi) * (35z^4-30z^2+3)
-			case[4,1]:  -0.669047 * dir.x * dir.z * (7.0 * dir.z * dir.z - 3.0);// -0.75 * sqrt(5/(2pi)) * x * z * (7z^2-3)
-			case[4,2]:  0.473087 * (dir.x * dir.x - dir.y * dir.y) * (7.0 * dir.z * dir.z - 1.0);// 3/8 * sqrt(5/pi) * (x^2 - y^2) * (7z^2 - 1)
-			case[4,3]:  -1.770131 * dir.x * dir.z * (dir.x * dir.x - 3.0 * dir.y * dir.y);// -0.75 * sqrt(35/(2pi)) * x * z * (x^2 - 3y^2)
-			case[4,4]:  0.625836 * (dir.x * dir.x * (dir.x * dir.x - 3.0 * dir.y * dir.y) - dir.y * dir.y * (3.0 * dir.x * dir.x - dir.y * dir.y)); // 3/16*sqrt(35/pi) * (x^2 * (x^2 - 3y^2) - y^2 * (3x^2 - y^2))
-			default: 0;
-		}
-	}
-}

+ 10 - 3
h3d/scene/pbr/LightSystem.hx

@@ -6,14 +6,21 @@ 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 = Std.instance(obj, h3d.scene.pbr.Light);
 		if( light != null ) {
 		if( light != null ) {
-			if( light.shadows.shader != null )
+			shaders = ctx.allocShaderList(light.shader, shaders);
+			if( light.shadows.shader != null && light.shadows.mode != None )
 				shaders = ctx.allocShaderList(light.shadows.shader, shaders);
 				shaders = ctx.allocShaderList(light.shadows.shader, shaders);
-			return ctx.allocShaderList(light.shader, shaders);
 		}
 		}
 		return shaders;
 		return shaders;
 	}
 	}
 
 
-	public function drawLights( r : h3d.scene.Renderer, lightPass : h3d.pass.ScreenFx<Dynamic> ) {
+
+	public function drawLight( light : Light, passes : h3d.pass.PassList ) {
+		light.shadows.setContext(ctx);
+		light.shadows.draw(passes);
+		passes.reset();
+	}
+
+	public function drawScreenLights( r : h3d.scene.Renderer, lightPass : h3d.pass.ScreenFx<Dynamic> ) {
 		var plight = @:privateAccess ctx.lights;
 		var plight = @:privateAccess ctx.lights;
 		var currentTarget = ctx.engine.getCurrentTarget();
 		var currentTarget = ctx.engine.getCurrentTarget();
 		var width = currentTarget == null ? ctx.engine.width : currentTarget.width;
 		var width = currentTarget == null ? ctx.engine.width : currentTarget.width;

+ 24 - 1
h3d/scene/pbr/PointLight.hx

@@ -4,6 +4,7 @@ class PointLight extends Light {
 
 
 	var pbr : h3d.shader.pbr.Light.PointLight;
 	var pbr : h3d.shader.pbr.Light.PointLight;
 	public var size : Float;
 	public var size : Float;
+	public var zNear : Float = 0.02;
 	/**
 	/**
 		Alias for uniform scale.
 		Alias for uniform scale.
 	**/
 	**/
@@ -17,6 +18,14 @@ class PointLight extends Light {
 		primitive = h3d.prim.Sphere.defaultUnitSphere();
 		primitive = h3d.prim.Sphere.defaultUnitSphere();
 	}
 	}
 
 
+	public override function clone( ?o : h3d.scene.Object ) : h3d.scene.Object {
+		var pl = o == null ? new PointLight(null) : cast o;
+		super.clone(pl);
+		pl.size = size;
+		pl.range = range;
+		return pl;
+	}
+
 	function get_range() {
 	function get_range() {
 		return cullingDistance;
 		return cullingDistance;
 	}
 	}
@@ -26,7 +35,7 @@ class PointLight extends Light {
 		return cullingDistance = v;
 		return cullingDistance = v;
 	}
 	}
 
 
-	override function draw(ctx) {
+	override function draw(ctx:RenderContext) {
 		primitive.render(ctx.engine);
 		primitive.render(ctx.engine);
 	}
 	}
 
 
@@ -43,10 +52,24 @@ class PointLight extends Light {
 		pbr.pointSize = size;
 		pbr.pointSize = size;
 	}
 	}
 
 
+	var s = new h3d.col.Sphere();
 	override function emit(ctx:RenderContext) {
 	override function emit(ctx:RenderContext) {
+		if( ctx.computingStatic ) {
+			super.emit(ctx);
+			return;
+		}
+
 		if( ctx.pbrLightPass == null )
 		if( ctx.pbrLightPass == null )
 			throw "Rendering a pbr light require a PBR compatible scene renderer";
 			throw "Rendering a pbr light require a PBR compatible scene renderer";
 
 
+		s.x = absPos._41;
+		s.y = absPos._42;
+		s.z = absPos._43;
+		s.r = cullingDistance;
+
+		if( !ctx.camera.frustum.hasSphere(s) )
+			return;
+
 		super.emit(ctx);
 		super.emit(ctx);
 		ctx.emitPass(ctx.pbrLightPass, this);
 		ctx.emitPass(ctx.pbrLightPass, this);
 	}
 	}

+ 89 - 24
h3d/scene/pbr/Renderer.hx

@@ -17,6 +17,10 @@ package h3d.scene.pbr;
 		Debug slides
 		Debug slides
 	*/
 	*/
 	var Debug = "Debug";
 	var Debug = "Debug";
+	/*
+		Distortion
+	*/
+	var Distortion = "Distortion";
 }
 }
 
 
 @:enum abstract SkyMode(String) {
 @:enum abstract SkyMode(String) {
@@ -34,18 +38,32 @@ package h3d.scene.pbr;
 typedef RenderProps = {
 typedef RenderProps = {
 	var mode : DisplayMode;
 	var mode : DisplayMode;
 	var env : String;
 	var env : String;
+	var colorGradingLUT : String;
+	var colorGradingLUTSize : Int;
 	var envPower : Float;
 	var envPower : Float;
 	var exposure : Float;
 	var exposure : Float;
 	var sky : SkyMode;
 	var sky : SkyMode;
 	var tone : TonemapMap;
 	var tone : TonemapMap;
 	var emissive : Float;
 	var emissive : Float;
 	var occlusion : Float;
 	var occlusion : Float;
+	var shadows : Bool;
+}
+
+class DepthCopy extends h3d.shader.ScreenShader {
+
+	static var SRC = {
+		@ignore @param var depthTexture : Channel;
+		function fragment() {
+			pixelColor = vec4(depthTexture.get(calculatedUV));
+		}
+	}
 }
 }
 
 
 class Renderer extends h3d.scene.Renderer {
 class Renderer extends h3d.scene.Renderer {
 	var slides = new h3d.pass.ScreenFx(new h3d.shader.pbr.Slides());
 	var slides = new h3d.pass.ScreenFx(new h3d.shader.pbr.Slides());
 	var pbrOut = new h3d.pass.ScreenFx(new h3d.shader.ScreenShader());
 	var pbrOut = new h3d.pass.ScreenFx(new h3d.shader.ScreenShader());
 	var tonemap = new h3d.pass.ScreenFx(new h3d.shader.pbr.ToneMapping());
 	var tonemap = new h3d.pass.ScreenFx(new h3d.shader.pbr.ToneMapping());
+	var depthCopy = new h3d.pass.ScreenFx(new DepthCopy());
 	var pbrLightPass : h3d.mat.Pass;
 	var pbrLightPass : h3d.mat.Pass;
 	var screenLightPass : h3d.pass.ScreenFx<h3d.shader.ScreenShader>;
 	var screenLightPass : h3d.pass.ScreenFx<h3d.shader.ScreenShader>;
 	var fxaa = new h3d.pass.FXAA();
 	var fxaa = new h3d.pass.FXAA();
@@ -56,14 +74,16 @@ class Renderer extends h3d.scene.Renderer {
 
 
 	public var skyMode : SkyMode = Hide;
 	public var skyMode : SkyMode = Hide;
 	public var toneMode : TonemapMap = Reinhard;
 	public var toneMode : TonemapMap = Reinhard;
+	public var colorgGradingLUT : h3d.mat.Texture;
+	public var colorGradingLUTSize : Int;
 	public var displayMode : DisplayMode = Pbr;
 	public var displayMode : DisplayMode = Pbr;
 	public var env : Environment;
 	public var env : Environment;
 	public var exposure(get,set) : Float;
 	public var exposure(get,set) : Float;
 	public var debugMode = 0;
 	public var debugMode = 0;
-
+	public var shadows = true;
 
 
 	static var ALPHA : hxsl.Output = Swiz(Value("output.color"),[W]);
 	static var ALPHA : hxsl.Output = Swiz(Value("output.color"),[W]);
-	var output = new h3d.pass.Output("mrt",[
+	var output = new h3d.pass.Output("default",[
 		Value("output.color"),
 		Value("output.color"),
 		Vec4([Value("output.normal",3),ALPHA]),
 		Vec4([Value("output.normal",3),ALPHA]),
 		Vec4([Value("output.metalness"), Value("output.roughness"), Value("output.occlusion"), ALPHA]),
 		Vec4([Value("output.metalness"), Value("output.roughness"), Value("output.occlusion"), ALPHA]),
@@ -73,12 +93,14 @@ class Renderer extends h3d.scene.Renderer {
 		Vec4([Swiz(Value("output.color"),[X,Y,Z]), Value("output.albedoStrength",1)]),
 		Vec4([Swiz(Value("output.color"),[X,Y,Z]), Value("output.albedoStrength",1)]),
 		Vec4([Value("output.normal",3), Value("output.normalStrength",1)]),
 		Vec4([Value("output.normal",3), Value("output.normalStrength",1)]),
 		Vec4([Value("output.metalness"), Value("output.roughness"), Value("output.occlusion"), Value("output.pbrStrength")]),
 		Vec4([Value("output.metalness"), Value("output.roughness"), Value("output.occlusion"), Value("output.pbrStrength")]),
+		Vec4([Value("output.emissive"),Value("output.depth"), Const(0), Const(1)])
 	]);
 	]);
 
 
+
 	public function new(env) {
 	public function new(env) {
 		super();
 		super();
 		this.env = env;
 		this.env = env;
-		defaultPass = new h3d.pass.Default("default");
+		defaultPass = new h3d.pass.Default("color");
 		slides.addShader(pbrProps);
 		slides.addShader(pbrProps);
 		pbrOut.addShader(pbrIndirect);
 		pbrOut.addShader(pbrIndirect);
 		pbrOut.addShader(pbrProps);
 		pbrOut.addShader(pbrProps);
@@ -88,6 +110,7 @@ class Renderer extends h3d.scene.Renderer {
 		allPasses.push(output);
 		allPasses.push(output);
 		allPasses.push(defaultPass);
 		allPasses.push(defaultPass);
 		allPasses.push(decalsOutput);
 		allPasses.push(decalsOutput);
+		allPasses.push(new h3d.pass.Shadows(null));
 		refreshProps();
 		refreshProps();
 	}
 	}
 
 
@@ -112,7 +135,7 @@ class Renderer extends h3d.scene.Renderer {
 				to be discarded when the camera is inside its volume.
 				to be discarded when the camera is inside its volume.
 			*/
 			*/
 			pbrLightPass.culling = Front;
 			pbrLightPass.culling = Front;
-			pbrLightPass.depth(false, Greater);
+			pbrLightPass.depth(false, GreaterEqual);
 			pbrLightPass.enableLights = true;
 			pbrLightPass.enableLights = true;
 		}
 		}
 		ctx.pbrLightPass = pbrLightPass;
 		ctx.pbrLightPass = pbrLightPass;
@@ -128,15 +151,14 @@ class Renderer extends h3d.scene.Renderer {
 		draw("overlay");
 		draw("overlay");
 	}
 	}
 
 
-	function drawShadows(){
+	function drawShadows( ls : LightSystem ) {
 		var light = @:privateAccess ctx.lights;
 		var light = @:privateAccess ctx.lights;
 		var passes = get("shadow");
 		var passes = get("shadow");
+		if( !shadows )
+			passes.clear();
 		while( light != null ) {
 		while( light != null ) {
 			var plight = Std.instance(light, h3d.scene.pbr.Light);
 			var plight = Std.instance(light, h3d.scene.pbr.Light);
-			if( plight != null ) {
-				plight.shadows.setContext(ctx);
-				plight.shadows.draw(passes);
-			}
+			if( plight != null ) ls.drawLight(plight, passes);
 			light = light.next;
 			light = light.next;
 		}
 		}
 	}
 	}
@@ -162,12 +184,9 @@ class Renderer extends h3d.scene.Renderer {
 
 
 	override function render() {
 	override function render() {
 		var props : RenderProps = props;
 		var props : RenderProps = props;
-		var ls = getLightSystem();
 
 
-		drawShadows();
-
-		var albedo = allocTarget("albedo");
-		var normal = allocTarget("normalDepth",false,1.,RGBA16F);
+		var albedo = allocTarget("albedo", true, 1.);
+		var normal = allocTarget("normal",false,1.,RGBA16F);
 		var pbr = allocTarget("pbr",false,1.);
 		var pbr = allocTarget("pbr",false,1.);
 		var other = allocTarget("other",false,1.,RGBA32F);
 		var other = allocTarget("other",false,1.,RGBA32F);
 
 
@@ -177,11 +196,24 @@ 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 count = ctx.engine.drawCalls;
+		if( ls != null ) drawShadows(ls);
+		ctx.lightSystem.drawPasses = ctx.engine.drawCalls - count;
+
 		setTargets([albedo,normal,pbr,other]);
 		setTargets([albedo,normal,pbr,other]);
 		clear(0, 1, 0);
 		clear(0, 1, 0);
 		mainDraw();
 		mainDraw();
 
 
-		setTargets([albedo,normal,pbr]);
+		var depth = allocTarget("depth",false,1.,R32F);
+		var depthMap = ctx.getGlobal("depthMap");
+		depthCopy.shader.depthTexture = depthMap.texture;
+		depthCopy.shader.depthTextureChannel = depthMap.channel;
+		setTargets([depth]);
+		depthCopy.render();
+		ctx.setGlobal("depthMap",{ texture : depth, channel : hxsl.Channel.R });
+
+		setTargets([albedo,normal,pbr,other]);
 		decalsOutput.draw(get("decal"));
 		decalsOutput.draw(get("decal"));
 
 
 		setTarget(albedo);
 		setTarget(albedo);
@@ -247,7 +279,6 @@ class Renderer extends h3d.scene.Renderer {
 
 
 		pbrDirect.doDiscard = true;
 		pbrDirect.doDiscard = true;
 
 
-		var ls = Std.instance(ls, LightSystem);
 		var lpass = screenLightPass;
 		var lpass = screenLightPass;
 		if( lpass == null ) {
 		if( lpass == null ) {
 			lpass = new h3d.pass.ScreenFx(new h3d.shader.ScreenShader());
 			lpass = new h3d.pass.ScreenFx(new h3d.shader.ScreenShader());
@@ -256,10 +287,18 @@ class Renderer extends h3d.scene.Renderer {
 			lpass.pass.setBlendMode(Add);
 			lpass.pass.setBlendMode(Add);
 			screenLightPass = lpass;
 			screenLightPass = lpass;
 		}
 		}
-		if( ls != null ) ls.drawLights(this, lpass);
 
 
+		// Draw DirLight, screenShader
+		pbrProps.isScreen = true;
+		if( ls != null ) {
+			var count = ctx.engine.drawCalls;
+			ls.drawScreenLights(this, lpass);
+			ctx.lightSystem.drawPasses += ctx.engine.drawCalls - count;
+		}
+
+		// Draw others lights with their primitive
 		pbrProps.isScreen = false;
 		pbrProps.isScreen = false;
-		draw("lights");
+		draw(pbrLightPass.name);
 
 
 		if( renderMode == LightProbe ) {
 		if( renderMode == LightProbe ) {
 			pbrProps.isScreen = true;
 			pbrProps.isScreen = true;
@@ -285,22 +324,28 @@ class Renderer extends h3d.scene.Renderer {
 		pbrOut.pass.stencil.setFunc(NotEqual, 0x80, 0x80, 0x80);
 		pbrOut.pass.stencil.setFunc(NotEqual, 0x80, 0x80, 0x80);
 		pbrOut.render();
 		pbrOut.render();
 
 
-		draw("BeforeTonemapping");
+		draw("beforeTonemapping");
 
 
 		apply(AfterHdr);
 		apply(AfterHdr);
 
 
-		var distortion = allocTarget("distortion", true, 1.0, RGBA16F);
+		var distortion = allocTarget("distortion", true, 1.0, RG16F);
 		distortion.clear(0x000000);
 		distortion.clear(0x000000);
 		setTarget(distortion);
 		setTarget(distortion);
-		draw("Distortion");
+		draw("distortion");
 
 
 		var ldr = allocTarget("ldrOutput");
 		var ldr = allocTarget("ldrOutput");
 		setTarget(ldr);
 		setTarget(ldr);
+		ctx.setGlobal("hdr", hdr);
 		var bloom = ctx.getGlobal("bloom");
 		var bloom = ctx.getGlobal("bloom");
 		tonemap.shader.bloom = bloom;
 		tonemap.shader.bloom = bloom;
 		tonemap.shader.hasBloom = bloom != null;
 		tonemap.shader.hasBloom = bloom != null;
 		tonemap.shader.distortion = distortion;
 		tonemap.shader.distortion = distortion;
 		tonemap.shader.hasDistortion = distortion != null;
 		tonemap.shader.hasDistortion = distortion != null;
+		tonemap.shader.hasColorGrading = colorgGradingLUT != null;
+		if( colorgGradingLUT != null  ){
+			tonemap.shader.colorGradingLUT = colorgGradingLUT;
+			tonemap.shader.lutSize = colorGradingLUTSize;
+		}
 		tonemap.shader.pixelSize = new Vector(1.0/ctx.engine.width, 1.0/ctx.engine.height);
 		tonemap.shader.pixelSize = new Vector(1.0/ctx.engine.width, 1.0/ctx.engine.height);
 		tonemap.shader.mode =	switch( toneMode ) {
 		tonemap.shader.mode =	switch( toneMode ) {
 									case Linear: 0;
 									case Linear: 0;
@@ -311,6 +356,7 @@ class Renderer extends h3d.scene.Renderer {
 		tonemap.render();
 		tonemap.render();
 
 
 		postDraw();
 		postDraw();
+		apply(Final);
 		resetTarget();
 		resetTarget();
 
 
 		switch( displayMode ) {
 		switch( displayMode ) {
@@ -318,8 +364,11 @@ class Renderer extends h3d.scene.Renderer {
 		case Pbr, Env, MatCap:
 		case Pbr, Env, MatCap:
 			fxaa.apply(ldr);
 			fxaa.apply(ldr);
 
 
-		case Debug:
+		case Distortion:
+			resetTarget();
+			copy( distortion, null);
 
 
+		case Debug:
 			var shadowMap = ctx.textures.getNamed("shadowMap");
 			var shadowMap = ctx.textures.getNamed("shadowMap");
 			if( shadowMap == null )
 			if( shadowMap == null )
 				shadowMap = h3d.mat.Texture.fromColor(0);
 				shadowMap = h3d.mat.Texture.fromColor(0);
@@ -331,7 +380,6 @@ class Renderer extends h3d.scene.Renderer {
 				hasDebugEvent = true;
 				hasDebugEvent = true;
 				hxd.Window.getInstance().addEventTarget(onEvent);
 				hxd.Window.getInstance().addEventTarget(onEvent);
 			}
 			}
-
 		}
 		}
 
 
 		if( hasDebugEvent && displayMode != Debug ) {
 		if( hasDebugEvent && displayMode != Debug ) {
@@ -364,12 +412,15 @@ class Renderer extends h3d.scene.Renderer {
 		var props : RenderProps = {
 		var props : RenderProps = {
 			mode : Pbr,
 			mode : Pbr,
 			env : null,
 			env : null,
+			colorGradingLUT : null,
+			colorGradingLUTSize : 1,
 			envPower : 1.,
 			envPower : 1.,
 			emissive : 1.,
 			emissive : 1.,
 			exposure : 0.,
 			exposure : 0.,
 			sky : Irrad,
 			sky : Irrad,
 			tone : Linear,
 			tone : Linear,
-			occlusion : 1.
+			occlusion : 1.,
+			shadows: true
 		};
 		};
 		return props;
 		return props;
 	}
 	}
@@ -391,6 +442,13 @@ class Renderer extends h3d.scene.Renderer {
 		toneMode = props.tone;
 		toneMode = props.tone;
 		exposure = props.exposure;
 		exposure = props.exposure;
 		env.power = props.envPower;
 		env.power = props.envPower;
+		shadows = props.shadows;
+
+		if( props.colorGradingLUT != null )
+			colorgGradingLUT = hxd.res.Loader.currentInstance.load(props.colorGradingLUT).toTexture();
+		else
+			colorgGradingLUT = null;
+		colorGradingLUTSize = props.colorGradingLUTSize;
 	}
 	}
 
 
 	#if editor
 	#if editor
@@ -406,6 +464,10 @@ class Renderer extends h3d.scene.Renderer {
 						<option value="Reinhard">Reinhard</option>
 						<option value="Reinhard">Reinhard</option>
 					</select>
 					</select>
 				</dd>
 				</dd>
+
+				<dt>Color Grading LUT</dt><input type="texturepath" field="colorGradingLUT" style="width:165px"/>
+				<dt>LUT Size</dt><dd><input step="1" type="range" min="0" max="32" field="colorGradingLUTSize"></dd>
+
 				<dt>Mode</dt>
 				<dt>Mode</dt>
 				<dd>
 				<dd>
 					<select field="mode">
 					<select field="mode">
@@ -413,8 +475,10 @@ class Renderer extends h3d.scene.Renderer {
 						<option value="Env">Env</option>
 						<option value="Env">Env</option>
 						<option value="MatCap">MatCap</option>
 						<option value="MatCap">MatCap</option>
 						<option value="Debug">Debug</option>
 						<option value="Debug">Debug</option>
+						<option value="Distortion">Distortion</option>
 					</select>
 					</select>
 				</dd>
 				</dd>
+
 				<dt>Env</dt>
 				<dt>Env</dt>
 				<dd>
 				<dd>
 					<input type="texturepath" field="env" style="width:165px"/>
 					<input type="texturepath" field="env" style="width:165px"/>
@@ -430,6 +494,7 @@ class Renderer extends h3d.scene.Renderer {
 				<dt>Emissive</dt><dd><input type="range" min="0" max="2" field="emissive"></dd>
 				<dt>Emissive</dt><dd><input type="range" min="0" max="2" field="emissive"></dd>
 				<dt>Occlusion</dt><dd><input type="range" min="0" max="2" field="occlusion"></dd>
 				<dt>Occlusion</dt><dd><input type="range" min="0" max="2" field="occlusion"></dd>
 				<dt>Exposure</dt><dd><input type="range" min="-3" max="3" field="exposure"></dd>
 				<dt>Exposure</dt><dd><input type="range" min="-3" max="3" field="exposure"></dd>
+				<dt>Shadows</dt><dd><input type="checkbox" field="shadows"></dd>
 			</dl>
 			</dl>
 			</div>
 			</div>
 		');
 		');

+ 28 - 2
h3d/scene/pbr/SpotLight.hx

@@ -15,10 +15,24 @@ class SpotLight extends Light {
 		pbr = new h3d.shader.pbr.Light.SpotLight();
 		pbr = new h3d.shader.pbr.Light.SpotLight();
 		shadows = new h3d.pass.SpotShadowMap(this);
 		shadows = new h3d.pass.SpotShadowMap(this);
 		super(pbr,parent);
 		super(pbr,parent);
-		range = 10;
 		generatePrim();
 		generatePrim();
 		lightProj = new h3d.Camera();
 		lightProj = new h3d.Camera();
 		lightProj.screenRatio = 1.0;
 		lightProj.screenRatio = 1.0;
+		range = 10;
+		maxRange = 10;
+		angle = 45;
+	}
+
+	public override function clone( ?o : h3d.scene.Object ) : h3d.scene.Object {
+		var sl = o == null ? new SpotLight(null) : cast o;
+		super.clone(sl);
+		sl.range = range;
+		sl.maxRange = maxRange;
+		sl.angle = angle;
+		sl.fallOff = fallOff;
+		sl.cookie = cookie;
+		sl.lightProj.load(lightProj);
+		return sl;
 	}
 	}
 
 
 	function get_maxRange() {
 	function get_maxRange() {
@@ -81,7 +95,7 @@ class SpotLight extends Light {
 		return absPos.front();
 		return absPos.front();
 	}
 	}
 
 
-	override function draw(ctx) {
+	override function draw(ctx:RenderContext) {
 		primitive.render(ctx.engine);
 		primitive.render(ctx.engine);
 	}
 	}
 
 
@@ -108,10 +122,22 @@ class SpotLight extends Light {
 		}
 		}
 	}
 	}
 
 
+	var s = new h3d.col.Sphere();
+	var d = new h3d.Vector();
 	override function emit(ctx:RenderContext) {
 	override function emit(ctx:RenderContext) {
 		if( ctx.pbrLightPass == null )
 		if( ctx.pbrLightPass == null )
 			throw "Rendering a pbr light require a PBR compatible scene renderer";
 			throw "Rendering a pbr light require a PBR compatible scene renderer";
 
 
+		d.load(absPos.front());
+		d.scale3(maxRange / 2.0);
+		s.x = absPos.tx + d.x;
+		s.y = absPos.ty + d.y;
+		s.z = absPos.tz + d.z;
+		s.r = maxRange / 2.0;
+
+		if( !ctx.camera.frustum.hasSphere(s) )
+			return;
+
 		super.emit(ctx);
 		super.emit(ctx);
 		ctx.emitPass(ctx.pbrLightPass, this);
 		ctx.emitPass(ctx.pbrLightPass, this);
 	}
 	}

+ 11 - 0
h3d/scene/pbr/VolumetricLightmap.hx

@@ -42,6 +42,17 @@ class VolumetricLightmap extends h3d.scene.Mesh {
 		voxelSize = new h3d.Vector(1,1,1);
 		voxelSize = new h3d.Vector(1,1,1);
 	}
 	}
 
 
+	public override function clone( ?o : h3d.scene.Object ) : h3d.scene.Object {
+		var vm = o == null ? new VolumetricLightmap(null) : cast o;
+		vm.shOrder = shOrder;
+		vm.strength = strength;
+		vm.useAlignedProb = useAlignedProb;
+		vm.lightProbeTexture = lightProbeTexture != null ? lightProbeTexture.clone() : null;
+		vm.probeCount.load(probeCount);
+		vm.voxelSize.load(voxelSize);
+		return vm;
+	}
+
 	public function getProbeSH(coords : h3d.col.IPoint, ?pixels : hxd.Pixels.PixelsFloat ) : SphericalHarmonic {
 	public function getProbeSH(coords : h3d.col.IPoint, ?pixels : hxd.Pixels.PixelsFloat ) : SphericalHarmonic {
 
 
 		if(lightProbeTexture == null)
 		if(lightProbeTexture == null)

+ 15 - 0
h3d/scene/pbr/terrain/Surface.hx

@@ -7,6 +7,7 @@ class Surface {
 	public var tilling = 1.0;
 	public var tilling = 1.0;
 	public var offset : h3d.Vector;
 	public var offset : h3d.Vector;
 	public var angle = 0.0;
 	public var angle = 0.0;
+	public var heightBias = 0.0;
 
 
 	public function new(?albedo : h3d.mat.Texture, ?normal : h3d.mat.Texture, ?pbr : h3d.mat.Texture){
 	public function new(?albedo : h3d.mat.Texture, ?normal : h3d.mat.Texture, ?pbr : h3d.mat.Texture){
 		this.albedo = albedo;
 		this.albedo = albedo;
@@ -15,6 +16,15 @@ class Surface {
 		this.offset = new h3d.Vector(0);
 		this.offset = new h3d.Vector(0);
 	}
 	}
 
 
+	public function clone() : Surface {
+		var o = new Surface(albedo, normal, pbr);
+		o.tilling = tilling;
+		o.offset.load(offset);
+		o.angle = angle;
+		o.heightBias = heightBias;
+		return o;
+	}
+
 	public function dispose() {
 	public function dispose() {
 		if(albedo != null) albedo.dispose();
 		if(albedo != null) albedo.dispose();
 		if(normal != null) normal.dispose();
 		if(normal != null) normal.dispose();
@@ -38,6 +48,11 @@ class SurfaceArray {
 		pbr.wrap = Repeat;
 		pbr.wrap = Repeat;
 	}
 	}
 
 
+	public function clone() : SurfaceArray {
+		var o = new SurfaceArray(albedo.layerCount, albedo.width);
+		return o;
+	}
+
 	public function dispose() {
 	public function dispose() {
 		if(albedo != null) albedo.dispose();
 		if(albedo != null) albedo.dispose();
 		if(normal != null) normal.dispose();
 		if(normal != null) normal.dispose();

+ 32 - 2
h3d/scene/pbr/terrain/Terrain.hx

@@ -14,7 +14,7 @@ class Terrain extends Object {
 	public var parallaxMinStep : Int;
 	public var parallaxMinStep : Int;
 	public var parallaxMaxStep : Int;
 	public var parallaxMaxStep : Int;
 	public var heightBlendStrength : Float;
 	public var heightBlendStrength : Float;
-	public var heightBlendSharpness : Float;
+	public var blendSharpness : Float;
 	public var copyPass (default, null): h3d.pass.Copy;
 	public var copyPass (default, null): h3d.pass.Copy;
 	public var tiles (default, null) : Array<Tile> = [];
 	public var tiles (default, null) : Array<Tile> = [];
 	public var surfaces (default, null) : Array<Surface> = [];
 	public var surfaces (default, null) : Array<Surface> = [];
@@ -25,6 +25,36 @@ class Terrain extends Object {
 		copyPass = new h3d.pass.Copy();
 		copyPass = new h3d.pass.Copy();
 	}
 	}
 
 
+	public override function clone( ?o : h3d.scene.Object ) : h3d.scene.Object {
+		var o = new Terrain();
+		o.tileSize = tileSize;
+		o.cellSize = cellSize;
+		o.cellCount = cellCount;
+		o.heightMapResolution = heightMapResolution;
+		o.weightMapResolution = weightMapResolution;
+		o.showGrid = showGrid;
+		o.showChecker = showChecker;
+		o.showComplexity = showComplexity;
+		o.parallaxAmount = parallaxAmount;
+		o.parallaxMinStep = parallaxMinStep;
+		o.parallaxMaxStep = parallaxMaxStep;
+		o.heightBlendStrength = heightBlendStrength;
+		o.blendSharpness = blendSharpness;
+
+		for( i in 0...tiles.length ){
+			var t = Std.instance(tiles[i].clone(), Tile);
+			t.parent = o;
+			o.tiles.push(t);
+		}
+
+		for( i in 0...surfaces.length )
+			o.surfaces.push(surfaces[i].clone());
+
+		o.surfaceArray = surfaceArray.clone();
+
+		return o;
+	}
+
 	public function getHeight(x : Float, y : Float) : Float {
 	public function getHeight(x : Float, y : Float) : Float {
 		var z = 0.0;
 		var z = 0.0;
 		var t = getTileAtWorldPos(x, y);
 		var t = getTileAtWorldPos(x, y);
@@ -83,7 +113,7 @@ class Terrain extends Object {
 	public function updateSurfaceParams(){
 	public function updateSurfaceParams(){
 		for(i in 0 ... surfaces.length){
 		for(i in 0 ... surfaces.length){
 			surfaceArray.params[i] = new h3d.Vector(surfaces[i].tilling, surfaces[i].offset.x, surfaces[i].offset.y, hxd.Math.degToRad(surfaces[i].angle));
 			surfaceArray.params[i] = new h3d.Vector(surfaces[i].tilling, surfaces[i].offset.x, surfaces[i].offset.y, hxd.Math.degToRad(surfaces[i].angle));
-			surfaceArray.secondParams[i] = new h3d.Vector(0, 0, 0, 0);
+			surfaceArray.secondParams[i] = new h3d.Vector(surfaces[i].heightBias, 0, 0, 0);
 		}
 		}
 	}
 	}
 
 

+ 167 - 138
h3d/scene/pbr/terrain/Tile.hx

@@ -18,36 +18,56 @@ class Tile extends h3d.scene.Mesh {
 	var heightmapPixels : hxd.Pixels.PixelsFloat;
 	var heightmapPixels : hxd.Pixels.PixelsFloat;
 	var shader : h3d.shader.pbr.Terrain;
 	var shader : h3d.shader.pbr.Terrain;
 
 
-	public function new( x : Int, y : Int , ?parent ){
+	public function new( x : Int, y : Int , ?parent ) {
 		super(null, null, parent);
 		super(null, null, parent);
 		this.tileX = x;
 		this.tileX = x;
 		this.tileY = y;
 		this.tileY = y;
 		shader = new h3d.shader.pbr.Terrain();
 		shader = new h3d.shader.pbr.Terrain();
 		material.mainPass.addShader(shader);
 		material.mainPass.addShader(shader);
 		material.mainPass.culling = None;
 		material.mainPass.culling = None;
+		material.shadows = false;
 		this.x = x * getTerrain().tileSize;
 		this.x = x * getTerrain().tileSize;
 		this.y = y * getTerrain().tileSize;
 		this.y = y * getTerrain().tileSize;
 		name = "tile_" + x + "_" + y;
 		name = "tile_" + x + "_" + y;
 	}
 	}
 
 
-	function set_heightMap(v){
+	public override function clone( ?o : h3d.scene.Object ) : h3d.scene.Object {
+		var o = new Tile(tileX, tileY, parent);
+		o.heightMap = heightMap.clone();
+		o.surfaceIndexMap = surfaceIndexMap.clone();
+
+		for( i in 0...surfaceWeights.length )
+			o.surfaceWeights.push(surfaceWeights[i].clone());
+
+		o.surfaceWeightArray = new h3d.mat.TextureArray(getTerrain().weightMapResolution, getTerrain().weightMapResolution, surfaceWeights.length, [Target], R8);
+		o.surfaceWeightArray.wrap = Clamp;
+		o.surfaceWeightArray.preventAutoDispose();
+		o.surfaceWeightArray.realloc = null;
+		for( i in 0 ... surfaceWeights.length )
+			if( surfaceWeights[i] != null ) getTerrain().copyPass.apply(surfaceWeights[i], o.surfaceWeightArray, None, null, i);
+
+		o.heightmapPixels = heightmapPixels.clone();
+		return o;
+	}
+
+	function set_heightMap( v ) {
 		shader.heightMap = v;
 		shader.heightMap = v;
 		return heightMap = v;
 		return heightMap = v;
 	}
 	}
 
 
-	inline function getTerrain(){
+	inline function getTerrain() {
 		return Std.instance(parent, Terrain);
 		return Std.instance(parent, Terrain);
 	}
 	}
 
 
-	public function getHeightPixels(){
-		if(needNewPixelCapture || heightmapPixels == null)
+	public function getHeightPixels() {
+		if( needNewPixelCapture || heightmapPixels == null )
 			heightmapPixels = heightMap.capturePixels();
 			heightmapPixels = heightMap.capturePixels();
 		needNewPixelCapture = false;
 		needNewPixelCapture = false;
 		return heightmapPixels;
 		return heightmapPixels;
 	}
 	}
 
 
-	public function refreshMesh(){
-		if(grid == null || grid.width != getTerrain().cellCount || grid.height != getTerrain().cellCount || grid.cellWidth != getTerrain().cellSize || grid.cellHeight != getTerrain().cellSize){
+	public function refreshMesh() {
+		if( grid == null || grid.width != getTerrain().cellCount || grid.height != getTerrain().cellCount || grid.cellWidth != getTerrain().cellSize || grid.cellHeight != getTerrain().cellSize ) {
 			if(grid != null) grid.dispose();
 			if(grid != null) grid.dispose();
 		 	grid = new h3d.prim.Grid( getTerrain().cellCount, getTerrain().cellCount, getTerrain().cellSize, getTerrain().cellSize);
 		 	grid = new h3d.prim.Grid( getTerrain().cellCount, getTerrain().cellCount, getTerrain().cellSize, getTerrain().cellSize);
 			primitive = grid;
 			primitive = grid;
@@ -56,21 +76,21 @@ class Tile extends h3d.scene.Mesh {
 		computeNormals();
 		computeNormals();
 	}
 	}
 
 
-	public function blendEdges(){
+	public function blendEdges() {
 		var adjTileX = getTerrain().getTile(tileX - 1, tileY);
 		var adjTileX = getTerrain().getTile(tileX - 1, tileY);
-		if( adjTileX != null){
+		if( adjTileX != null ) {
 			var flags = new haxe.EnumFlags<Direction>();
 			var flags = new haxe.EnumFlags<Direction>();
         	flags.set(Left);
         	flags.set(Left);
 			adjTileX.computeEdgesHeight(flags);
 			adjTileX.computeEdgesHeight(flags);
 		}
 		}
 		var adjTileY = getTerrain().getTile(tileX, tileY - 1);
 		var adjTileY = getTerrain().getTile(tileX, tileY - 1);
-		if( adjTileY != null){
+		if( adjTileY != null ) {
 			var flags = new haxe.EnumFlags<Direction>();
 			var flags = new haxe.EnumFlags<Direction>();
         	flags.set(Up);
         	flags.set(Up);
 			adjTileY.computeEdgesHeight(flags);
 			adjTileY.computeEdgesHeight(flags);
 		}
 		}
 		var adjTileXY = getTerrain().getTile(tileX - 1, tileY - 1);
 		var adjTileXY = getTerrain().getTile(tileX - 1, tileY - 1);
-		if( adjTileXY != null){
+		if( adjTileXY != null ) {
 			var flags = new haxe.EnumFlags<Direction>();
 			var flags = new haxe.EnumFlags<Direction>();
         	flags.set(UpLeft);
         	flags.set(UpLeft);
 			adjTileXY.computeEdgesHeight(flags);
 			adjTileXY.computeEdgesHeight(flags);
@@ -86,92 +106,92 @@ class Tile extends h3d.scene.Mesh {
 	}
 	}
 
 
 	public function refresh(){
 	public function refresh(){
-		if(heightMap == null || heightMap.width != getTerrain().heightMapResolution + 1){
+		if( heightMap == null || heightMap.width != getTerrain().heightMapResolution + 1 ) {
 			var oldHeightMap = heightMap;
 			var oldHeightMap = heightMap;
 			heightMap = new h3d.mat.Texture(getTerrain().heightMapResolution + 1, getTerrain().heightMapResolution + 1, [Target], RGBA32F );
 			heightMap = new h3d.mat.Texture(getTerrain().heightMapResolution + 1, getTerrain().heightMapResolution + 1, [Target], RGBA32F );
 			heightMap.wrap = Clamp;
 			heightMap.wrap = Clamp;
 			heightMap.filter = Linear;
 			heightMap.filter = Linear;
 			heightMap.preventAutoDispose();
 			heightMap.preventAutoDispose();
 			heightMap.realloc = null;
 			heightMap.realloc = null;
-			if(oldHeightMap != null){
+			if( oldHeightMap != null ) {
 				getTerrain().copyPass.apply(oldHeightMap, heightMap);
 				getTerrain().copyPass.apply(oldHeightMap, heightMap);
 				oldHeightMap.dispose();
 				oldHeightMap.dispose();
 			}
 			}
 			needNewPixelCapture = true;
 			needNewPixelCapture = true;
 		}
 		}
 
 
-		if(surfaceIndexMap == null || surfaceIndexMap.width != getTerrain().weightMapResolution){
+		if( surfaceIndexMap == null || surfaceIndexMap.width != getTerrain().weightMapResolution ) {
 			var oldSurfaceIndexMap = surfaceIndexMap;
 			var oldSurfaceIndexMap = surfaceIndexMap;
 			surfaceIndexMap = new h3d.mat.Texture(getTerrain().weightMapResolution, getTerrain().weightMapResolution, [Target], RGBA);
 			surfaceIndexMap = new h3d.mat.Texture(getTerrain().weightMapResolution, getTerrain().weightMapResolution, [Target], RGBA);
 			surfaceIndexMap.filter = Nearest;
 			surfaceIndexMap.filter = Nearest;
 			surfaceIndexMap.preventAutoDispose();
 			surfaceIndexMap.preventAutoDispose();
 			surfaceIndexMap.realloc = null;
 			surfaceIndexMap.realloc = null;
-			if(oldSurfaceIndexMap != null){
+			if( oldSurfaceIndexMap != null ) {
 				getTerrain().copyPass.apply(oldSurfaceIndexMap, surfaceIndexMap);
 				getTerrain().copyPass.apply(oldSurfaceIndexMap, surfaceIndexMap);
 				oldSurfaceIndexMap.dispose();
 				oldSurfaceIndexMap.dispose();
 			}
 			}
 		}
 		}
 
 
-		if( getTerrain().surfaces.length > 0 && (surfaceWeights.length != getTerrain().surfaces.length || surfaceWeights[0].width != getTerrain().weightMapResolution)){
+		if( getTerrain().surfaces.length > 0 && (surfaceWeights.length != getTerrain().surfaces.length || surfaceWeights[0].width != getTerrain().weightMapResolution) ) {
 			var oldArray = surfaceWeights;
 			var oldArray = surfaceWeights;
 			surfaceWeights = new Array<h3d.mat.Texture>();
 			surfaceWeights = new Array<h3d.mat.Texture>();
-			surfaceWeights = [for (i in 0...getTerrain().surfaces.length) null];
-			for(i in 0 ... surfaceWeights.length){
+			surfaceWeights = [for( i in 0...getTerrain().surfaces.length ) null];
+			for( i in 0 ... surfaceWeights.length ) {
 				surfaceWeights[i] = new h3d.mat.Texture(getTerrain().weightMapResolution, getTerrain().weightMapResolution, [Target], R8);
 				surfaceWeights[i] = new h3d.mat.Texture(getTerrain().weightMapResolution, getTerrain().weightMapResolution, [Target], R8);
 				surfaceWeights[i].wrap = Clamp;
 				surfaceWeights[i].wrap = Clamp;
 				surfaceWeights[i].preventAutoDispose();
 				surfaceWeights[i].preventAutoDispose();
 				surfaceWeights[i].realloc = null;
 				surfaceWeights[i].realloc = null;
-				if(i < oldArray.length && oldArray[i] != null)
+				if( i < oldArray.length && oldArray[i] != null )
 					getTerrain().copyPass.apply(oldArray[i], surfaceWeights[i]);
 					getTerrain().copyPass.apply(oldArray[i], surfaceWeights[i]);
 			}
 			}
 			generateWeightArray();
 			generateWeightArray();
 
 
-			for(i in 0 ... oldArray.length)
+			for( i in 0 ... oldArray.length )
 				if( oldArray[i] != null) oldArray[i].dispose();
 				if( oldArray[i] != null) oldArray[i].dispose();
 		}
 		}
 	}
 	}
 
 
-	public function generateWeightArray(){
-		if(surfaceWeightArray == null || surfaceWeightArray.width != getTerrain().weightMapResolution || surfaceWeightArray.get_layerCount() != surfaceWeights.length){
-			if(surfaceWeightArray != null) surfaceWeightArray.dispose();
+	public function generateWeightArray() {
+		if( surfaceWeightArray == null || surfaceWeightArray.width != getTerrain().weightMapResolution || surfaceWeightArray.get_layerCount() != surfaceWeights.length ) {
+			if( surfaceWeightArray != null ) surfaceWeightArray.dispose();
 			surfaceWeightArray = new h3d.mat.TextureArray(getTerrain().weightMapResolution, getTerrain().weightMapResolution, surfaceWeights.length, [Target], R8);
 			surfaceWeightArray = new h3d.mat.TextureArray(getTerrain().weightMapResolution, getTerrain().weightMapResolution, surfaceWeights.length, [Target], R8);
 			surfaceWeightArray.wrap = Clamp;
 			surfaceWeightArray.wrap = Clamp;
 			surfaceWeightArray.preventAutoDispose();
 			surfaceWeightArray.preventAutoDispose();
 			surfaceWeightArray.realloc = null;
 			surfaceWeightArray.realloc = null;
 		}
 		}
-		for(i in 0 ... surfaceWeights.length)
-			if(surfaceWeights[i] != null) getTerrain().copyPass.apply(surfaceWeights[i], surfaceWeightArray, None, null, i);
+		for( i in 0 ... surfaceWeights.length )
+			if( surfaceWeights[i] != null ) getTerrain().copyPass.apply(surfaceWeights[i], surfaceWeightArray, None, null, i);
 	}
 	}
 
 
 	public function computeEdgesHeight(flag : haxe.EnumFlags<Direction>){
 	public function computeEdgesHeight(flag : haxe.EnumFlags<Direction>){
 
 
-		if(heightMap == null) return;
+		if( heightMap == null ) return;
 		var pixels : hxd.Pixels.PixelsFloat = getHeightPixels();
 		var pixels : hxd.Pixels.PixelsFloat = getHeightPixels();
 
 
-		if(flag.has(Left)){
+		if( flag.has(Left) ) {
 			var adjTileX = getTerrain().getTile(tileX + 1, tileY);
 			var adjTileX = getTerrain().getTile(tileX + 1, tileY);
 			var adjHeightMapX = adjTileX != null ? adjTileX.heightMap : null;
 			var adjHeightMapX = adjTileX != null ? adjTileX.heightMap : null;
-			if(adjHeightMapX != null){
+			if( adjHeightMapX != null ) {
 				var adjpixels : hxd.Pixels.PixelsFloat = adjTileX.getHeightPixels();
 				var adjpixels : hxd.Pixels.PixelsFloat = adjTileX.getHeightPixels();
-				for( i in 0 ... heightMap.height - 1){
+				for( i in 0 ... heightMap.height - 1 ) {
 					pixels.setPixelF(heightMap.width - 1, i, adjpixels.getPixelF(0,i) );
 					pixels.setPixelF(heightMap.width - 1, i, adjpixels.getPixelF(0,i) );
 				}
 				}
 			}
 			}
 		}
 		}
-		if(flag.has(Up)){
+		if( flag.has(Up) ) {
 			var adjTileY = getTerrain().getTile(tileX, tileY + 1);
 			var adjTileY = getTerrain().getTile(tileX, tileY + 1);
 			var adjHeightMapY = adjTileY != null ? adjTileY.heightMap : null;
 			var adjHeightMapY = adjTileY != null ? adjTileY.heightMap : null;
-			if(adjHeightMapY != null){
+			if( adjHeightMapY != null ) {
 				var adjpixels : hxd.Pixels.PixelsFloat = adjTileY.getHeightPixels();
 				var adjpixels : hxd.Pixels.PixelsFloat = adjTileY.getHeightPixels();
-				for( i in 0 ... heightMap.width - 1){
+				for( i in 0 ... heightMap.width - 1) {
 					pixels.setPixelF(i, heightMap.height - 1, adjpixels.getPixelF(i,0) );
 					pixels.setPixelF(i, heightMap.height - 1, adjpixels.getPixelF(i,0) );
 				}
 				}
 			}
 			}
 		}
 		}
-		if(flag.has(UpLeft)){
+		if( flag.has(UpLeft) ) {
 			var adjTileXY = getTerrain().getTile(tileX + 1, tileY + 1);
 			var adjTileXY = getTerrain().getTile(tileX + 1, tileY + 1);
 			var adjHeightMapXY = adjTileXY != null ? adjTileXY.heightMap : null;
 			var adjHeightMapXY = adjTileXY != null ? adjTileXY.heightMap : null;
-			if(adjHeightMapXY != null){
+			if( adjHeightMapXY != null ) {
 				var adjpixels : hxd.Pixels.PixelsFloat = adjTileXY.getHeightPixels();
 				var adjpixels : hxd.Pixels.PixelsFloat = adjTileXY.getHeightPixels();
 				pixels.setPixelF(heightMap.width - 1, heightMap.height - 1, adjpixels.getPixelF(0,0));
 				pixels.setPixelF(heightMap.width - 1, heightMap.height - 1, adjpixels.getPixelF(0,0));
 			}
 			}
@@ -181,8 +201,8 @@ class Tile extends h3d.scene.Mesh {
 		needNewPixelCapture = false;
 		needNewPixelCapture = false;
 	}
 	}
 
 
-	public function computeEdgesNormals(){
-		if(grid.normals == null) return;
+	public function computeEdgesNormals() {
+		if( grid.normals == null ) return;
 		var t0 = new h3d.col.Point(); var t1 = new h3d.col.Point(); var t2 = new h3d.col.Point();
 		var t0 = new h3d.col.Point(); var t1 = new h3d.col.Point(); var t2 = new h3d.col.Point();
 		var triCount = Std.int(grid.triCount() / grid.width);
 		var triCount = Std.int(grid.triCount() / grid.width);
 		var vertexCount = grid.width + 1;
 		var vertexCount = grid.width + 1;
@@ -191,7 +211,7 @@ class Tile extends h3d.scene.Mesh {
 		var istep = triCount * 3 - 6;
 		var istep = triCount * 3 - 6;
 		var i0, i1, i2 : Int = 0;
 		var i0, i1, i2 : Int = 0;
 
 
-		inline function computeVertexPos(tile : Tile){
+		inline function computeVertexPos( tile : Tile ) {
 			t0.load(tile.grid.points[i0]); t1.load(tile.grid.points[i1]); t2.load(tile.grid.points[i2]);
 			t0.load(tile.grid.points[i0]); t1.load(tile.grid.points[i1]); t2.load(tile.grid.points[i2]);
 			t0.z += tile.getHeight(t0.x / getTerrain().tileSize, t0.y / getTerrain().tileSize);
 			t0.z += tile.getHeight(t0.x / getTerrain().tileSize, t0.y / getTerrain().tileSize);
 			t1.z += tile.getHeight(t1.x / getTerrain().tileSize, t1.y / getTerrain().tileSize);
 			t1.z += tile.getHeight(t1.x / getTerrain().tileSize, t1.y / getTerrain().tileSize);
@@ -223,21 +243,21 @@ class Tile extends h3d.scene.Mesh {
 		var adjDownRightTile = getTerrain().getTile(tileX - 1, tileY - 1);
 		var adjDownRightTile = getTerrain().getTile(tileX - 1, tileY - 1);
 		var adjDownRightGrid = adjDownRightTile != null ? adjDownRightTile.grid: null;
 		var adjDownRightGrid = adjDownRightTile != null ? adjDownRightTile.grid: null;
 
 
-		if(adjUpGrid != null && adjUpGrid.normals != null){
+		if( adjUpGrid != null && adjUpGrid.normals != null ) {
 			var pos = 0;
 			var pos = 0;
-			for( i in 0 ... vertexCount)
+			for( i in 0 ... vertexCount )
 				adjUpGrid.normals[i].set(0,0,0);
 				adjUpGrid.normals[i].set(0,0,0);
 			for( i in 0 ... triCount ) {
 			for( i in 0 ... triCount ) {
 				i0 = adjUpGrid.idx[pos++]; i1 = adjUpGrid.idx[pos++]; i2 = adjUpGrid.idx[pos++];
 				i0 = adjUpGrid.idx[pos++]; i1 = adjUpGrid.idx[pos++]; i2 = adjUpGrid.idx[pos++];
 				computeVertexPos(adjUpTile);
 				computeVertexPos(adjUpTile);
 				var n = computeNormal();
 				var n = computeNormal();
-				if(i0 <= adjUpGrid.width){ adjUpGrid.normals[i0].x += n.x; adjUpGrid.normals[i0].y += n.y; adjUpGrid.normals[i0].z += n.z;}
-				if(i1 <= adjUpGrid.width){ adjUpGrid.normals[i1].x += n.x; adjUpGrid.normals[i1].y += n.y; adjUpGrid.normals[i1].z += n.z;}
-				if(i2 <= adjUpGrid.width){ adjUpGrid.normals[i2].x += n.x; adjUpGrid.normals[i2].y += n.y; adjUpGrid.normals[i2].z += n.z;}
+				if( i0 <= adjUpGrid.width ) { adjUpGrid.normals[i0].x += n.x; adjUpGrid.normals[i0].y += n.y; adjUpGrid.normals[i0].z += n.z;}
+				if( i1 <= adjUpGrid.width ) { adjUpGrid.normals[i1].x += n.x; adjUpGrid.normals[i1].y += n.y; adjUpGrid.normals[i1].z += n.z;}
+				if( i2 <= adjUpGrid.width ) { adjUpGrid.normals[i2].x += n.x; adjUpGrid.normals[i2].y += n.y; adjUpGrid.normals[i2].z += n.z;}
 			}
 			}
-			for( i in 0 ... vertexCount)
+			for( i in 0 ... vertexCount )
 				adjUpGrid.normals[i].normalize();
 				adjUpGrid.normals[i].normalize();
-			for( i in 1 ... vertexCount - 1){
+			for( i in 1 ... vertexCount - 1 ) {
 				var n = grid.normals[s + i].add(adjUpGrid.normals[i]);
 				var n = grid.normals[s + i].add(adjUpGrid.normals[i]);
 				n.normalize();
 				n.normalize();
 				grid.normals[s + i].load(n);
 				grid.normals[s + i].load(n);
@@ -245,7 +265,7 @@ class Tile extends h3d.scene.Mesh {
 			}
 			}
 		}
 		}
 
 
-		if(adjDownGrid != null && adjDownGrid.normals != null){
+		if( adjDownGrid != null && adjDownGrid.normals != null ) {
 			var pos = triCount * (adjDownGrid.width - 1) * 3;
 			var pos = triCount * (adjDownGrid.width - 1) * 3;
 			for( i in 0 ... vertexCount)
 			for( i in 0 ... vertexCount)
 				adjDownGrid.normals[s + i].set(0,0,0);
 				adjDownGrid.normals[s + i].set(0,0,0);
@@ -253,13 +273,13 @@ class Tile extends h3d.scene.Mesh {
 				i0 = adjDownGrid.idx[pos++]; i1 = adjDownGrid.idx[pos++]; i2 = adjDownGrid.idx[pos++];
 				i0 = adjDownGrid.idx[pos++]; i1 = adjDownGrid.idx[pos++]; i2 = adjDownGrid.idx[pos++];
 				computeVertexPos(adjDownTile);
 				computeVertexPos(adjDownTile);
 				var n = computeNormal();
 				var n = computeNormal();
-				if(i0 >= (adjDownGrid.width * adjDownGrid.height + adjDownGrid.height)){ adjDownGrid.normals[i0].x += n.x; adjDownGrid.normals[i0].y += n.y; adjDownGrid.normals[i0].z += n.z;}
-				if(i1 >= (adjDownGrid.width * adjDownGrid.height + adjDownGrid.height)){ adjDownGrid.normals[i1].x += n.x; adjDownGrid.normals[i1].y += n.y; adjDownGrid.normals[i1].z += n.z;}
-				if(i2 >= (adjDownGrid.width * adjDownGrid.height + adjDownGrid.height)){ adjDownGrid.normals[i2].x += n.x; adjDownGrid.normals[i2].y += n.y; adjDownGrid.normals[i2].z += n.z;}
+				if( i0 >= (adjDownGrid.width * adjDownGrid.height + adjDownGrid.height) ) { adjDownGrid.normals[i0].x += n.x; adjDownGrid.normals[i0].y += n.y; adjDownGrid.normals[i0].z += n.z;}
+				if( i1 >= (adjDownGrid.width * adjDownGrid.height + adjDownGrid.height) ) { adjDownGrid.normals[i1].x += n.x; adjDownGrid.normals[i1].y += n.y; adjDownGrid.normals[i1].z += n.z;}
+				if( i2 >= (adjDownGrid.width * adjDownGrid.height + adjDownGrid.height) ) { adjDownGrid.normals[i2].x += n.x; adjDownGrid.normals[i2].y += n.y; adjDownGrid.normals[i2].z += n.z;}
 			}
 			}
-			for( i in 1 ... vertexCount - 1)
+			for( i in 1 ... vertexCount - 1 )
 				adjDownGrid.normals[s + i].normalize();
 				adjDownGrid.normals[s + i].normalize();
-			for( i in 1 ... vertexCount - 1){
+			for( i in 1 ... vertexCount - 1 ){
 				var n = grid.normals[i].add(adjDownGrid.normals[s + i]);
 				var n = grid.normals[i].add(adjDownGrid.normals[s + i]);
 				n.normalize();
 				n.normalize();
 				grid.normals[i].load(n);
 				grid.normals[i].load(n);
@@ -267,25 +287,25 @@ class Tile extends h3d.scene.Mesh {
 			}
 			}
 		}
 		}
 
 
-		if(adjLeftGrid != null && adjLeftGrid.normals != null){
+		if( adjLeftGrid != null && adjLeftGrid.normals != null ) {
 			var pos = 0;
 			var pos = 0;
 			var istep = triCount * 3 - 6;
 			var istep = triCount * 3 - 6;
 			var needStep = false;
 			var needStep = false;
-			for( i in 0 ... vertexCount)
+			for( i in 0 ... vertexCount )
 				adjLeftGrid.normals[i * step].set(0,0,0);
 				adjLeftGrid.normals[i * step].set(0,0,0);
 			for( i in 0 ... triCount ) {
 			for( i in 0 ... triCount ) {
 				i0 = adjLeftGrid.idx[pos++]; i1 = adjLeftGrid.idx[pos++]; i2 = adjLeftGrid.idx[pos++];
 				i0 = adjLeftGrid.idx[pos++]; i1 = adjLeftGrid.idx[pos++]; i2 = adjLeftGrid.idx[pos++];
 				computeVertexPos(adjLeftTile);
 				computeVertexPos(adjLeftTile);
 				var n = computeNormal();
 				var n = computeNormal();
-				if(i0 % (adjLeftGrid.width + 1) == 0){ adjLeftGrid.normals[i0].x += n.x; adjLeftGrid.normals[i0].y += n.y; adjLeftGrid.normals[i0].z += n.z;}
-				if(i1 % (adjLeftGrid.width + 1) == 0){ adjLeftGrid.normals[i1].x += n.x; adjLeftGrid.normals[i1].y += n.y; adjLeftGrid.normals[i1].z += n.z;}
-				if(i2 % (adjLeftGrid.width + 1) == 0){ adjLeftGrid.normals[i2].x += n.x; adjLeftGrid.normals[i2].y += n.y; adjLeftGrid.normals[i2].z += n.z;}
-				if(needStep) pos += istep;
+				if( i0 % (adjLeftGrid.width + 1) == 0 ) { adjLeftGrid.normals[i0].x += n.x; adjLeftGrid.normals[i0].y += n.y; adjLeftGrid.normals[i0].z += n.z;}
+				if( i1 % (adjLeftGrid.width + 1) == 0 ) { adjLeftGrid.normals[i1].x += n.x; adjLeftGrid.normals[i1].y += n.y; adjLeftGrid.normals[i1].z += n.z;}
+				if( i2 % (adjLeftGrid.width + 1) == 0 ) { adjLeftGrid.normals[i2].x += n.x; adjLeftGrid.normals[i2].y += n.y; adjLeftGrid.normals[i2].z += n.z;}
+				if( needStep) pos += istep;
 				needStep = !needStep;
 				needStep = !needStep;
 			}
 			}
-			for( i in 0 ... vertexCount)
+			for( i in 0 ... vertexCount )
 				adjLeftGrid.normals[i * step].normalize();
 				adjLeftGrid.normals[i * step].normalize();
-			for( i in 1 ... vertexCount - 1){
+			for( i in 1 ... vertexCount - 1 ){
 				var n = grid.normals[i * step + (step - 1)].add(adjLeftGrid.normals[i * step]);
 				var n = grid.normals[i * step + (step - 1)].add(adjLeftGrid.normals[i * step]);
 				n.normalize();
 				n.normalize();
 				grid.normals[i * step + (step - 1)].load(n);
 				grid.normals[i * step + (step - 1)].load(n);
@@ -293,25 +313,25 @@ class Tile extends h3d.scene.Mesh {
 			}
 			}
 		}
 		}
 
 
-		if(adjRightGrid != null && adjRightGrid.normals != null){
+		if( adjRightGrid != null && adjRightGrid.normals != null ) {
 			var pos = (triCount - 2) * 3;
 			var pos = (triCount - 2) * 3;
 			var istep = (triCount - 2) * 3;
 			var istep = (triCount - 2) * 3;
 			var needStep = false;
 			var needStep = false;
-			for( i in 0 ... vertexCount)
+			for( i in 0 ... vertexCount )
 				adjRightGrid.normals[i * step + (step - 1)].set(0,0,0);
 				adjRightGrid.normals[i * step + (step - 1)].set(0,0,0);
 			for( i in 0 ... triCount ) {
 			for( i in 0 ... triCount ) {
 				i0 = adjRightGrid.idx[pos++]; i1 = adjRightGrid.idx[pos++]; i2 = adjRightGrid.idx[pos++];
 				i0 = adjRightGrid.idx[pos++]; i1 = adjRightGrid.idx[pos++]; i2 = adjRightGrid.idx[pos++];
 				computeVertexPos(adjRightTile);
 				computeVertexPos(adjRightTile);
 				var n = computeNormal();
 				var n = computeNormal();
-				if((i0 + 1) % (adjRightGrid.width + 1) == 0){ adjRightGrid.normals[i0].x += n.x; adjRightGrid.normals[i0].y += n.y; adjRightGrid.normals[i0].z += n.z;}
-				if((i1 + 1) % (adjRightGrid.width + 1) == 0){ adjRightGrid.normals[i1].x += n.x; adjRightGrid.normals[i1].y += n.y; adjRightGrid.normals[i1].z += n.z;}
-				if((i2 + 1) % (adjRightGrid.width + 1) == 0){ adjRightGrid.normals[i2].x += n.x; adjRightGrid.normals[i2].y += n.y; adjRightGrid.normals[i2].z += n.z;}
-				if(needStep) pos += istep;
+				if( (i0 + 1) % (adjRightGrid.width + 1) == 0 ) { adjRightGrid.normals[i0].x += n.x; adjRightGrid.normals[i0].y += n.y; adjRightGrid.normals[i0].z += n.z; }
+				if( (i1 + 1) % (adjRightGrid.width + 1) == 0 ) { adjRightGrid.normals[i1].x += n.x; adjRightGrid.normals[i1].y += n.y; adjRightGrid.normals[i1].z += n.z; }
+				if( (i2 + 1) % (adjRightGrid.width + 1) == 0 ) { adjRightGrid.normals[i2].x += n.x; adjRightGrid.normals[i2].y += n.y; adjRightGrid.normals[i2].z += n.z; }
+				if( needStep) pos += istep;
 				needStep = !needStep;
 				needStep = !needStep;
 			}
 			}
-			for( i in 0 ... vertexCount)
+			for( i in 0 ... vertexCount )
 				adjRightGrid.normals[i * step + (step - 1)].normalize();
 				adjRightGrid.normals[i * step + (step - 1)].normalize();
-			for( i in 1 ... vertexCount - 1){
+			for( i in 1 ... vertexCount - 1 ) {
 				var n = grid.normals[i * step].add(adjRightGrid.normals[i * step + (step - 1)]);
 				var n = grid.normals[i * step].add(adjRightGrid.normals[i * step + (step - 1)]);
 				n.normalize();
 				n.normalize();
 				grid.normals[i * step].load(n);
 				grid.normals[i * step].load(n);
@@ -325,7 +345,7 @@ class Tile extends h3d.scene.Mesh {
 		var upRight = step * grid.height;
 		var upRight = step * grid.height;
 
 
 		var n = new h3d.col.Point();
 		var n = new h3d.col.Point();
-		if(adjUpRightGrid != null && adjUpRightGrid.normals != null){
+		if( adjUpRightGrid != null && adjUpRightGrid.normals != null ) {
 			var pos = (triCount) * 3 - 6;
 			var pos = (triCount) * 3 - 6;
 			i0 = adjUpRightGrid.idx[pos++]; i1 = adjUpRightGrid.idx[pos++]; i2 = adjUpRightGrid.idx[pos++];
 			i0 = adjUpRightGrid.idx[pos++]; i1 = adjUpRightGrid.idx[pos++]; i2 = adjUpRightGrid.idx[pos++];
 			computeVertexPos(adjUpRightTile);
 			computeVertexPos(adjUpRightTile);
@@ -335,34 +355,34 @@ class Tile extends h3d.scene.Mesh {
 			n = n.add(computeNormal());
 			n = n.add(computeNormal());
 			n.normalize();
 			n.normalize();
 		}
 		}
-		if(adjRightGrid != null && adjRightGrid.normals != null) n = n.add(adjRightGrid.normals[topLeft]);
-		if(adjUpGrid != null && adjUpGrid.normals != null) n = n.add(adjUpGrid.normals[downRight]);
+		if( adjRightGrid != null && adjRightGrid.normals != null ) n = n.add(adjRightGrid.normals[topLeft]);
+		if( adjUpGrid != null && adjUpGrid.normals != null ) n = n.add(adjUpGrid.normals[downRight]);
 		n = n.add(grid.normals[upRight]);
 		n = n.add(grid.normals[upRight]);
 		n.normalize();
 		n.normalize();
-		if(adjUpRightGrid != null && adjUpRightGrid.normals != null) adjUpRightGrid.normals[downLeft].load(n);
-		if(adjRightGrid != null && adjRightGrid.normals != null) adjRightGrid.normals[topLeft].load(n);
-		if(adjUpGrid != null && adjUpGrid.normals != null) adjUpGrid.normals[downRight].load(n);
+		if( adjUpRightGrid != null && adjUpRightGrid.normals != null ) adjUpRightGrid.normals[downLeft].load(n);
+		if( adjRightGrid != null && adjRightGrid.normals != null ) adjRightGrid.normals[topLeft].load(n);
+		if( adjUpGrid != null && adjUpGrid.normals != null ) adjUpGrid.normals[downRight].load(n);
 		grid.normals[upRight].load(n);
 		grid.normals[upRight].load(n);
 
 
 		n.set(0,0,0);
 		n.set(0,0,0);
-		if(adjUpLeftGrid != null && adjUpLeftGrid.normals != null){
+		if( adjUpLeftGrid != null && adjUpLeftGrid.normals != null ) {
 			var pos = 0;
 			var pos = 0;
 			i0 = adjUpLeftGrid.idx[pos++]; i1 = adjUpLeftGrid.idx[pos++]; i2 = adjUpLeftGrid.idx[pos++];
 			i0 = adjUpLeftGrid.idx[pos++]; i1 = adjUpLeftGrid.idx[pos++]; i2 = adjUpLeftGrid.idx[pos++];
 			computeVertexPos(adjUpLeftTile);
 			computeVertexPos(adjUpLeftTile);
 			n = computeNormal();
 			n = computeNormal();
 			n.normalize();
 			n.normalize();
 		}
 		}
-		if(adjLeftGrid != null && adjLeftGrid.normals != null) n = n.add(adjLeftGrid.normals[upRight]);
-		if(adjUpGrid != null && adjUpGrid.normals != null) n = n.add(adjUpGrid.normals[downLeft]);
+		if( adjLeftGrid != null && adjLeftGrid.normals != null ) n = n.add(adjLeftGrid.normals[upRight]);
+		if( adjUpGrid != null && adjUpGrid.normals != null ) n = n.add(adjUpGrid.normals[downLeft]);
 		n = n.add(grid.normals[topLeft]);
 		n = n.add(grid.normals[topLeft]);
 		n.normalize();
 		n.normalize();
-		if(adjUpLeftGrid != null && adjUpLeftGrid.normals != null) adjUpLeftGrid.normals[downRight].load(n);
-		if(adjLeftGrid != null && adjLeftGrid.normals != null) adjLeftGrid.normals[upRight].load(n);
-		if(adjUpGrid != null && adjUpGrid.normals != null) adjUpGrid.normals[downLeft].load(n);
+		if( adjUpLeftGrid != null && adjUpLeftGrid.normals != null ) adjUpLeftGrid.normals[downRight].load(n);
+		if( adjLeftGrid != null && adjLeftGrid.normals != null ) adjLeftGrid.normals[upRight].load(n);
+		if( adjUpGrid != null && adjUpGrid.normals != null ) adjUpGrid.normals[downLeft].load(n);
 		grid.normals[topLeft].load(n);
 		grid.normals[topLeft].load(n);
 
 
 		n.set(0,0,0);
 		n.set(0,0,0);
-		if(adjDownLeftGrid != null && adjDownLeftGrid.normals != null){
+		if( adjDownLeftGrid != null && adjDownLeftGrid.normals != null ) {
 			var pos = (triCount) * 3 * (adjDownLeftGrid.height - 1) ;
 			var pos = (triCount) * 3 * (adjDownLeftGrid.height - 1) ;
 			i0 = adjDownLeftGrid.idx[pos++]; i1 = adjDownLeftGrid.idx[pos++]; i2 = adjDownLeftGrid.idx[pos++];
 			i0 = adjDownLeftGrid.idx[pos++]; i1 = adjDownLeftGrid.idx[pos++]; i2 = adjDownLeftGrid.idx[pos++];
 			computeVertexPos(adjDownLeftTile);
 			computeVertexPos(adjDownLeftTile);
@@ -372,48 +392,48 @@ class Tile extends h3d.scene.Mesh {
 			n = n.add(computeNormal());
 			n = n.add(computeNormal());
 			n.normalize();
 			n.normalize();
 		}
 		}
-		if(adjLeftGrid != null && adjLeftGrid.normals != null) n = n.add(adjLeftGrid.normals[downRight]);
-		if(adjDownGrid != null && adjDownGrid.normals != null) n = n.add(adjDownGrid.normals[topLeft]);
+		if( adjLeftGrid != null && adjLeftGrid.normals != null ) n = n.add(adjLeftGrid.normals[downRight]);
+		if( adjDownGrid != null && adjDownGrid.normals != null ) n = n.add(adjDownGrid.normals[topLeft]);
 		n = n.add(grid.normals[downLeft]);
 		n = n.add(grid.normals[downLeft]);
 		n.normalize();
 		n.normalize();
-		if(adjDownLeftGrid != null && adjDownLeftGrid.normals != null) adjDownLeftGrid.normals[upRight].load(n);
-		if(adjLeftGrid != null && adjLeftGrid.normals != null) adjLeftGrid.normals[downRight].load(n);
-		if(adjDownGrid != null && adjDownGrid.normals != null) adjDownGrid.normals[topLeft].load(n);
+		if( adjDownLeftGrid != null && adjDownLeftGrid.normals != null ) adjDownLeftGrid.normals[upRight].load(n);
+		if( adjLeftGrid != null && adjLeftGrid.normals != null ) adjLeftGrid.normals[downRight].load(n);
+		if( adjDownGrid != null && adjDownGrid.normals != null ) adjDownGrid.normals[topLeft].load(n);
 		grid.normals[downLeft].load(n);
 		grid.normals[downLeft].load(n);
 
 
 		n.set(0,0,0);
 		n.set(0,0,0);
-		if(adjDownRightGrid != null && adjDownRightGrid.normals != null){
+		if( adjDownRightGrid != null && adjDownRightGrid.normals != null ) {
 			var pos = triCount * 3 * adjDownRightGrid.width - 3;
 			var pos = triCount * 3 * adjDownRightGrid.width - 3;
 			i0 = adjDownRightGrid.idx[pos++]; i1 = adjDownRightGrid.idx[pos++]; i2 = adjDownRightGrid.idx[pos++];
 			i0 = adjDownRightGrid.idx[pos++]; i1 = adjDownRightGrid.idx[pos++]; i2 = adjDownRightGrid.idx[pos++];
 			computeVertexPos(adjDownRightTile);
 			computeVertexPos(adjDownRightTile);
 			n = computeNormal();
 			n = computeNormal();
 			n.normalize();
 			n.normalize();
 		}
 		}
-		if(adjRightGrid != null && adjRightGrid.normals != null) n = n.add(adjRightGrid.normals[downLeft]);
-		if(adjDownGrid != null && adjDownGrid.normals != null) n = n.add(adjDownGrid.normals[upRight]);
+		if( adjRightGrid != null && adjRightGrid.normals != null ) n = n.add(adjRightGrid.normals[downLeft]);
+		if( adjDownGrid != null && adjDownGrid.normals != null ) n = n.add(adjDownGrid.normals[upRight]);
 		n = n.add(grid.normals[downRight]);
 		n = n.add(grid.normals[downRight]);
 		n.normalize();
 		n.normalize();
-		if(adjDownRightGrid != null && adjDownRightGrid.normals != null) adjDownRightGrid.normals[topLeft].load(n);
-		if(adjRightGrid != null && adjRightGrid.normals != null) adjRightGrid.normals[downLeft].load(n);
-		if(adjDownGrid != null && adjDownGrid.normals != null) adjDownGrid.normals[upRight].load(n);
+		if( adjDownRightGrid != null && adjDownRightGrid.normals != null ) adjDownRightGrid.normals[topLeft].load(n);
+		if( adjRightGrid != null && adjRightGrid.normals != null ) adjRightGrid.normals[downLeft].load(n);
+		if( adjDownGrid != null && adjDownGrid.normals != null ) adjDownGrid.normals[upRight].load(n);
 		grid.normals[downRight].load(n);
 		grid.normals[downRight].load(n);
 
 
-		if(adjUpTile != null) adjUpTile.needAlloc = true;
-		if(adjDownTile != null) adjDownTile.needAlloc = true;
-		if(adjLeftTile != null) adjLeftTile.needAlloc = true;
-		if(adjRightTile != null) adjRightTile.needAlloc = true;
-		if(adjUpLeftTile != null) adjUpLeftTile.needAlloc = true;
-		if(adjDownLeftTile != null) adjDownLeftTile.needAlloc = true;
-		if(adjUpRightTile != null) adjUpRightTile.needAlloc = true;
-		if(adjDownRightTile != null) adjDownRightTile.needAlloc = true;
+		if( adjUpTile != null ) adjUpTile.needAlloc = true;
+		if( adjDownTile != null ) adjDownTile.needAlloc = true;
+		if( adjLeftTile != null ) adjLeftTile.needAlloc = true;
+		if( adjRightTile != null ) adjRightTile.needAlloc = true;
+		if( adjUpLeftTile != null ) adjUpLeftTile.needAlloc = true;
+		if( adjDownLeftTile != null ) adjDownLeftTile.needAlloc = true;
+		if( adjUpRightTile != null ) adjUpRightTile.needAlloc = true;
+		if( adjDownRightTile != null ) adjDownRightTile.needAlloc = true;
 		this.needAlloc = true;
 		this.needAlloc = true;
 	}
 	}
 
 
-	public function computeNormals(){
-		if(grid.normals == null) grid.normals = new Array<h3d.col.Point>();
+	public function computeNormals() {
+		if( grid.normals == null ) grid.normals = new Array<h3d.col.Point>();
 		grid.normals = [
 		grid.normals = [
-		for (i in 0...grid.points.length){
-			if(i < grid.normals.length){
+		for ( i in 0...grid.points.length ) {
+			if( i < grid.normals.length ) {
 				grid.normals[i].set(0,0,0);
 				grid.normals[i].set(0,0,0);
 				grid.normals[i];
 				grid.normals[i];
 			} else
 			} else
@@ -452,9 +472,9 @@ class Tile extends h3d.scene.Mesh {
 
 
 	public function getHeight(u : Float, v : Float, ?fast = false) : Float {
 	public function getHeight(u : Float, v : Float, ?fast = false) : Float {
 		var pixels = getHeightPixels();
 		var pixels = getHeightPixels();
-		if(pixels == null) return 0.0;
-		if(heightMap.filter == Linear && !fast){
-			inline function getPix(u, v){
+		if( pixels == null ) return 0.0;
+		if( heightMap.filter == Linear && !fast ) {
+			inline function getPix(u, v) {
 				return pixels.getPixelF(Std.int(hxd.Math.clamp(u, 0, pixels.width - 1)), Std.int(hxd.Math.clamp(v, 0, pixels.height - 1))).r;
 				return pixels.getPixelF(Std.int(hxd.Math.clamp(u, 0, pixels.width - 1)), Std.int(hxd.Math.clamp(v, 0, pixels.height - 1))).r;
 			}
 			}
 			var px = u * (heightMap.width - 1) + 0.5;
 			var px = u * (heightMap.width - 1) + 0.5;
@@ -480,24 +500,24 @@ class Tile extends h3d.scene.Mesh {
 	}
 	}
 
 
 	public override function dispose() {
 	public override function dispose() {
-		if(heightMap != null) heightMap.dispose();
-		if(surfaceIndexMap != null) surfaceIndexMap.dispose();
-		for(i in 0 ... surfaceWeights.length)
-			if( surfaceWeights[i] != null) surfaceWeights[i].dispose();
+		if( heightMap != null ) heightMap.dispose();
+		if( surfaceIndexMap != null ) surfaceIndexMap.dispose();
+		for( i in 0 ... surfaceWeights.length )
+			if( surfaceWeights[i] != null ) surfaceWeights[i].dispose();
 	}
 	}
 
 
 	var cachedBounds : h3d.col.Bounds;
 	var cachedBounds : h3d.col.Bounds;
 	var cachedHeightBound : Bool = false;
 	var cachedHeightBound : Bool = false;
 	override function emit( ctx:RenderContext ){
 	override function emit( ctx:RenderContext ){
-		if(!isReady()) return;
-		if(cachedBounds == null) {
+		if( !isReady() ) return;
+		if( cachedBounds == null ) {
 			cachedBounds = getBounds();
 			cachedBounds = getBounds();
 			cachedBounds.zMax = 0;
 			cachedBounds.zMax = 0;
 			cachedBounds.zMin = 0;
 			cachedBounds.zMin = 0;
 		}
 		}
-		if(cachedBounds != null && cachedHeightBound == false && heightMap != null){
-			for( u in 0 ... heightMap.width ){
-				for( v in 0 ... heightMap.height ){
+		if( cachedBounds != null && cachedHeightBound == false && heightMap != null ){
+			for( u in 0 ... heightMap.width ) {
+				for( v in 0 ... heightMap.height ) {
 					var h = getHeight(u / heightMap.width, v / heightMap.height, true);
 					var h = getHeight(u / heightMap.width, v / heightMap.height, true);
 					cachedBounds.zMin = cachedBounds.zMin > h ? h : cachedBounds.zMin;
 					cachedBounds.zMin = cachedBounds.zMin > h ? h : cachedBounds.zMin;
 					cachedBounds.zMax = cachedBounds.zMax < h ? h : cachedBounds.zMax;
 					cachedBounds.zMax = cachedBounds.zMax < h ? h : cachedBounds.zMax;
@@ -505,53 +525,62 @@ class Tile extends h3d.scene.Mesh {
 			}
 			}
 			cachedHeightBound = true;
 			cachedHeightBound = true;
 		}
 		}
-		if(ctx.camera.frustum.hasBounds(cachedBounds))
+		if( ctx.camera.frustum.hasBounds(cachedBounds) )
 			super.emit(ctx);
 			super.emit(ctx);
 	}
 	}
 
 
 	override function sync(ctx:RenderContext) {
 	override function sync(ctx:RenderContext) {
-		if(!isReady()) return;
+		if( !isReady() ) return;
 
 
 		shader.SHOW_GRID = getTerrain().showGrid;
 		shader.SHOW_GRID = getTerrain().showGrid;
 		shader.SURFACE_COUNT = getTerrain().surfaces.length;
 		shader.SURFACE_COUNT = getTerrain().surfaces.length;
 		shader.CHECKER = getTerrain().showChecker;
 		shader.CHECKER = getTerrain().showChecker;
 		shader.COMPLEXITY = getTerrain().showComplexity;
 		shader.COMPLEXITY = getTerrain().showComplexity;
+		shader.PARALLAX = getTerrain().parallaxAmount != 0;
 
 
 		shader.heightMapSize = heightMap.width;
 		shader.heightMapSize = heightMap.width;
 		shader.primSize = getTerrain().tileSize;
 		shader.primSize = getTerrain().tileSize;
 		shader.cellSize = getTerrain().cellSize;
 		shader.cellSize = getTerrain().cellSize;
 
 
-		shader.albedoTextures = getTerrain().surfaceArray.albedo;
-		shader.normalTextures = getTerrain().surfaceArray.normal;
-		shader.pbrTextures = getTerrain().surfaceArray.pbr;
-		shader.weightTextures = surfaceWeightArray;
-		shader.heightMap = heightMap;
-		shader.surfaceIndexMap = surfaceIndexMap;
-
-		shader.surfaceParams = getTerrain().surfaceArray.params;
-		shader.secondSurfaceParams = getTerrain().surfaceArray.secondParams;
-		shader.tileIndex.set(tileX, tileY); // = new h3d.Vector(tileX, tileY);
-		shader.parallaxAmount = getTerrain().parallaxAmount;
-		shader.minStep = getTerrain().parallaxMinStep;
-		shader.maxStep = getTerrain().parallaxMaxStep;
-		shader.heightBlendStrength = getTerrain().heightBlendStrength;
-		shader.heightBlendSharpness = getTerrain().heightBlendSharpness;
+		if( !shader.CHECKER && !shader.COMPLEXITY ){
+			shader.albedoTextures = getTerrain().surfaceArray.albedo;
+			shader.normalTextures = getTerrain().surfaceArray.normal;
+			shader.pbrTextures = getTerrain().surfaceArray.pbr;
+			shader.weightTextures = surfaceWeightArray;
+			shader.heightMap = heightMap;
+			shader.surfaceIndexMap = surfaceIndexMap;
+
+			shader.surfaceParams = getTerrain().surfaceArray.params;
+			shader.secondSurfaceParams = getTerrain().surfaceArray.secondParams;
+			shader.tileIndex.set(tileX, tileY); // = new h3d.Vector(tileX, tileY);
+			shader.parallaxAmount = getTerrain().parallaxAmount;
+			shader.minStep = getTerrain().parallaxMinStep;
+			shader.maxStep = getTerrain().parallaxMaxStep;
+			shader.heightBlendStrength = getTerrain().heightBlendStrength;
+			shader.blendSharpness = getTerrain().blendSharpness;
+		}
 	}
 	}
 
 
 	function isReady(){
 	function isReady(){
-		if(primitive == null)
-			return false;
-		if( getTerrain().surfaceArray == null || getTerrain().surfaces.length == 0 || surfaceWeights.length != getTerrain().surfaces.length)
+		if( primitive == null )
 			return false;
 			return false;
+
+		if( !getTerrain().showChecker || getTerrain().showComplexity ) {
+			if( getTerrain().surfaceArray == null || getTerrain().surfaces.length == 0 || surfaceWeights.length != getTerrain().surfaces.length )
+				return false;
+
+			for( i in 0 ... surfaceWeights.length )
+				if( surfaceWeights[i] == null )
+					return false;
+		}
+
 		if( heightMap == null )
 		if( heightMap == null )
 			return false;
 			return false;
-		for( i in 0 ... surfaceWeights.length )
-			if( surfaceWeights[i] == null )
-				return false;
+
 		return true;
 		return true;
 	}
 	}
 
 
-	override function getLocalCollider():h3d.col.Collider {
+	override function getLocalCollider() : h3d.col.Collider {
 		return null;
 		return null;
 	}
 	}
 }
 }

+ 44 - 0
h3d/shader/DistanceFog.hx

@@ -0,0 +1,44 @@
+package h3d.shader;
+
+class DistanceFog extends ScreenShader {
+
+	static var SRC = {
+
+		@param var startDistance : Float;
+		@param var endDistance : Float;
+		@param var density : Float;
+		@param var startColor : Vec3;
+		@param var endColor : Vec3;
+
+		@ignore @param var depthTexture : Channel;
+		@ignore @param var cameraPos : Vec3;
+		@ignore @param var cameraInverseViewProj : Mat4;
+
+		function getPosition( uv: Vec2 ) : Vec3 {
+			var depth = depthTexture.get(uv);
+			var uv2 = uvToScreen(calculatedUV);
+			var isSky = 1 - ceil(depth);
+			depth = mix(depth, 1, isSky);
+			var temp = vec4(uv2, depth, 1) * cameraInverseViewProj;
+			var originWS = temp.xyz / temp.w;
+			return originWS;
+		}
+
+		function fragment() {
+			var calculatedUV = input.uv;
+			var origin = getPosition(calculatedUV);
+			var distance = (origin - cameraPos).length();
+
+			var fog = clamp((distance - startDistance) / (endDistance - startDistance), 0, 1);
+			var fogColor = mix(startColor, endColor, fog);
+			var fogDensity = mix(0, density, fog);
+
+			pixelColor = vec4(fogColor,fogDensity);
+		}
+	};
+
+	public function new() {
+		super();
+	}
+
+}

+ 6 - 1
h3d/shader/SAO.hx

@@ -9,7 +9,7 @@ class SAO extends ScreenShader {
 	static var SRC = {
 	static var SRC = {
 
 
 		@range(4,30) @const var numSamples : Int;
 		@range(4,30) @const var numSamples : Int;
-		@range(1,10) @const var numSpiralTurns : Int;
+		@range(1,10) @const(16) var numSpiralTurns : Int;
 		@const var useWorldUV : Bool;
 		@const var useWorldUV : Bool;
 
 
 		@ignore @param var depthTexture : Channel;
 		@ignore @param var depthTexture : Channel;
@@ -26,6 +26,9 @@ class SAO extends ScreenShader {
 		@ignore @param var screenRatio : Vec2;
 		@ignore @param var screenRatio : Vec2;
 		@ignore @param var fovTan : Float;
 		@ignore @param var fovTan : Float;
 
 
+		@ignore @param var microOcclusion : Channel;
+		@param var microOcclusionIntensity : Float;
+
 		function sampleAO(uv : Vec2, position : Vec3, normal : Vec3, radiusSS : Float, tapIndex : Int, rotationAngle : Float) : Float {
 		function sampleAO(uv : Vec2, position : Vec3, normal : Vec3, radiusSS : Float, tapIndex : Int, rotationAngle : Float) : Float {
 			// returns a unit vector and a screen-space radius for the tap on a unit disk
 			// returns a unit vector and a screen-space radius for the tap on a unit disk
 			// (the caller should scale by the actual disk radius)
 			// (the caller should scale by the actual disk radius)
@@ -76,6 +79,8 @@ class SAO extends ScreenShader {
 			occlusion = 1.0 - occlusion / float(numSamples);
 			occlusion = 1.0 - occlusion / float(numSamples);
 			occlusion = pow(occlusion, 1.0 + intensity).saturate();
 			occlusion = pow(occlusion, 1.0 + intensity).saturate();
 
 
+			occlusion *= mix(1, microOcclusion.get(vUV).r, microOcclusionIntensity);
+
 			output.color = vec4(occlusion.xxx, 1.);
 			output.color = vec4(occlusion.xxx, 1.);
 		}
 		}
 	};
 	};

+ 5 - 0
h3d/shader/pbr/ComputeSH.hx

@@ -30,6 +30,11 @@ class ComputeSH extends h3d.shader.ScreenShader {
 		var coefL21Final : Vec3;
 		var coefL21Final : Vec3;
 		var coefL22Final : Vec3;
 		var coefL22Final : Vec3;
 
 
+		function vertex(){
+			var position = vec3(input.position.x, input.position.y * flipY, 0);
+			output.position = vec4(position, 1.0);
+		}
+
 		function evalSH(dir:Vec3) {
 		function evalSH(dir:Vec3) {
 
 
 			if (ORDER >= 1){
 			if (ORDER >= 1){

+ 0 - 28
h3d/shader/pbr/DistortionAcc.hx

@@ -1,28 +0,0 @@
-package h3d.shader.pbr;
-
-/* Need a RG16F or RG32F Target*/
-
-class DistortionAcc extends hxsl.Shader {
-
-	static var SRC = {
-
-		@input var input : {
-			var uv : Vec2;
-		};
-
-		@param var distortionDir : Sampler2D;
-		@param var intensity : Float;
-
-		var calculatedUV : Vec2;
-		var pixelColor : Vec4;
-
-		function vertex() {
-			calculatedUV = input.uv;
-		}
-
-		function fragment() {
-			var dir = normalize(((distortionDir.get(calculatedUV).xyz - 0.5) * 2.0)).xy * intensity;
-			pixelColor = vec4(dir, 0, 1);
-		}
-	};
-}

+ 112 - 133
h3d/shader/pbr/Terrain.hx

@@ -9,6 +9,7 @@ class Terrain extends hxsl.Shader {
 		@const var SURFACE_COUNT : Int;
 		@const var SURFACE_COUNT : Int;
 		@const var CHECKER : Bool;
 		@const var CHECKER : Bool;
 		@const var COMPLEXITY : Bool;
 		@const var COMPLEXITY : Bool;
+		@const var PARALLAX : Bool;
 
 
 		@param var heightMapSize : Float;
 		@param var heightMapSize : Float;
 		@param var primSize : Float;
 		@param var primSize : Float;
@@ -24,7 +25,8 @@ class Terrain extends hxsl.Shader {
 		@param var secondSurfaceParams : Array<Vec4, SURFACE_COUNT>;
 		@param var secondSurfaceParams : Array<Vec4, SURFACE_COUNT>;
 
 
 		@param var heightBlendStrength : Float;
 		@param var heightBlendStrength : Float;
-		@param var heightBlendSharpness : Float;
+		@param var blendSharpness : Float;
+
 		@param var parallaxAmount : Float;
 		@param var parallaxAmount : Float;
 		@param var minStep : Int;
 		@param var minStep : Int;
 		@param var maxStep : Int;
 		@param var maxStep : Int;
@@ -39,155 +41,78 @@ class Terrain extends hxsl.Shader {
 		var roughnessValue : Float;
 		var roughnessValue : Float;
 		var occlusionValue : Float;
 		var occlusionValue : Float;
 
 
+		var tangentViewPos : Vec3;
+		var tangentFragPos : Vec3;
+
 		function vertex() {
 		function vertex() {
 			calculatedUV = input.position.xy / primSize;
 			calculatedUV = input.position.xy / primSize;
 			var terrainUV = (calculatedUV * (heightMapSize - 1)) / heightMapSize;
 			var terrainUV = (calculatedUV * (heightMapSize - 1)) / heightMapSize;
 			terrainUV += 0.5 / heightMapSize;
 			terrainUV += 0.5 / heightMapSize;
 			transformedPosition += (vec3(0,0, textureLod(heightMap, terrainUV, 0).r) * global.modelView.mat3());
 			transformedPosition += (vec3(0,0, textureLod(heightMap, terrainUV, 0).r) * global.modelView.mat3());
 			TBN = mat3(normalize(cross(transformedNormal, vec3(0,1,0))), normalize(cross(transformedNormal,vec3(-1,0,0))), transformedNormal);
 			TBN = mat3(normalize(cross(transformedNormal, vec3(0,1,0))), normalize(cross(transformedNormal,vec3(-1,0,0))), transformedNormal);
+			tangentViewPos = TBN * camera.position;
+			tangentFragPos = TBN * transformedPosition;
 		}
 		}
 
 
-		function getPOMUV( uv : Vec2, surfaceIndex : Int) : Vec2 {
-			var viewWS = (camera.position - transformedPosition).normalize();
-			var viewNS : Vec3;
-			{
-				var n = transformedNormal.normalize();
-				var transformedTangent = normalize(cross(transformedNormal, vec3(0,1,0)));
-				var tanX = transformedTangent.xyz.normalize();
-				var tanY = n.cross(tanX);
-				viewNS = vec3(viewWS.dot(tanX), viewWS.dot(tanY), viewWS.dot(n)).normalize();
-			}
+		function getWeight( i : Vec3,  uv : Vec2 ) : Vec3 {
+			var weight = vec3(0);
+			weight.x = weightTextures.getLod(vec3(uv, i.x), 0).r;
+			if( i.y != i.x ) weight.y = weightTextures.getLod(vec3(uv, i.y), 0).r;
+			if( i.z != i.x ) weight.z = weightTextures.getLod(vec3(uv, i.z), 0).r;
+			return weight;
+		}
 
 
-			var numLayers = mix(float(maxStep), float(minStep), abs(viewNS.z));
-			var layerDepth = 1 / numLayers;
-			var curLayerDepth = 0.;
-			var delta = (viewNS.xy / viewNS.z) * parallaxAmount / numLayers * 1.0 / surfaceParams[surfaceIndex].x;
-			var curUV = uv;
-			var curDepth = 1 - pbrTextures.get(getsurfaceUV(surfaceIndex, curUV)).a;
-			while( curLayerDepth < curDepth ) {
-				curUV += delta;
-				curDepth =  1 - pbrTextures.getLod( getsurfaceUV(surfaceIndex, curUV), 0).a;
-				curLayerDepth += layerDepth;
+		function getDepth( i : Vec3,  uv : Vec2 ) : Vec3 {
+			var depth = vec3(0);
+			if( w.x > 0 ) depth.x = pbrTextures.getLod(getsurfaceUV(i.x, uv), 0).a - secondSurfaceParams[i.x].x;
+			if( w.y > 0 ) depth.y = pbrTextures.getLod(getsurfaceUV(i.y, uv), 0).a - secondSurfaceParams[i.y].x;
+			if( w.z > 0 ) depth.z = pbrTextures.getLod(getsurfaceUV(i.z, uv), 0).a - secondSurfaceParams[i.z].x;
+			return 1 - depth;
+		}
+
+		var w : Vec3;
+		function getPOMUV( i : Vec3, uv : Vec2 ) : Vec2 {
+			if( !PARALLAX )
+				return uv;
+			else {
+				var viewDir = normalize(tangentViewPos - tangentFragPos);
+				var numLayers = mix(float(maxStep), float(minStep), viewDir.dot(transformedNormal));
+				var layerDepth = 1 / numLayers;
+				var curLayerDepth = 0.;
+				var delta = (viewDir.xy / viewDir.z) * parallaxAmount / numLayers;
+				var curUV = uv;
+				var depth = getDepth(i, curUV);
+				var curDepth = depth.dot(w);
+				var prevDepth = 0.;
+				while( curLayerDepth < curDepth ) {
+					curUV += delta;
+					prevDepth = curDepth;
+					depth = getDepth(i, curUV);
+					curDepth = depth.dot(w);
+					curLayerDepth += layerDepth;
+				}
+				var prevUV = curUV - delta;
+				var after = curDepth - curLayerDepth;
+				var before = prevDepth - curLayerDepth + layerDepth;
+				var pomUV = mix(curUV, prevUV,  after / (after - before));
+				return pomUV;
 			}
 			}
-			var prevUV = curUV - delta;
-			var after = curDepth - curLayerDepth;
-			var before = (1 - pbrTextures.get(vec3(prevUV, surfaceIndex)).a) - curLayerDepth + layerDepth;
-			return mix(curUV, prevUV, after / (after - before));
 		}
 		}
 
 
-		function getsurfaceUV(i : Int, uv : Vec2) : Vec3 {
-			var angle = surfaceParams[i].w;
-			var offset = vec2(surfaceParams[i].y, surfaceParams[i].z);
-			var tilling = surfaceParams[i].x;
-			var worldUV = vec2((uv + tileIndex) * tilling) + offset;
+		function getsurfaceUV( i : Float, uv : Vec2 ) : Vec3 {
+			var id = int(i);
+			var angle = surfaceParams[id].w;
+			var offset = vec2(surfaceParams[id].y, surfaceParams[id].z);
+			var tilling = surfaceParams[id].x;
+			var worldUV = (uv + tileIndex) * tilling + offset;
 			var res = vec2( worldUV.x * cos(angle) - worldUV.y * sin(angle) , worldUV.y * cos(angle) + worldUV.x * sin(angle));
 			var res = vec2( worldUV.x * cos(angle) - worldUV.y * sin(angle) , worldUV.y * cos(angle) + worldUV.x * sin(angle));
 			var surfaceUV = vec3(res, i);
 			var surfaceUV = vec3(res, i);
 			return surfaceUV;
 			return surfaceUV;
 		}
 		}
 
 
 		function fragment() {
 		function fragment() {
-			// Extract participating surfaces from the pixel
-			var texIndex = surfaceIndexMap.get(calculatedUV).rgb;
-
-			var i1 : Int = int(texIndex.r * 255);
-			var uv1 = getPOMUV(calculatedUV, i1);
-			var surfaceUV1 = getsurfaceUV(i1, uv1);
-			var pbr1 = pbrTextures.get(surfaceUV1).rgba;
-			var albedo1 = albedoTextures.get(surfaceUV1).rgb;
-			var normal1 = normalTextures.get(surfaceUV1).rgba;
-			var h1 = pbr1.a;
-			var aw1 = weightTextures.get(vec3(calculatedUV, i1)).r;
-
-			var i2 : Int = int(texIndex.g * 255);
-			var aw2 = weightTextures.get(vec3(calculatedUV, i2)).r;
-
-			var i3 : Int = int(texIndex.b * 255);
-			var aw3 = weightTextures.get(vec3(calculatedUV, i3)).r;
-
-			// Sum of each surface
-			var albedo = vec3(0);
-			var normal = vec4(0,0,0,0);
-			var pbr = vec4(0);
-			var weightSum = 0.0;
-
-			// Keep the surface with the heightest weight for sharpness
-			var maxAlbedo = vec3(0);
-			var maxPbr = vec4(0);
-			var maxNormal = vec4(0);
-			var curMaxWeight = -1.0;
-
-			// Alpha / Height Blend
-			var b1 = 0.0, b2 = 0.0, b3 = 0.0;
-			b1 = mix(aw1, aw1 * h1, heightBlendStrength);
-			albedo += albedo1 * b1;
-			pbr += pbr1 * b1;
-			normal += normal1 * b1;
-
-			// Find the max
-			var maxW = clamp(ceil(b1 - curMaxWeight), 0, 1);
-			curMaxWeight = mix(curMaxWeight, b1, maxW);
-			maxAlbedo = mix(maxAlbedo, albedo1, maxW);
-			maxPbr = mix(maxPbr, pbr1, maxW);
-			maxNormal = mix(maxNormal, normal1, maxW);
-
-			if(aw2 > 0){
-				var uv2 = getPOMUV(calculatedUV, i2);
-				var surfaceUV2 = getsurfaceUV(i2, uv2);
-				var pbr2 = pbrTextures.get(surfaceUV2).rgba;
-				var albedo2 = albedoTextures.get(surfaceUV2).rgb;
-				var normal2 = normalTextures.get(surfaceUV2).rgba;
-				var h2 = pbr2.a;
-				b2 = mix(aw2, aw2 * h2, heightBlendStrength);
-				albedo += albedo2 * b2;
-				pbr += pbr2 * b2;
-				normal += normal2 * b2;
-				maxW = clamp(ceil(b2 - curMaxWeight), 0, 1);
-				curMaxWeight = mix(curMaxWeight, b2, maxW);
-				maxAlbedo = mix(maxAlbedo, albedo2, maxW);
-				maxPbr = mix(maxPbr, pbr2, maxW);
-				maxNormal = mix(maxNormal, normal2, maxW);
-			}
 
 
-			if(aw3 > 0){
-				var uv3 = getPOMUV(calculatedUV, i3);
-				var surfaceUV3 = getsurfaceUV(i3, uv3);
-				var pbr3 = pbrTextures.get(surfaceUV3).rgba;
-				var albedo3 = albedoTextures.get(surfaceUV3).rgb;
-				var normal3 = normalTextures.get(surfaceUV3).rgba;
-				var h3 = pbr3.a;
-				b3 = mix(aw3, aw3 * h3, heightBlendStrength);
-				albedo += albedo3 * b3;
-				pbr += pbr3 * b3;
-				normal += normal3 * b3;
-				maxW = clamp(ceil(b3 - curMaxWeight), 0,1);
-				curMaxWeight = mix(curMaxWeight, b3, maxW);
-				maxAlbedo = mix(maxAlbedo, albedo3, maxW);
-				maxPbr = mix(maxPbr, pbr3, maxW);
-				maxNormal = mix(maxNormal, normal3, maxW);
-			}
-
-			// Normalisation
-			weightSum = b1 + b2 + b3;
-			albedo /= vec3(weightSum);
-			pbr /= vec4(weightSum);
-			normal /= vec4(weightSum);
-
-			// Sharpness
-			albedo = mix(albedo, maxAlbedo, heightBlendSharpness);
-			pbr = mix(pbr, maxPbr, heightBlendSharpness);
-			normal = mix(normal, maxNormal, heightBlendSharpness);
-
-			// Output
-			normal = vec4(unpackNormal(normal), 0.0);
-			pixelColor = vec4(albedo, 1.0);
-			transformedNormal = normalize(normal.xyz) * TBN;
-			roughnessValue = 1 - pbr.g * pbr.g;
-			metalnessValue = pbr.r;
-			occlusionValue = pbr.b;
-			emissiveValue = 0;
-
-			// DEBUG
-			if(CHECKER){
+			if( CHECKER ) {
 				var tile = abs(abs(floor(input.position.x)) % 2 - abs(floor(input.position.y)) % 2);
 				var tile = abs(abs(floor(input.position.x)) % 2 - abs(floor(input.position.y)) % 2);
 				pixelColor = vec4(mix(vec3(0.4), vec3(0.1), tile), 1.0);
 				pixelColor = vec4(mix(vec3(0.4), vec3(0.1), tile), 1.0);
 				transformedNormal = vec3(0,0,1) * TBN;
 				transformedNormal = vec3(0,0,1) * TBN;
@@ -196,7 +121,7 @@ class Terrain extends hxsl.Shader {
 				occlusionValue = 1;
 				occlusionValue = 1;
 				emissiveValue = 0;
 				emissiveValue = 0;
 			}
 			}
-			else if(COMPLEXITY){
+			else if( COMPLEXITY ) {
 				var blendCount = 0 + weightTextures.get(vec3(0)).r * 0;
 				var blendCount = 0 + weightTextures.get(vec3(0)).r * 0;
 				for(i in 0 ... SURFACE_COUNT)
 				for(i in 0 ... SURFACE_COUNT)
 					blendCount += ceil(weightTextures.get(vec3(calculatedUV, i)).r);
 					blendCount += ceil(weightTextures.get(vec3(calculatedUV, i)).r);
@@ -207,7 +132,61 @@ class Terrain extends hxsl.Shader {
 				metalnessValue = 0;
 				metalnessValue = 0;
 				occlusionValue = 1;
 				occlusionValue = 1;
 			}
 			}
-			if(SHOW_GRID){
+			else {
+				var i = surfaceIndexMap.get(calculatedUV).rgb * 255;
+				w = getWeight(i, calculatedUV);
+				var pomUV = getPOMUV(i, calculatedUV);
+				if( PARALLAX ) w = getWeight(i, pomUV);
+				var h = vec3(0);
+				var surfaceUV1 = getsurfaceUV(i.x, pomUV);
+				var surfaceUV2 = getsurfaceUV(i.y, pomUV);
+				var surfaceUV3 = getsurfaceUV(i.z, pomUV);
+				var pbr1 = vec3(0), pbr2 = vec3(0), pbr3 = vec3(0);
+				var pbr1 = pbrTextures.get(surfaceUV1).rgba;
+				var pbr2 = pbrTextures.get(surfaceUV2).rgba;
+				var pbr3 = pbrTextures.get(surfaceUV3).rgba;
+
+				// Height Blend
+				var h = vec3(pbr1.a - secondSurfaceParams[i.x].x, pbr2.a - secondSurfaceParams[i.y].x, pbr3.a - secondSurfaceParams[i.z].x );
+				w = mix(w, vec3(w.x*h.x, w.y*h.y, w.z*h.z), heightBlendStrength);
+
+				// Sharpness
+				var m = max(w.x, max(w.y, w.z));
+				var mw = ceil(w - m + 0.01);
+				w = mix( w, mw, blendSharpness);
+
+				// Blend
+				var albedo = vec3(0);
+				var normal = vec4(0,0,0,0);
+				var pbr = vec4(0);
+				if( w.x > 0 ) {
+					albedo += albedoTextures.get(surfaceUV1).rgb * w.x;
+					normal += normalTextures.get(surfaceUV1).rgba * w.x;
+				}
+				if( w.y > 0 ) {
+					albedo += albedoTextures.get(surfaceUV2).rgb * w.y;
+					normal += normalTextures.get(surfaceUV2).rgba * w.y;
+				}
+				if( w.z > 0 ) {
+					albedo += albedoTextures.get(surfaceUV3).rgb * w.z;
+					normal += normalTextures.get(surfaceUV3).rgba * w.z;
+				}
+				var wSum = w.x + w.y + w.z;
+				albedo /= wSum;
+				pbr = (pbr1 * w.x + pbr2 * w.y + pbr3 * w.z) / wSum;
+				normal /= wSum;
+
+				// Output
+				normal = vec4(unpackNormal(normal), 0.0);
+				pixelColor = vec4(albedo, 1.0);
+				transformedNormal = normalize(normal.xyz) * TBN;
+				roughnessValue = 1 - pbr.g * pbr.g;
+				metalnessValue = pbr.r;
+				occlusionValue = pbr.b;
+				emissiveValue = 0;
+			}
+
+			if( SHOW_GRID ) {
 				var gridColor = vec4(1,0,0,1);
 				var gridColor = vec4(1,0,0,1);
 				var tileEdgeColor = vec4(1,1,0,1);
 				var tileEdgeColor = vec4(1,1,0,1);
 				var grid : Vec2 = ((input.position.xy.mod(cellSize) / cellSize ) - 0.5) * 2.0;
 				var grid : Vec2 = ((input.position.xy.mod(cellSize) / cellSize ) - 0.5) * 2.0;

+ 24 - 1
h3d/shader/pbr/ToneMapping.hx

@@ -12,13 +12,19 @@ class ToneMapping extends ScreenShader {
 
 
 		@const var hasBloom : Bool;
 		@const var hasBloom : Bool;
 		@param var bloom : Sampler2D;
 		@param var bloom : Sampler2D;
+
 		@const var hasDistortion : Bool;
 		@const var hasDistortion : Bool;
 		@param var distortion : Sampler2D;
 		@param var distortion : Sampler2D;
+
+		@const var hasColorGrading : Bool;
+		@param var colorGradingLUT : Sampler2D;
+		@param var lutSize : Float;
+
 		@param var pixelSize : Vec2;
 		@param var pixelSize : Vec2;
 
 
 		function fragment() {
 		function fragment() {
 
 
-			if(hasDistortion){
+			if( hasDistortion)  {
 				var baseUV = calculatedUV;
 				var baseUV = calculatedUV;
 				var distortionVal = distortion.get(baseUV).rg;
 				var distortionVal = distortion.get(baseUV).rg;
 				calculatedUV = baseUV + distortionVal ;
 				calculatedUV = baseUV + distortionVal ;
@@ -41,6 +47,23 @@ class ToneMapping extends ScreenShader {
 			// gamma correct
 			// gamma correct
 			if( !isSRBG ) color.rgb = color.rgb.sqrt();
 			if( !isSRBG ) color.rgb = color.rgb.sqrt();
 
 
+			if( hasColorGrading ) {
+				var uv = min(color.rgb, vec3(1,1,1));
+				var sliceSize = 1.0 / lutSize;
+				var slicePixelSize = sliceSize / lutSize;
+				var sliceInnerSize = slicePixelSize * (lutSize - 1.0);
+				var blueSlice0 = min(floor(uv.b * lutSize), lutSize - 1.0);
+				var blueSlice1 = min(blueSlice0 + 1.0, lutSize - 1.0);
+				var xOffset = slicePixelSize * 0.5 + uv.r * sliceInnerSize;
+				var s0 = xOffset + (blueSlice0 * sliceSize);
+				var s1 = xOffset + (blueSlice1 * sliceSize);
+				var slice0Color = texture(colorGradingLUT, vec2(s0, uv.y)).rgb;
+				var slice1Color = texture(colorGradingLUT, vec2(s1, uv.y)).rgb;
+				var bOffset = mod(uv.b * lutSize, 1.0);
+				var result = mix(slice0Color, slice1Color, bOffset);
+				color.rgb = result;
+			}
+
 			pixelColor = color;
 			pixelColor = color;
 		}
 		}
 	}
 	}

+ 251 - 0
h3d/shader/pbr/VolumeDecal.hx

@@ -0,0 +1,251 @@
+package h3d.shader.pbr;
+
+class DecalOverlay extends hxsl.Shader {
+	static var SRC = {
+
+		@global var global : {
+			var time : Float;
+			var pixelSize : Vec2;
+			@perObject var modelView : Mat4;
+			@perObject var modelViewInverse : Mat4;
+		};
+
+		@global var camera : {
+			var view : Mat4;
+			var proj : Mat4;
+			var position : Vec3;
+			var projFlip : Float;
+			var projDiag : Vec3;
+			var viewProj : Mat4;
+			var inverseViewProj : Mat4;
+			var zNear : Float;
+			var zFar : Float;
+			@var var dir : Vec3;
+		};
+
+		var output : {
+			color : Vec4
+		};
+
+		@const var CENTERED : Bool;
+
+		@global var depthMap : Channel;
+
+		@param var maxAngle : Float;
+		@param var fadePower : Float;
+		@param var fadeStart : Float;
+		@param var fadeEnd : Float;
+		@param var emissive : Float;
+
+		@param var colorTexture : Sampler2D;
+
+		var calculatedUV : Vec2;
+		var pixelColor : Vec4;
+		var pixelTransformedPosition : Vec3;
+		var projectedPosition : Vec4;
+		var localPos : Vec3;
+
+		function outsideBounds() : Bool {
+			return ( localPos.x > 0.5 || localPos.x < -0.5 || localPos.y > 0.5 || localPos.y < -0.5 || localPos.z > 0.5 || localPos.z < -0.5 );
+		}
+
+		function fragment() {
+
+			var matrix = camera.inverseViewProj * global.modelViewInverse;
+			var screenPos = projectedPosition.xy / projectedPosition.w;
+			var depth = depthMap.get(screenToUv(screenPos));
+			var ruv = vec4( screenPos, depth, 1 );
+			var wpos = ruv * matrix;
+			var ppos = ruv * camera.inverseViewProj;
+
+			pixelTransformedPosition = ppos.xyz / ppos.w;
+			localPos = (wpos.xyz / wpos.w);
+			calculatedUV = localPos.xy;
+			var fadeFactor = 1 - clamp( pow( max( 0.0, abs(localPos.z * 2) - fadeStart) / (fadeEnd - fadeStart), fadePower), 0, 1);
+
+			if( CENTERED )
+				calculatedUV += 0.5;
+
+			if(	outsideBounds() )
+				discard;
+
+			var color = colorTexture.get(calculatedUV);
+			pixelColor.rgb = color.rgb + color.rgb * emissive;
+			pixelColor.a = max(max(pixelColor.r, pixelColor.g), pixelColor.b) * fadeFactor;
+		}
+	}
+
+	public function new( ) {
+		super();
+	}
+}
+
+class DecalPBR extends hxsl.Shader {
+
+	static var SRC = {
+
+		@global var global : {
+			var time : Float;
+			var pixelSize : Vec2;
+			@perObject var modelView : Mat4;
+			@perObject var modelViewInverse : Mat4;
+		};
+
+		@global var camera : {
+			var view : Mat4;
+			var proj : Mat4;
+			var position : Vec3;
+			var projFlip : Float;
+			var projDiag : Vec3;
+			var viewProj : Mat4;
+			var inverseViewProj : Mat4;
+			var zNear : Float;
+			var zFar : Float;
+			@var var dir : Vec3;
+		};
+
+		var output : {
+			color : Vec4,
+			normal : Vec3,
+			metalness : Float,
+			roughness : Float,
+			occlusion : Float,
+			emissive : Float,
+			albedoStrength : Float,
+			normalStrength : Float,
+			pbrStrength : Float,
+			emissiveStrength : Float,
+			//depth : Float,
+		};
+
+		@const var CENTERED : Bool;
+		@const var USE_ALBEDO : Bool;
+		@const var USE_NORMAL : Bool;
+		@const var USE_PBR : Bool;
+
+		@param var albedoStrength : Float;
+		@param var normalStrength : Float;
+		@param var pbrStrength : Float;
+
+		@global var depthMap : Channel;
+
+		@param var normal : Vec3;
+		@param var tangent : Vec3;
+
+		@param var minBound : Vec3;
+		@param var maxBound : Vec3;
+		@param var maxAngle : Float;
+		@param var fadePower : Float;
+		@param var fadeStart : Float;
+		@param var fadeEnd : Float;
+		@param var emissive : Float;
+
+		@param var albedoTexture : Sampler2D;
+		@param var normalTexture : Sampler2D;
+		@param var pbrTexture : Sampler2D;
+
+		var calculatedUV : Vec2;
+		var transformedTangent : Vec4;
+		var transformedNormal : Vec3;
+		var pixelTransformedPosition : Vec3;
+		var projectedPosition : Vec4;
+		var pixelColor : Vec4;
+		var prbValues : Vec4;
+		var strength : Vec4;
+		var localPos : Vec3;
+		var alpha : Float;
+
+		function __init__vertex() {
+			transformedNormal = (normal * global.modelView.mat3()).normalize();
+			transformedTangent = vec4((tangent * global.modelView.mat3()).normalize(),1.);
+		}
+
+		function __init__fragment() {
+
+		}
+
+		function getWorlPos( pos : Vec2 ) : Vec3{
+			var depth = depthMap.get(screenToUv(pos)).r;
+			var ruv = vec4( pos, depth, 1 );
+			var wpos = ruv * camera.inverseViewProj;
+			var result = (wpos.xyz / wpos.w);
+			return result;
+		}
+
+		function outsideBounds() : Bool {
+			return ( localPos.x > 0.5 || localPos.x < -0.5 || localPos.y > 0.5 || localPos.y < -0.5 || localPos.z > 0.5 || localPos.z < -0.5 );
+		}
+
+		function fragment() {
+
+			var matrix = camera.inverseViewProj * global.modelViewInverse;
+			var screenPos = projectedPosition.xy / projectedPosition.w;
+			var depth = depthMap.get(screenToUv(screenPos));
+			var ruv = vec4( screenPos, depth, 1 );
+			var wpos = ruv * matrix;
+			var ppos = ruv * camera.inverseViewProj;
+			alpha = 1.0;
+
+			pixelTransformedPosition = ppos.xyz / ppos.w;
+			localPos = (wpos.xyz / wpos.w);
+			calculatedUV = localPos.xy;
+			var fadeFactor = 1 - clamp( pow( max( 0.0, abs(localPos.z * 2) - fadeStart) / (fadeEnd - fadeStart), fadePower), 0, 1);
+
+			if( CENTERED )
+				calculatedUV += 0.5;
+
+			if(	outsideBounds() )
+				discard;
+
+			strength = vec4(0,0,0,0);
+			prbValues = vec4(0,0,0,0);
+
+			if( USE_ALBEDO ) {
+				var albedo = albedoTexture.get(calculatedUV);
+				pixelColor = albedo;
+				strength.r = albedoStrength * albedo.a;
+				alpha = albedo.a;
+			}
+
+			if( USE_NORMAL ) {
+				var worldPos = getWorlPos(screenPos);
+				var ddx = worldPos - getWorlPos(screenPos + vec2(global.pixelSize.x, 0));
+				var ddy = worldPos - getWorlPos(screenPos + vec2(0, global.pixelSize.y));
+				var worldNormal = normalize(cross(ddy, ddx));
+				var worldTangent = cross(worldNormal, vec3(0,1,0));
+				var normal = normalTexture.get(calculatedUV).rgba;
+				var n = worldNormal;
+				var nf = unpackNormal(normal);
+				var tanX = worldTangent.xyz.normalize();
+				var tanY = n.cross(tanX) * -1;
+				transformedNormal = (nf.x * tanX + nf.y * tanY + nf.z * n).normalize();
+				strength.g = normalStrength * alpha;
+			}
+
+			if( USE_PBR ) {
+				var pbr = pbrTexture.get(calculatedUV).rgba;
+				prbValues.r = pbr.r;
+				prbValues.g = 1 - pbr.g * pbr.g;
+				prbValues.b = pbr.b;
+				strength.b = pbrStrength * alpha;
+			}
+
+			//output.color = pixelColor; // Allow override
+			output.normal = transformedNormal;
+			output.metalness = prbValues.r;
+			output.roughness = prbValues.g;
+			output.occlusion = prbValues.b;
+			output.emissive = emissive * alpha;
+			output.albedoStrength = strength.r * fadeFactor;
+			output.normalStrength = strength.g * fadeFactor;
+			output.pbrStrength = strength.b * fadeFactor;
+			output.emissiveStrength = 0;
+			//output.depth = 0; // Dont draw depth again
+		}
+	};
+
+	public function new( ) {
+		super();
+		CENTERED = true;
+	}
+}

+ 1 - 0
hxd/Res.hx

@@ -24,6 +24,7 @@ class Res {
 	public static macro function initLocal() {
 	public static macro function initLocal() {
 		var dir = haxe.macro.Context.definedValue("resourcesPath");
 		var dir = haxe.macro.Context.definedValue("resourcesPath");
 		if( dir == null ) dir = "res";
 		if( dir == null ) dir = "res";
+		dir = haxe.macro.Context.resolvePath(dir);
 		return macro hxd.Res.loader = new hxd.res.Loader(new hxd.fs.LocalFileSystem($v{dir}));
 		return macro hxd.Res.loader = new hxd.res.Loader(new hxd.fs.LocalFileSystem($v{dir}));
 	}
 	}
 
 

+ 8 - 0
hxd/System.hl.hx

@@ -130,9 +130,17 @@ class System {
 			#if hxtelemetry
 			#if hxtelemetry
 			hxt.advance_frame();
 			hxt.advance_frame();
 			#end
 			#end
+			#if hot_reload
+			check_reload();
+			#end
 		}
 		}
 		Sys.exit(0);
 		Sys.exit(0);
 	}
 	}
+	
+	#if hot_reload
+	@:hlNative("std","sys_check_reload")
+	static function check_reload() return false;
+	#end
 
 
 	public dynamic static function reportError( e : Dynamic ) {
 	public dynamic static function reportError( e : Dynamic ) {
 		var stack = haxe.CallStack.toString(haxe.CallStack.exceptionStack());
 		var stack = haxe.CallStack.toString(haxe.CallStack.exceptionStack());

+ 2 - 2
hxd/UString.hx

@@ -10,7 +10,7 @@ abstract UStringImpl(String) from String to String {
 	public var length(get,never) : Int;
 	public var length(get,never) : Int;
 
 
 	inline function get_length() : Int {
 	inline function get_length() : Int {
-		return this.length;
+		return haxe.Utf8.length(this);
 	}
 	}
 
 
 	@:op(a + b) inline static function add( a : UStringImpl, b : UStringImpl ) : String {
 	@:op(a + b) inline static function add( a : UStringImpl, b : UStringImpl ) : String {
@@ -31,4 +31,4 @@ abstract UStringImpl(String) from String to String {
 
 
 }
 }
 
 
-#end
+#end

+ 4 - 1
hxd/Window.hl.hx

@@ -87,6 +87,9 @@ class Window {
 		#if (hldx || hlsdl)
 		#if (hldx || hlsdl)
 		window.resize(width, height);
 		window.resize(width, height);
 		#end
 		#end
+		windowWidth = width;
+		windowHeight = height;
+		for( f in resizeEvents ) f();
 	}
 	}
 
 
 	public function setFullScreen( v : Bool ) : Void {
 	public function setFullScreen( v : Bool ) : Void {
@@ -327,7 +330,7 @@ class Window {
 			1086 => K.NUMPAD_SUB,
 			1086 => K.NUMPAD_SUB,
 			1099 => K.NUMPAD_DOT,
 			1099 => K.NUMPAD_DOT,
 			1084 => K.NUMPAD_DIV,
 			1084 => K.NUMPAD_DIV,
-			
+
 			39 => K.QWERTY_QUOTE,
 			39 => K.QWERTY_QUOTE,
 			44 => K.QWERTY_COMMA,
 			44 => K.QWERTY_COMMA,
 			45 => K.QWERTY_MINUS,
 			45 => K.QWERTY_MINUS,

+ 39 - 14
hxd/Window.js.hx

@@ -12,6 +12,7 @@ class Window {
 	public var mouseLock(get, set) : Bool;
 	public var mouseLock(get, set) : Bool;
 	public var vsync(get, set) : Bool;
 	public var vsync(get, set) : Bool;
 	public var isFocused(get, never) : Bool;
 	public var isFocused(get, never) : Bool;
+	public var propagateKeyEvents : Bool;
 
 
 	var curMouseX : Float = 0.;
 	var curMouseX : Float = 0.;
 	var curMouseY : Float = 0.;
 	var curMouseY : Float = 0.;
@@ -24,23 +25,32 @@ class Window {
 	var curW : Int;
 	var curW : Int;
 	var curH : Int;
 	var curH : Int;
 
 
-	var focused = true;
+	var focused : Bool;
 
 
 	public function new( ?canvas : js.html.CanvasElement, ?globalEvents ) : Void {
 	public function new( ?canvas : js.html.CanvasElement, ?globalEvents ) : Void {
+		var customCanvas = canvas != null;
 		eventTargets = new List();
 		eventTargets = new List();
 		resizeEvents = new List();
 		resizeEvents = new List();
 
 
-		element = canvas == null || globalEvents ? js.Browser.window : canvas;
 		if( canvas == null ) {
 		if( canvas == null ) {
 			canvas = cast js.Browser.document.getElementById("webgl");
 			canvas = cast js.Browser.document.getElementById("webgl");
 			if( canvas == null ) throw "Missing canvas #webgl";
 			if( canvas == null ) throw "Missing canvas #webgl";
-			if( canvas.getAttribute("globalEvents") == "0" )
-				element = canvas;
+			if( canvas.getAttribute("globalEvents") == "1" )
+				globalEvents = true;
 		}
 		}
+
 		this.canvas = canvas;
 		this.canvas = canvas;
+		this.propagateKeyEvents = globalEvents;
+		focused = globalEvents;
+		element = globalEvents ? js.Browser.window : canvas;
 		canvasPos = canvas.getBoundingClientRect();
 		canvasPos = canvas.getBoundingClientRect();
+		// add mousemove on window (track mouse even when outside of component)
+		// unless we're having a custom canvas (prevent leaking the listener)
+		if( customCanvas )
+			canvas.addEventListener("mousemove", onMouseMove);
+		else
+			js.Browser.window.addEventListener("mousemove", onMouseMove);
 		element.addEventListener("mousedown", onMouseDown);
 		element.addEventListener("mousedown", onMouseDown);
-		element.addEventListener("mousemove", onMouseMove);
 		element.addEventListener("mouseup", onMouseUp);
 		element.addEventListener("mouseup", onMouseUp);
 		element.addEventListener("wheel", onMouseWheel);
 		element.addEventListener("wheel", onMouseWheel);
 		element.addEventListener("touchstart", onTouchStart);
 		element.addEventListener("touchstart", onTouchStart);
@@ -51,25 +61,28 @@ class Window {
 		element.addEventListener("keypress", onKeyPress);
 		element.addEventListener("keypress", onKeyPress);
 		element.addEventListener("blur", onFocus.bind(false));
 		element.addEventListener("blur", onFocus.bind(false));
 		element.addEventListener("focus", onFocus.bind(true));
 		element.addEventListener("focus", onFocus.bind(true));
-		if( element == canvas ) {
-			canvas.setAttribute("tabindex","1"); // allow focus
-			canvas.style.outline = 'none';
-		} else {
+		canvas.oncontextmenu = function(e){
+			e.stopPropagation();
+			e.preventDefault();
+			return false;
+		};
+		if( globalEvents ) {
+			// make first mousedown on canvas trigger event
 			canvas.addEventListener("mousedown", function(e) {
 			canvas.addEventListener("mousedown", function(e) {
 				onMouseDown(e);
 				onMouseDown(e);
 				e.stopPropagation();
 				e.stopPropagation();
 				e.preventDefault();
 				e.preventDefault();
 			});
 			});
-			canvas.oncontextmenu = function(e){
-				e.stopPropagation();
-				e.preventDefault();
-				return false;
-			};
 			element.addEventListener("contextmenu",function(e) {
 			element.addEventListener("contextmenu",function(e) {
 				e.stopPropagation();
 				e.stopPropagation();
 				e.preventDefault();
 				e.preventDefault();
 				return false;
 				return false;
 			});
 			});
+		} else {
+			// allow focus
+			if( canvas.getAttribute("tabindex") == null )
+				canvas.setAttribute("tabindex","1");
+			canvas.style.outline = 'none';
 		}
 		}
 		curW = this.width;
 		curW = this.width;
 		curH = this.height;
 		curH = this.height;
@@ -254,18 +267,30 @@ class Window {
 		var ev = new Event(EKeyUp, mouseX, mouseY);
 		var ev = new Event(EKeyUp, mouseX, mouseY);
 		ev.keyCode = e.keyCode;
 		ev.keyCode = e.keyCode;
 		event(ev);
 		event(ev);
+		if( !propagateKeyEvents ) {
+			e.preventDefault();
+			e.stopPropagation();
+		}
 	}
 	}
 
 
 	function onKeyDown(e:js.html.KeyboardEvent) {
 	function onKeyDown(e:js.html.KeyboardEvent) {
 		var ev = new Event(EKeyDown, mouseX, mouseY);
 		var ev = new Event(EKeyDown, mouseX, mouseY);
 		ev.keyCode = e.keyCode;
 		ev.keyCode = e.keyCode;
 		event(ev);
 		event(ev);
+		if( !propagateKeyEvents ) {
+			e.preventDefault();
+			e.stopPropagation();
+		}
 	}
 	}
 
 
 	function onKeyPress(e:js.html.KeyboardEvent) {
 	function onKeyPress(e:js.html.KeyboardEvent) {
 		var ev = new Event(ETextInput, mouseX, mouseY);
 		var ev = new Event(ETextInput, mouseX, mouseY);
 		ev.charCode = e.charCode;
 		ev.charCode = e.charCode;
 		event(ev);
 		event(ev);
+		if( !propagateKeyEvents ) {
+			e.preventDefault();
+			e.stopPropagation();
+		}
 	}
 	}
 
 
 	function onFocus(b: Bool) {
 	function onFocus(b: Bool) {

+ 2 - 2
hxd/inspect/ScenePanel.hx

@@ -139,7 +139,7 @@ private class CustomSceneProps extends SceneProps {
 							if( g.m.bits == mid ) {
 							if( g.m.bits == mid ) {
 								var inf = emap.get(g);
 								var inf = emap.get(g);
 								if( inf == null ) {
 								if( inf == null ) {
-									inf = { count : 0, pass : Lambda.count({ iterator : buf.material.getPasses }), tri : 0, name : e.model.r.name, e : e, g : g };
+									inf = { count : 0, pass : buf.material.getPasses().length, tri : 0, name : e.model.r.name, e : e, g : g };
 									all.push(inf);
 									all.push(inf);
 									emap.set(g, inf);
 									emap.set(g, inf);
 								}
 								}
@@ -153,7 +153,7 @@ private class CustomSceneProps extends SceneProps {
 				if( r.name != null && !meshes.remove(r) && r.isMesh() && ((o == null && (panel.showHidden || (r.visible && !r.culled))) || chunk.root == o) ) {
 				if( r.name != null && !meshes.remove(r) && r.isMesh() && ((o == null && (panel.showHidden || (r.visible && !r.culled))) || chunk.root == o) ) {
 					var m = r.toMesh();
 					var m = r.toMesh();
 					var inf = extraMap.get(r.name);
 					var inf = extraMap.get(r.name);
-					var npass = Lambda.count({ iterator : m.material.getPasses });
+					var npass = m.material.getPasses().length;
 					if( inf == null ) {
 					if( inf == null ) {
 						inf = { count : 0, pass : npass, tri : 0, name : r.name, e : null, g : null };
 						inf = { count : 0, pass : npass, tri : 0, name : r.name, e : null, g : null };
 						all.push(inf);
 						all.push(inf);

+ 9 - 7
hxd/inspect/SceneProps.hx

@@ -81,11 +81,13 @@ class SceneProps {
 
 
 			var ls = scene.lightSystem;
 			var ls = scene.lightSystem;
 			var props = [];
 			var props = [];
-			props.push(PGroup("LightSystem",[
-				PRange("maxLightsPerObject", 0, 10, function() return ls.maxLightsPerObject, function(s) ls.maxLightsPerObject = Std.int(s), 1),
-				PColor("ambientLight", false, function() return ls.ambientLight, function(v) ls.ambientLight = v),
-				PBool("perPixelLighting", function() return ls.perPixelLighting, function(b) ls.perPixelLighting = b),
-			]));
+			var fls = Std.instance(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),
+					PColor("ambientLight", false, function() return fls.ambientLight, function(v) fls.ambientLight.load(v)),
+					PBool("perPixelLighting", function() return fls.perPixelLighting, function(b) fls.perPixelLighting = b),
+				]));
 
 
 			if( ls.shadowLight != null )
 			if( ls.shadowLight != null )
 				props.push(PGroup("DirLight", getObjectProps(ls.shadowLight)));
 				props.push(PGroup("DirLight", getObjectProps(ls.shadowLight)));
@@ -247,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.DirLight);
+		var dl = Std.instance(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.PointLight);
+		var pl = Std.instance(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);

+ 1 - 1
hxd/poly2tri/VisiblePolygon.hx

@@ -38,7 +38,7 @@ class VisiblePolygon
 		if (!this.triangulated) return null;
 		if (!this.triangulated) return null;
 
 
 		var vertices = new Array();
 		var vertices = new Array();
-		var ids = new Array();
+		var ids = new Map();
 
 
 		for (i in 0...sweepContext.points.length)
 		for (i in 0...sweepContext.points.length)
 		{
 		{

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно