浏览代码

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

Yanrishatum 6 年之前
父节点
当前提交
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:
   - development
-  - "3.4.7"
-  - "3.4.4"
+  - "4.0.0-preview.5"
 
 matrix:
   allow_failures:
-    - haxe: development
+    - haxe: "3.4.7"
 
 install:
   - yes | haxelib install all

+ 1 - 1
all.hxml

@@ -3,7 +3,7 @@
 -lib hxbit
 -lib stb_ogg_sound
 --macro include('h3d')
---macro include('h2d')
+--macro include('h2d',true,['h2d.domkit'])
 --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'])
 --no-output

+ 2 - 0
h2d/CdbLevel.hx

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

+ 1 - 0
h2d/Dropdown.hx

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

+ 192 - 69
h2d/Flow.hx

@@ -8,6 +8,12 @@ enum FlowAlign {
 	Bottom;
 }
 
+enum FlowLayout {
+	Horizontal;
+	Vertical;
+	Stack;
+}
+
 @:allow(h2d.Flow)
 class FlowProperties {
 
@@ -150,10 +156,15 @@ class Flow extends Object {
 	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.
@@ -171,6 +182,11 @@ class Flow extends Object {
 	**/
 	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 debugGraphics : h2d.Graphics;
 	var properties : Array<FlowProperties> = [];
@@ -194,11 +210,20 @@ class Flow extends Object {
 		return properties[getChildIndex(e)];
 	}
 
-	function set_isVertical(v) {
-		if( isVertical == v )
+	function set_layout(v) {
+		if(layout == v)
 			return v;
 		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) {
@@ -243,6 +268,13 @@ class Flow extends Object {
 		return multiline = v;
 	}
 
+	function set_reverse(v) {
+		if( reverse == v )
+			return v;
+		needReflow = true;
+		return reverse = v;
+	}
+
 	function set_needReflow(v) {
 		if( needReflow == v )
 			return v;
@@ -353,16 +385,19 @@ class Flow extends Object {
 		var last = properties.length - 1;
 		while( last >= 0 && properties[last].isAbsolute )
 			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( !isInline )
 				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);
+			}
 		} else
 			super.getBoundsRec(relativeTo, out, forSize);
 	}
@@ -412,7 +454,7 @@ class Flow extends Object {
 		var k = 0;
 		while( numChildren>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 maxTotHeight = realMaxHeight < 0 ? 100000000 : Math.floor(realMaxHeight);
 		// 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 )
 			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;
-		if( !isVertical ) {
+		switch(layout) {
+		case Horizontal:
 			var halign = horizontalAlign == null ? Left : horizontalAlign;
 			var valign = verticalAlign == null ? Bottom : verticalAlign;
 
@@ -582,9 +632,9 @@ class Flow extends Object {
 				else if( overflow && minLineHeight != 0 )
 					maxLineHeight = minLineHeight;
 				for( i in lastIndex...maxIndex ) {
-					var p = properties[i];
+					var p = propAt(i);
 					if( p.isAbsolute ) continue;
-					var c = children[i];
+					var c = childAt(i);
 					if( !c.visible ) continue;
 					var a = p.verticalAlign != null ? p.verticalAlign : valign;
 					c.y = y + p.offsetY + p.paddingTop;
@@ -599,15 +649,26 @@ class Flow extends Object {
 				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 ) {
-				var p = properties[i];
+				var p = propAt(i);
 				if( p.isAbsolute ) continue;
-				var c = children[i];
+				var c = childAt(i);
 				if( !c.visible ) continue;
 
 				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);
@@ -616,7 +677,7 @@ class Flow extends Object {
 				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( multiline && x + p.calculatedWidth > maxWidth && x > startX ) {
+				if( multiline && x - startX + p.calculatedWidth > maxInWidth && x - startX > 0 ) {
 					br = true;
 					alignLine(i);
 					y += maxLineHeight + verticalSpacing;
@@ -639,8 +700,8 @@ class Flow extends Object {
 			var xmin = startX, xmax = endX;
 			var midSpace = 0;
 			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 ) {
 					xmin = startX;
 					xmax = endX;
@@ -650,22 +711,16 @@ class Flow extends Object {
 				var align = p.horizontalAlign == null ? halign : p.horizontalAlign;
 				switch( align ) {
 				case Right:
-					if( midSpace != 0 ) {
+					if( midSpace == 0 ) {
+						var remSize = p.calculatedWidth + remSize(i + 1);
+						midSpace = (xmax - xmin) - remSize;
 						xmin += midSpace;
-						midSpace = 0;
 					}
-					xmax -= p.calculatedWidth;
-					px = xmax;
-					xmax -= horizontalSpacing;
+					px = xmin;
+					xmin += p.calculatedWidth + horizontalSpacing;
 				case Middle:
 					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);
 						xmin += midSpace;
 					}
@@ -679,11 +734,10 @@ class Flow extends Object {
 					px = xmin;
 					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 valign = verticalAlign == null ? Top : verticalAlign;
 
@@ -701,9 +755,9 @@ class Flow extends Object {
 				else if( overflow && minColWidth != 0 )
 					maxColWidth = minColWidth;
 				for( i in lastIndex...maxIndex ) {
-					var p = properties[i];
+					var p = propAt(i);
 					if( p.isAbsolute ) continue;
-					var c = children[i];
+					var c = childAt(i);
 					if( !c.visible ) continue;
 					var a = p.horizontalAlign != null ? p.horizontalAlign : halign;
 					c.x = x + p.offsetX + p.paddingLeft;
@@ -718,16 +772,27 @@ class Flow extends Object {
 				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 ) {
-				var p = properties[i];
+				var p = propAt(i);
 				if( p.isAbsolute ) continue;
 
-				var c = children[i];
+				var c = childAt(i);
 				if( !c.visible ) continue;
 
 				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);
@@ -738,7 +803,7 @@ class Flow extends Object {
 				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( multiline && y + p.calculatedHeight > maxHeight && y > startY ) {
+				if( multiline && y - startY + p.calculatedHeight > maxInHeight && y - startY > 0 ) {
 					br = true;
 					alignLine(i);
 					x += maxColWidth + horizontalSpacing;
@@ -763,8 +828,8 @@ class Flow extends Object {
 			var ymin = startY, ymax = endY;
 			var midSpace = 0;
 			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 ) {
 					ymin = startY;
 					ymax = endY;
@@ -774,22 +839,16 @@ class Flow extends Object {
 				var align = p.verticalAlign == null ? valign : p.verticalAlign;
 				switch( align ) {
 				case Bottom:
-					if( midSpace != 0 ) {
+					if( midSpace == 0 ) {
+						var remSize = p.calculatedHeight + remSize(i + 1);
+						midSpace = (ymax - ymin) - remSize;
 						ymin += midSpace;
-						midSpace = 0;
 					}
-					ymax -= p.calculatedHeight;
-					py = ymax;
-					ymax -= verticalSpacing;
+					py = ymin;
+					ymin += p.calculatedHeight + verticalSpacing;
 				case Middle:
 					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);
 						ymin += midSpace;
 					}
@@ -803,7 +862,71 @@ class Flow extends Object {
 					py = ymin;
 					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);
 			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;
 				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));
 	}
 
-	override function onParentChanged() {
-		super.onParentChanged();
+	override private function onHierarchyMoved(parentChanged:Bool)
+	{
+		super.onHierarchyMoved(parentChanged);
 		if( scene != null ) {
 			scene.removeEventTarget(this);
 			scene.addEventTarget(this);
 		}
-		updateMask();
+		if ( parentChanged )
+			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 ) {
 		for( i in 0...children.length )
 			if( children[i] == s ) {
@@ -69,10 +72,16 @@ class Layers extends Object {
 					p--;
 				}
 				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 ) {
 		for( i in 0...children.length )
 			if( children[i] == s ) {
@@ -81,12 +90,20 @@ class Layers extends Object {
 						for( p in i...l-1 )
 							children[p] = children[p + 1];
 						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> {
 		var a;
 		if( layer >= layerCount )
@@ -99,6 +116,19 @@ class Layers extends Object {
 		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 ) {
 		if( layer >= layerCount )
 			return;
@@ -114,6 +144,9 @@ class Layers extends Object {
 		ctx.globalAlpha = old;
 	}
 
+	/**
+		Sorts specified layer based on Y value of it's Objects.
+	**/
 	public function ysort( layer : Int ) {
 		if( layer >= layerCount ) return;
 		var start = layer == 0 ? 0 : layersIndexes[layer - 1];
@@ -133,6 +166,8 @@ class Layers extends Object {
 					p--;
 				}
 				children[p + 1] = c;
+				if ( c.allocated )
+					c.onHierarchyMoved(false);
 			} else
 				ymax = c.y;
 			pos++;

+ 4 - 3
h2d/Mask.hx

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

+ 5 - 5
h2d/Object.hx

@@ -347,7 +347,7 @@ class Object {
 			if( !s.allocated )
 				s.onAdd();
 			else
-				s.onParentChanged();
+				s.onHierarchyMoved(true);
 		}
 		onContentChanged();
 	}
@@ -357,9 +357,9 @@ class Object {
 	}
 
 	// 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
@@ -418,7 +418,7 @@ class Object {
 
 	/**
 		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() {
 		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() {
+		if(points.length < 3) return true;
+
 		var p1 = points[points.length - 2];
 		var p2 = points[points.length - 1];
 		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 tmpMatrix : h2d.col.Matrix;
 	var obj : h2d.Object;
-	var bindCount : Int;
+	var bindCount : Int = 0;
 	public var mask(default, set) : h2d.Object;
 	public var maskVisible(default, set) : Bool;
 

+ 15 - 2
h3d/Buffer.hx

@@ -152,7 +152,11 @@ class Buffer {
 }
 
 class BufferOffset {
+	#if flash
+	static var UID = 0;
 	public var id : Int;
+	#end
+
 	public var buffer : Buffer;
 	public var offset : Int;
 
@@ -161,13 +165,22 @@ class BufferOffset {
 	*/
 	public var next : BufferOffset;
 
-	static var UID = 0;
-
 	public function new(buffer, offset) {
+		#if flash
 		this.id = UID++;
+		#end
 		this.buffer = buffer;
 		this.offset = offset;
 	}
+
+	public inline function clone() {
+		var b = new BufferOffset(buffer,offset);
+		#if flash
+		b.id = id;
+		#end
+		return b;
+	}
+
 	public function dispose() {
 		if( buffer != null ) {
 			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 frustum(default, null) = new h3d.col.Frustum();
+	public var frustum(default, null) : h3d.col.Frustum;
 
 	var minv : Matrix;
 	var miview : Matrix;
@@ -60,6 +60,7 @@ class Camera {
 		m = new Matrix();
 		mcam = new Matrix();
 		mproj = new Matrix();
+		frustum = new h3d.col.Frustum();
 		update();
 	}
 
@@ -118,7 +119,7 @@ class Camera {
 	/**
 		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;
 		switch( face ) {
 		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 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);
 	}
 
@@ -175,9 +177,9 @@ class Camera {
 		}
 		makeCameraMatrix(mcam);
 		makeFrustumMatrix(mproj);
-		
+
 		m.multiply(mcam, mproj);
-		
+
 		needInv = true;
 		if( miview != null ) miview._44 = 0;
 
@@ -235,10 +237,11 @@ class Camera {
 		// 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
 		// 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);
-		ax.normalize();
+		ax.normalizeFast();
 		if( ax.length() == 0 ) {
 			ax.x = az.y;
 			ax.y = az.z;

+ 8 - 14
h3d/col/Bounds.hx

@@ -18,20 +18,14 @@ class Bounds implements Collider {
 		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 ) {

+ 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 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);
 	}
 
-	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)
 	function customSerialize(ctx:hxbit.Serializer) {
@@ -73,13 +76,19 @@ class GroupCollider implements Collider {
 		return false;
 	}
 
-	public function inFrustum( mvp : h3d.Matrix ) {
+	public function inFrustum( f : Frustum ) {
 		for( c in colliders )
-			if( c.inFrustum(mvp) )
+			if( c.inFrustum(f) )
 				return true;
 		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)
 

+ 12 - 0
h3d/col/Frustum.hx

@@ -76,6 +76,18 @@ class Frustum {
 		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 ) {
 		var p = s.getCenter();
 		if( pleft.distance(p) < -s.r ) return false;

+ 6 - 0
h3d/col/IPoint.hx

@@ -23,4 +23,10 @@ class IPoint {
 		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;
 	}
 
-	public function inFrustum( mvp : h3d.Matrix ) {
+	public function inFrustum( f : Frustum ) {
+		throw "Not implemented";
+		return false;
+	}
+
+	public function inSphere( s : Sphere ) {
 		throw "Not implemented";
 		return false;
 	}

+ 2 - 14
h3d/col/Point.hx

@@ -19,20 +19,8 @@ class Point {
 		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) {

+ 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;
 	}
 
-	public function inFrustum( m : h3d.Matrix ) {
+	public function inFrustum( f : Frustum ) {
+		throw "Not implemented";
+		return false;
+	}
+
+	public function inSphere( s : Sphere ) {
 		throw "Not implemented";
 		return false;
 	}
@@ -233,7 +238,12 @@ class Polygon implements Collider {
 		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";
 		return false;
 	}

+ 6 - 1
h3d/col/PolygonBuffer.hx

@@ -41,7 +41,12 @@ class PolygonBuffer implements Collider {
 		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";
 		return false;
 	}

+ 5 - 0
h3d/col/SkinCollider.hx

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

+ 14 - 31
h3d/col/Sphere.hx

@@ -8,10 +8,14 @@ class Sphere implements Collider {
 	public var r : Float;
 
 	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() {
@@ -43,33 +47,12 @@ class Sphere implements Collider {
 		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() {

+ 6 - 5
h3d/impl/DirectXDriver.hx

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

+ 21 - 1
h3d/impl/Driver.hx

@@ -125,6 +125,26 @@ enum RenderFlag {
 	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 {
 
 	public var logEnable : Bool;
@@ -198,7 +218,7 @@ class Driver {
 	public function uploadShaderBuffers( buffers : h3d.shader.Buffers, which : h3d.shader.Buffers.BufferKind ) {
 	}
 
-	public function getShaderInputNames() : Array<String> {
+	public function getShaderInputNames() : InputNames {
 		return null;
 	}
 

+ 12 - 10
h3d/impl/GlDriver.hx

@@ -131,7 +131,7 @@ private class CompiledProgram {
 	public var vertex : CompiledShader;
 	public var fragment : CompiledShader;
 	public var stride : Int;
-	public var attribNames : Array<String>;
+	public var inputs : InputNames;
 	public var attribs : Array<CompiledAttribute>;
 	public function new() {
 	}
@@ -283,7 +283,7 @@ class GlDriver extends Driver {
 	}
 
 	override function getShaderInputNames() {
-		return curShader.attribNames;
+		return curShader.inputs;
 	}
 
 	override function getNativeShaderCode( shader : hxsl.RuntimeShader ) {
@@ -410,12 +410,12 @@ class GlDriver extends Driver {
 					return selectShader(shader);
 				}
 				#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;
 			initShader(p, p.vertex, shader.vertex);
 			initShader(p, p.fragment, shader.fragment);
-			p.attribNames = [];
+			var attribNames = [];
 			p.attribs = [];
 			p.stride = 0;
 			for( v in shader.vertex.data.vars )
@@ -447,10 +447,11 @@ class GlDriver extends Driver {
 							}
 					}
 					p.attribs.push(a);
-					p.attribNames.push(v.name);
+					attribNames.push(v.name);
 					p.stride += size;
 				default:
 				}
+			p.inputs = InputNames.get(attribNames);
 			programs.set(shader.id, p);
 		}
 		if( curShader == p ) return false;
@@ -1097,10 +1098,11 @@ class GlDriver extends Driver {
 		#if hl
 		var needed = streamPos + length;
 		var total = (needed + 7) & ~7; // align on 8 bytes
+		var alen = total - streamPos;
 		if( total > streamLen ) expandStream(total);
 		streamBytes.blit(streamPos, data, pos, length);
 		data = streamBytes.offset(streamPos);
-		streamPos = total;
+		streamPos += alen;
 		#end
 		return data;
 	}
@@ -1232,7 +1234,7 @@ class GlDriver extends Driver {
 			for( i in 0...curShader.attribs.length ) {
 				var a = curShader.attribs[i];
 				var pos;
-				switch( curShader.attribNames[i] ) {
+				switch( curShader.inputs.names[i] ) {
 				case "position":
 					pos = 0;
 				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) {
-		
+
 		var pixels : hxd.Pixels;
 		var x : Int, y : Int;
 		if (region != null) {
@@ -1399,7 +1401,7 @@ class GlDriver extends Driver {
 			x = 0;
 			y = 0;
 		}
-		
+
 		var old = curTarget;
 		var oldCount = numTargets;
 		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) )
 			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 )
 			tex.alloc();

+ 1 - 1
h3d/impl/LogDriver.hx

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

+ 2 - 2
h3d/impl/NullDriver.hx

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

+ 5 - 3
h3d/impl/TextureCache.hx

@@ -40,12 +40,14 @@ class TextureCache {
 		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];
 		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();
-			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;
 		}
 		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);
 			p = p.nextPass;
 		}
-		return out.iterator();
+		return out;
 	}
 
 	public function getPass( name : String ) : Pass {

+ 3 - 3
h3d/mat/MaterialSetup.hx

@@ -13,11 +13,11 @@ class MaterialSetup {
 	}
 
 	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() {

+ 7 - 4
h3d/mat/Pass.hx

@@ -8,6 +8,7 @@ import h3d.mat.Data;
 class Pass implements hxd.impl.Serializable {
 
 	@:s public var name(default, null) : String;
+	var flags : Int;
 	var passId : Int;
 	@:s var bits : Int = 0;
 	@:s var parentPass : Pass;
@@ -15,18 +16,20 @@ class Pass implements hxd.impl.Serializable {
 	var shaders : hxsl.ShaderList;
 	@: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,
 		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
 		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 depthWrite : Bool;
@@ -283,7 +286,7 @@ class Pass implements hxd.impl.Serializable {
 			return h3d.Engine.getCurrent().driver.getNativeShaderCode(shader);
 		}
 	}
-	
+
 	#if hxbit
 
 	public function customSerialize( ctx : hxbit.Serializer ) {

+ 2 - 2
h3d/mat/PbrMaterial.hx

@@ -96,12 +96,12 @@ class PbrMaterial extends Material {
 		case Albedo:
 			mainPass.setPassName("albedo");
 		case BeforeTonemapping:
-			mainPass.setPassName("BeforeTonemapping");
+			mainPass.setPassName("beforeTonemapping");
 			var e = mainPass.getShader(h3d.shader.Emissive);
 			if( e == null ) e = mainPass.addShader(new h3d.shader.Emissive(props.emissive));
 			e.emissive = props.emissive;
 		case Distortion:
-			mainPass.setPassName("Distortion");
+			mainPass.setPassName("distortion");
 			mainPass.depthWrite = false;
 		case Overlay:
 			mainPass.setPassName("overlay");

+ 4 - 0
h3d/mat/PbrMaterialSetup.hx

@@ -38,4 +38,8 @@ class PbrMaterialSetup extends MaterialSetup {
 		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
 	**/
 	Cone;
+
+	/**
+		A disc, emit in one direction
+	**/
+	Disc;
+
 	/**
 		The GpuParticles specified volumeBounds
 	**/
@@ -125,6 +131,7 @@ class GpuPartGroup {
 	public var emitAngle(default,set) : Float 	= 1.5;
 	public var emitSync(default, set) : Float	= 0;
 	public var emitDelay(default, set) : Float	= 0;
+	public var emitOnBorder(default, set) : 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_emitSync(v) { needRebuild = true; return emitSync = 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_rotSpeed(v) { needRebuild = true; return rotSpeed = v; }
 	inline function set_rotSpeedRand(v) { needRebuild = true; return rotSpeedRand = v; }
@@ -283,6 +291,17 @@ class GpuPartGroup {
 				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:
 			var d = speed * (1 + speedIncr * life) * life;
 			var max = (1 + emitDist) * 0.5;
@@ -313,7 +332,6 @@ class GpuPartGroup {
 
 		switch( g.emitMode ) {
 		case Point:
-
 			v.x = srand();
 			v.y = srand();
 			v.z = srand();
@@ -335,6 +353,22 @@ class GpuPartGroup {
 			p.y = v.y * 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:
 
 			var max = 1 + g.emitDist;
@@ -514,12 +548,12 @@ class GpuParticles extends h3d.scene.MultiMaterial {
 			material.mainPass.culling = None;
 			material.mainPass.depthWrite = false;
 			material.blendMode = Alpha;
-			if( this.material == null ) this.material = material;
 			if( g.material != null ) {
 				material.props = g.getMaterialProps();
 				material.name = g.name;
 			}
 		}
+		if( this.material == null ) this.material = material;
 		material.mainPass.addShader(g.pshader);
 		if( index == null )
 			index = groups.length;
@@ -613,7 +647,7 @@ class GpuParticles extends h3d.scene.MultiMaterial {
 				case VolumeBounds, CameraBounds:
 					ebounds = volumeBounds;
 					if( ebounds == null ) ebounds = volumeBounds = h3d.col.Bounds.fromValues( -1, -1, -1, 2, 2, 2 );
-				case Cone, Point:
+				case Cone, Point, Disc:
 					ebounds = null;
 				}
 			}

+ 1 - 2
h3d/pass/Base.hx

@@ -21,8 +21,7 @@ class Base {
 	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 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.values = values;

+ 24 - 56
h3d/pass/Default.hx

@@ -6,13 +6,11 @@ class Default extends Base {
 
 	var manager : ShaderManager;
 	var globals(get, never) : hxsl.Globals;
-	var cachedBuffer : h3d.shader.Buffers;
 	var shaderCount : Int = 1;
 	var textureCount : Int = 1;
 	var shaderIdMap : Array<Int>;
 	var textureIdMap : Array<Int>;
 	var sortPasses = true;
-	var logEnable(get, never) : Bool;
 
 	inline function get_globals() return manager.globals;
 
@@ -38,26 +36,18 @@ class Default extends Base {
 		initGlobals();
 	}
 
-	inline function get_logEnable() {
-		#if debug
-		return ctx.engine.driver.logEnable;
-		#else
-		return false;
-		#end
-	}
-
 	function getOutputs() : Array<hxsl.Output> {
 		return [Value("output.color")];
 	}
 
 	override function compileShader( p : h3d.mat.Pass ) {
-		var o = new Object();
+		var o = @:privateAccess new h3d.pass.PassObject();
 		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;
 		while( p != null ) {
 			shaders = ctx.allocShaderList(p.s, shaders);
@@ -67,10 +57,9 @@ class Default extends Base {
 	}
 
 	@:access(h3d.scene)
-	function setupShaders( passes : Object ) {
-		var p = passes;
+	function setupShaders( passes : h3d.pass.PassList ) {
 		var lightInit = false;
-		while( p != null ) {
+		for( p in passes ) {
 			var shaders = p.pass.getShadersRec();
 			shaders = processShaders(p, shaders);
 			if( p.pass.enableLights && ctx.lightSystem != null ) {
@@ -80,7 +69,7 @@ class Default extends Base {
 				}
 				shaders = ctx.lightSystem.computeLight(p.obj, shaders);
 			}
-			p.shader = manager.compileShaders(shaders);
+			p.shader = manager.compileShaders(shaders, p.pass.batchMode);
 			p.shaders = shaders;
 			var t = p.shader.fragment.textures;
 			if( t == null )
@@ -89,59 +78,44 @@ class Default extends Base {
 				var t : h3d.mat.Texture = manager.getParamValue(t, shaders, true);
 				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 ) {
 		ctx.engine.driver.log(str);
 	}
 
-	function drawObject( p : Object ) {
+	function drawObject( p : h3d.pass.PassObject ) {
 		ctx.drawPass = p;
 		ctx.engine.selectMaterial(p.pass);
 		@:privateAccess p.obj.draw(ctx);
 	}
 
 	@:access(h3d.scene)
-	override function draw( passes : Object ) {
+	override function draw( passes : h3d.pass.PassList ) {
+		if( passes.isEmpty() )
+			return;
 		for( g in ctx.sharedGlobals )
 			globals.fastSet(g.gid, g.value);
 		setGlobals();
 		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];
 				if( d != 0 ) return d;
 				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;
 			if( p.shader.hasGlobal(globalModelViewInverse_id.toInt()) )
 				globalModelViewInverse = p.obj.getInvPos();
@@ -149,7 +123,7 @@ class Default extends Base {
 				prevShader = p.shader;
 				ctx.engine.selectShader(p.shader);
 				if( buf == null )
-					buf = cachedBuffer = new h3d.shader.Buffers(p.shader);
+					buf = ctx.shaderBuffers = new h3d.shader.Buffers(p.shader);
 				else
 					buf.grow(p.shader);
 				manager.fillGlobals(buf, p.shader);
@@ -162,14 +136,8 @@ class Default extends Base {
 				ctx.engine.uploadShaderBuffers(buf, Buffers);
 			}
 			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();
-		return passes;
 	}
 
 }

+ 0 - 1
h3d/pass/DefaultShadowMap.hx

@@ -31,7 +31,6 @@ class DefaultShadowMap extends DirShadowMap {
 		ctx.setGlobalID(shadowColorId, color);
 		ctx.setGlobalID(shadowPowerId, power);
 		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();
 			for( pt in ctx.camera.getFrustumCorners() ) {
 				pt.transform(camera.mcam);
-				cameraBounds.addPos(pt.x, pt.y, pt.z);				
+				cameraBounds.addPos(pt.x, pt.y, pt.z);
 			}
 			cameraBounds.zMin = bounds.zMin;
 			bounds.intersection(bounds, cameraBounds);
@@ -109,7 +109,7 @@ class DirShadowMap extends Shadows {
 		cameraViewProj = getShadowProj();
 	}
 
-	function syncShader(texture) {
+	override function syncShader(texture) {
 		dshader.shadowMap = texture;
 		dshader.shadowMapChannel = format == h3d.mat.Texture.nativeFormat ? PackedFloat : R;
 		dshader.shadowBias = bias;
@@ -167,30 +167,16 @@ class DirShadowMap extends Shadows {
 		if( staticTexture != null ) staticTexture.dispose();
 		staticTexture = new h3d.mat.Texture(size, size, [Target], format);
 		staticTexture.uploadPixels(pixels);
+		staticTexture.name = "defaultDirShadowMap";
 		syncShader(staticTexture);
 		return true;
 	}
 
 	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( depth != null ) depth.dispose();
 			depth = new h3d.mat.DepthBuffer(size, size);
@@ -216,12 +202,12 @@ class DirShadowMap extends Shadows {
 
 		ctx.engine.pushTarget(texture);
 		ctx.engine.clear(0xFFFFFF, 1);
-		passes = super.draw(passes);
+		super.draw(passes);
 		if( border != null ) border.render();
 		ctx.engine.popTarget();
 
 		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.texB = staticTexture;
 			ctx.engine.pushTarget(merge);
@@ -234,10 +220,9 @@ class DirShadowMap extends Shadows {
 			blur.apply(ctx, texture);
 
 		syncShader(texture);
-		return passes;
 	}
 
-	override function computeStatic( passes : h3d.pass.Object ) {
+	override function computeStatic( passes : h3d.pass.PassList ) {
 		if( mode != Static && mode != Mixed )
 			return;
 		draw(passes);

+ 7 - 14
h3d/pass/HardwarePick.hx

@@ -63,17 +63,13 @@ class HardwarePick extends Default {
 		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
-			@: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;
 
@@ -82,12 +78,11 @@ class HardwarePick extends Default {
 		ctx.engine.pushTarget(texOut);
 		ctx.engine.clear(0xFF000000, 1);
 		ctx.extraShaders = ctx.allocShaderList(fixedColor);
-		var passes = super.draw(passes);
+		super.draw(passes);
 		ctx.extraShaders = null;
 		ctx.engine.popTarget();
 
-		var cur = passes;
-		while( cur != null ) {
+		for( cur in passes ) {
 			// will reset bits
 			cur.pass.blendSrc = cur.pass.blendSrc;
 			cur.pass.blendDst = cur.pass.blendDst;
@@ -96,13 +91,11 @@ class HardwarePick extends Default {
 			cur.pass.blendAlphaDst = cur.pass.blendAlphaDst;
 			cur.pass.blendAlphaOp = cur.pass.blendAlphaOp;
 			cur.pass.colorMask = cur.pass.colorMask;
-			cur = cur.next;
 		}
 
 		ctx.engine.clear(null, null, 0);
 		var pix = texOut.capturePixels();
 		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) {
 		if (output == null)
 			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.size.set(size / src.width, size / src.height);
 		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;
 
-class Object {
+class PassObject {
+	@:noCompletion public var next : PassObject;
+	var nextAlloc : PassObject;
 	public var pass : h3d.mat.Pass;
 	public var obj : h3d.scene.Object;
 	public var index : Int;
-	public var next : Object;
-	public var nextAlloc : Object;
 
 	// cache
 	public var shaders : hxsl.ShaderList;
@@ -13,6 +13,6 @@ class Object {
 	public var depth : Float;
 	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;
 	}
 
-	function syncShader(texture) {
+	override function syncShader(texture) {
 		var absPos = light.getAbsPos();
 		var pointLight = cast(light, h3d.scene.pbr.PointLight);
 		pshader.shadowMap = texture;
@@ -90,7 +90,8 @@ class PointShadowMap extends Shadows {
 			return false;
 
 		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){
 			var len = buffer.readInt32();
@@ -101,59 +102,49 @@ class PointShadowMap extends Shadows {
 		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);
-		}
 		return tex;
 	}
 
 	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.dispose();
-				depth = new h3d.mat.DepthBuffer(size, size);
+			if( depth != null ) depth.dispose();
+			depth = new h3d.mat.DepthBuffer(size, size);
 		}
 		texture.depthBuffer = depth;
 
 		var validBakedTexture = (staticTexture != null && staticTexture.width == texture.width);
 		var merge : h3d.mat.Texture = null;
 		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();
 
 			ctx.engine.pushTarget(texture, i);
 			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();
 		}
 
@@ -173,7 +164,6 @@ class PointShadowMap extends Shadows {
 		}
 
 		syncShader(texture);
-		return passes;
 	}
 
 	function updateCamera(){
@@ -182,7 +172,7 @@ class PointShadowMap extends Shadows {
 		lightCamera.update();
 	}
 
-	override function computeStatic( passes : h3d.pass.Object ) {
+	override function computeStatic( passes : h3d.pass.PassList ) {
 		if( mode != Static && mode != Mixed )
 			return;
 		draw(passes);

+ 39 - 15
h3d/pass/ShaderManager.hx

@@ -24,7 +24,7 @@ class ShaderManager {
 	}
 
 	@: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 ) {
 		case TInt:
 			out[pos] = v;
@@ -163,6 +163,14 @@ class ShaderManager {
 		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 {
 		if( p.perObjectGlobal != null ) {
 			var v : Dynamic = globals.fastGet(p.perObjectGlobal.gid);
@@ -182,17 +190,18 @@ class ShaderManager {
 	public function fillGlobals( buf : h3d.shader.Buffers, s : hxsl.RuntimeShader ) {
 		inline function fill(buf:h3d.shader.Buffers.ShaderBuffers, s:hxsl.RuntimeShader.RuntimeShaderData) {
 			var g = s.globals;
+			var ptr = getPtr(buf.globals);
 			while( g != null ) {
 				var v = globals.fastGet(g.gid);
 				if( v == null ) {
 					if( g.path == "__consts__" ) {
-						fillRec(s.consts, g.type, buf.globals, g.pos);
+						fillRec(s.consts, g.type, ptr, g.pos);
 						g = g.next;
 						continue;
 					}
 					throw "Missing global value " + g.path;
 				}
-				fillRec(v, g.type, buf.globals, g.pos);
+				fillRec(v, g.type, ptr, g.pos);
 				g = g.next;
 			}
 		}
@@ -201,19 +210,34 @@ class ShaderManager {
 	}
 
 	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) {
 			var p = s.params;
+			var ptr = getPtr(buf.params);
 			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;
 			}
 			var tid = 0;
@@ -234,11 +258,11 @@ class ShaderManager {
 		fill(buf.fragment, s.fragment);
 	}
 
-	public function compileShaders( shaders : hxsl.ShaderList ) {
+	public function compileShaders( shaders : hxsl.ShaderList, batchMode : Bool = false ) {
 		globals.resetChannels();
 		for( s in shaders ) s.updateConstants(globals);
 		currentOutput.next = shaders;
-		var s = shaderCache.link(currentOutput);
+		var s = shaderCache.link(currentOutput, batchMode);
 		currentOutput.next = null;
 		return s;
 	}

+ 50 - 37
h3d/pass/Shadows.hx

@@ -24,7 +24,7 @@ class Shadows extends Default {
 	public function new(light) {
 		if( format == null ) format = R16F;
 		if( !h3d.Engine.getCurrent().driver.isSupportedFormat(format) ) format = h3d.mat.Texture.nativeFormat;
-		super("shadows");
+		super("shadow");
 		this.light = light;
 		blur = new Blur(5);
 		blur.quality = 0.5;
@@ -80,52 +80,65 @@ class Shadows extends Default {
 		return null;
 	}
 
-	public function computeStatic( passes : h3d.pass.Object ) {
+	public function computeStatic( passes : h3d.pass.PassList ) {
 		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 ) {
 		case None:
-			return null;
+			passes.clear();
 		case Dynamic:
-			if( ctx.computingStatic ) return null;
-			return passes;
+			if( ctx.computingStatic ) passes.clear();
 		case Mixed:
-			isStatic = ctx.computingStatic;
+			passes.filter(function(p) return p.pass.isStatic == ctx.computingStatic);
 		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();
 	}
 
-	function syncShader(texture) {
+	override function syncShader(texture) {
 		sshader.shadowMap = texture;
 		sshader.shadowMapChannel = format == h3d.mat.Texture.nativeFormat ? PackedFloat : R;
 		sshader.shadowBias = bias;
@@ -89,26 +89,11 @@ class SpotShadowMap extends Shadows {
 	}
 
 	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();
+		cullPasses(passes, function(col) return col.inFrustum(lightCamera.frustum));
 
 		var texture = ctx.textures.allocTarget("shadowMap", size, size, false, format);
 		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.clear(0xFFFFFF, 1);
-		passes = super.draw(passes);
+		super.draw(passes);
 		if( border != null ) border.render();
 		ctx.engine.popTarget();
 
@@ -138,7 +123,6 @@ class SpotShadowMap extends Shadows {
 		}
 
 		syncShader(texture);
-		return passes;
 	}
 
 	function updateCamera(){
@@ -152,7 +136,7 @@ class SpotShadowMap extends Shadows {
 		lightCamera.update();
 	}
 
-	override function computeStatic( passes : h3d.pass.Object ) {
+	override function computeStatic( passes : h3d.pass.PassList ) {
 		if( mode != Static && mode != Mixed )
 			return;
 		draw(passes);

+ 5 - 5
h3d/prim/Cylinder.hx

@@ -22,15 +22,15 @@ class Cylinder extends Quads {
 		super(pts);
 	}
 
-	override public function addTCoords() {
+	override function addUVs() {
 		uvs = new Array();
 		for( s in 0...segs ) {
 			var u = s / 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 {
 
 	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() {
+		offset = new h3d.col.Sphere();
+		tmpBounds = new h3d.col.Bounds();
 	}
 
 	public function setMesh( m : MeshPrimitive ) {
@@ -12,6 +17,7 @@ class Instanced extends MeshPrimitive {
 		if( m.buffer == null ) m.alloc(engine);
 		buffer = m.buffer;
 		indexes = m.indexes;
+		baseBounds = m.getBounds();
 		if( indexes == null ) indexes = engine.mem.triIndexes;
 		for( bid in m.bufferCache.keys() ) {
 			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 ) {
 		super.addBuffer(name, buffer, offset);
 	}

+ 13 - 11
h3d/prim/MeshPrimitive.hx

@@ -3,8 +3,7 @@ package h3d.prim;
 class MeshPrimitive extends Primitive {
 
 	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 ) {
 		return null;
@@ -31,17 +30,20 @@ class MeshPrimitive extends Primitive {
 			for( b in bufferCache )
 				b.dispose();
 		bufferCache = null;
-		prevNames = null;
+		layouts = null;
 	}
 
 	function getBuffers( engine : h3d.Engine ) {
 		if( bufferCache == null )
 			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 b = bufferCache.get(id);
 			if( b == null ) {
@@ -49,7 +51,7 @@ class MeshPrimitive extends Primitive {
 				if( b == null ) throw "Buffer " + name + " is not available";
 				bufferCache.set(id, b);
 			}
-			b.next = null;
+			b = b.clone();
 			if( prev == null ) {
 				buffers = prev = b;
 			} else {
@@ -57,8 +59,8 @@ class MeshPrimitive extends Primitive {
 				prev = b;
 			}
 		}
-		prevNames = names;
-		return prevBuffers = buffers;
+		layouts.set(inputs.id, buffers);
+		return buffers;
 	}
 
 	override function render( engine : h3d.Engine ) {

+ 1 - 1
h3d/prim/Plane2D.hx

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

+ 4 - 1
h3d/prim/Quads.hx

@@ -7,6 +7,9 @@ class Quads extends Primitive {
 	var uvs : Array<UV>;
 	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 ) {
 		this.pts = pts;
 		this.uvs = uvs;
@@ -39,7 +42,7 @@ class Quads extends Primitive {
 	/**
 	* Warning : This will splice four basic uv value but can provoke aliasing problems.
 	*/
-	public function addTCoords() {
+	public function addUVs() {
 		uvs = [];
 		var a = new UV(0, 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);
 	}
 
+	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() {
 		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.x *= targetOffset.w;
 
+		curOffset.w = scene.camera.fovY;
+
 		if( !animate )
 			toTarget();
 		else

+ 1 - 1
h3d/scene/Light.hx

@@ -6,7 +6,7 @@ class Light extends Object {
 	var objectDistance : Float; // used internaly
 	@: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;
 	public var color(get, set) : h3d.Vector;
 	public var enableSpecular(get, set) : Bool;

+ 16 - 65
h3d/scene/LightSystem.hx

@@ -1,44 +1,24 @@
 package h3d.scene;
 
-@:access(h3d.scene.Light)
-@:access(h3d.scene.Object.absPos)
-@:access(h3d.scene.RenderContext.lights)
 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 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() {
-		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 frustum = new h3d.col.Frustum(ctx.camera.m);
 		var s = new h3d.col.Sphere();
 		while( l != null ) {
 			s.x = l.absPos._41;
@@ -46,7 +26,7 @@ class LightSystem {
 			s.z = l.absPos._43;
 			s.r = l.cullingDistance;
 
-			if(!ctx.computingStatic && !frustum.hasSphere(s) ) {
+			if( l.cullingDistance > 0 && !ctx.computingStatic && !ctx.camera.frustum.hasSphere(s) ) {
 				if( prev == null )
 					ctx.lights = l.next;
 				else
@@ -60,12 +40,16 @@ class LightSystem {
 			prev = l;
 			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) {
 			var l = ctx.lights;
 			while( l != null ) {
-				var dir = @:privateAccess l.getShadowDirection();
+				var dir = l.getShadowDirection();
 				if( dir != null ) {
 					shadowLight = l;
 					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 {
-		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;
 	}
 

+ 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 FIgnoreCollide = 0x400;
 	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 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 cullingCollider : h3d.col.Collider;
+
 	var absPos : h3d.Matrix;
 	var invPos : h3d.Matrix;
 	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.
 	**/
 	public function new( ?parent : Object ) {
-		flags = new ObjectFlags();
+		flags = new ObjectFlags(0);
 		absPos = new h3d.Matrix();
 		absPos.identity();
 		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 ) {
 			follow.syncPos();
 			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
 				absPos.multiply3x4(absPos, follow.absPos);
 		} else if( parent != null && !ignoreParentTransform )

+ 27 - 18
h3d/scene/RenderContext.hx

@@ -1,5 +1,4 @@
 package h3d.scene;
-import h3d.pass.Object in ObjectPass;
 
 private class SharedGlobal {
 	public var gid : Int;
@@ -14,26 +13,29 @@ class RenderContext extends h3d.impl.RenderContext {
 
 	public var camera : h3d.Camera;
 	public var scene : Scene;
-	public var drawPass : ObjectPass;
+	public var drawPass : h3d.pass.PassObject;
 	public var pbrLightPass : h3d.mat.Pass;
 	public var computingStatic : Bool;
 
 	var sharedGlobals : Array<SharedGlobal>;
 	public var lightSystem : h3d.scene.LightSystem;
-	public var uploadParams : Void -> Void;
 	public var extraShaders : hxsl.ShaderList;
 	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 cachedPassObjects : Array<Renderer.PassObjects>;
 	var cachedPos : Int;
-	var passes : ObjectPass;
+	var passes : h3d.pass.PassObject;
 	var lights : Light;
+	var currentManager : h3d.pass.ShaderManager;
 
 	public function new() {
 		super();
 		cachedShaderList = [];
+		cachedPassObjects = [];
 	}
 
 	@:access(h3d.mat.Pass)
@@ -51,7 +53,6 @@ class RenderContext extends h3d.impl.RenderContext {
 		drawPass = null;
 		passes = null;
 		lights = null;
-		uploadParams = null;
 		cachedPos = 0;
 		visibleFlag = true;
 		time += elapsedTime;
@@ -84,14 +85,14 @@ class RenderContext extends h3d.impl.RenderContext {
 		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 ) {
-			o = new ObjectPass();
-			o.nextAlloc = firstAlloc;
-			firstAlloc = o;
+			o = new h3d.pass.PassObject();
+			o.nextAlloc = allocFirst;
+			allocFirst = o;
 		} else
-			pool = o.nextAlloc;
+			allocPool = o.nextAlloc;
 		o.pass = pass;
 		o.obj = obj;
 		o.next = passes;
@@ -115,12 +116,17 @@ class RenderContext extends h3d.impl.RenderContext {
 		lights = l;
 	}
 
+	public function uploadParams() {
+		currentManager.fillParams(shaderBuffers, drawPass.shader, drawPass.shaders);
+		engine.uploadShaderBuffers(shaderBuffers, Params);
+		engine.uploadShaderBuffers(shaderBuffers, Textures);
+	}
+
 	public function done() {
 		drawPass = null;
-		uploadParams = null;
 		// 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.pass = null;
 			p.shader = null;
@@ -128,9 +134,12 @@ class RenderContext extends h3d.impl.RenderContext {
 			p.next = null;
 			p.index = 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 ) {
 			c.s = null;
 			c.next = null;

+ 13 - 16
h3d/scene/Renderer.hx

@@ -2,11 +2,10 @@ package h3d.scene;
 
 class PassObjects {
 	public var name : String;
-	public var passes : h3d.pass.Object;
+	public var passes : h3d.pass.PassList;
 	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 passObjects : SMap<PassObjects>;
 	var allPasses : Array<h3d.pass.Base>;
+	var emptyPasses = new h3d.pass.PassList();
 	var ctx : RenderContext;
 	var hasSetTarget = false;
 
@@ -81,20 +81,17 @@ class Renderer extends hxd.impl.AnyProps {
 	}
 
 	@: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;
-		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 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 = 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 ) {
@@ -134,15 +131,15 @@ class Renderer extends hxd.impl.AnyProps {
 
 	function get( name : String ) {
 		var p = passObjects.get(name);
-		if( p == null ) return null;
+		if( p == null ) return emptyPasses;
 		p.rendered = true;
 		return p.passes;
 	}
 
 	function getSort( name : String, front2Back = false ) {
 		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;
 		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;
 
 		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;
 			if( p == null )
 				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.pickY = pixelY;
 			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();
@@ -393,6 +392,7 @@ class Scene extends Object implements h3d.IDrawable implements hxd.SceneEvents.I
 		// group by pass implementation
 		var curPass = ctx.passes;
 		var passes = [];
+		var passIndex = -1;
 		while( curPass != null ) {
 			var passId = curPass.pass.passId;
 			var p = curPass, prev = null;
@@ -401,7 +401,14 @@ class Scene extends Object implements h3d.IDrawable implements hxd.SceneEvents.I
 				p = p.next;
 			}
 			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;
 		}
 
@@ -413,16 +420,9 @@ class Scene extends Object implements h3d.IDrawable implements hxd.SceneEvents.I
 		// check that passes have been rendered
 		#if debug
 		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.");
-					var o = p.passes;
-					while( o != null ) {
-						trace(" used by " + o.obj.name == null ? "" + o.obj : o.obj.name);
-						o = o.next;
-					}
-				}
-			}
 		#end
 
 		if( camera.rightHanded )
@@ -432,6 +432,11 @@ class Scene extends Object implements h3d.IDrawable implements hxd.SceneEvents.I
 		ctx.scene = null;
 		ctx.camera = 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);
 		// don't do in sync() since animations in our world might affect our chunks
 		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 ) {
 				c.lastFrame = ctx.frame;
 				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 {
 
@@ -28,7 +28,7 @@ class DirLight extends Light {
 	}
 
 	override function getShadowDirection() : h3d.Vector {
-		return absPos.front();	
+		return absPos.front();
 	}
 
 	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 {
 

+ 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
 
@@ -20,10 +20,9 @@ class DepthPass extends h3d.pass.Default {
 		var texture = ctx.textures.allocTarget("depthMap", ctx.engine.width, ctx.engine.height, true);
 		ctx.engine.pushTarget(texture);
 		ctx.engine.clear(enableSky ? 0 : 0xFF0000, 1);
-		passes = super.draw(passes);
+		super.draw(passes);
 		ctx.engine.popTarget();
 		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);
 		ctx.engine.pushTarget(texture);
 		ctx.engine.clear(0x808080, 1);
-		passes = super.draw(passes);
+		super.draw(passes);
 		ctx.engine.popTarget();
 		ctx.setGlobalID(normalMapId, texture);
-		return passes;
 	}
 
 }
 
-class DefaultRenderer extends Renderer {
+class Renderer extends h3d.scene.Renderer {
 
 	var def(get, never) : h3d.pass.Base;
 	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);
 	}
 
+	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 {
 		return absPos.front();
 	}

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

@@ -1,12 +1,5 @@
 package h3d.scene.pbr;
 
-enum ShadowMode {
-	None;
-	Dynamic;
-	Static;
-	Mixed;
-}
-
 class Light extends h3d.scene.Light {
 
 	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 {
 		var light = Std.instance(obj, h3d.scene.pbr.Light);
 		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);
-			return ctx.allocShaderList(light.shader, 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 currentTarget = ctx.engine.getCurrentTarget();
 		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;
 	public var size : Float;
+	public var zNear : Float = 0.02;
 	/**
 		Alias for uniform scale.
 	**/
@@ -17,6 +18,14 @@ class PointLight extends Light {
 		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() {
 		return cullingDistance;
 	}
@@ -26,7 +35,7 @@ class PointLight extends Light {
 		return cullingDistance = v;
 	}
 
-	override function draw(ctx) {
+	override function draw(ctx:RenderContext) {
 		primitive.render(ctx.engine);
 	}
 
@@ -43,10 +52,24 @@ class PointLight extends Light {
 		pbr.pointSize = size;
 	}
 
+	var s = new h3d.col.Sphere();
 	override function emit(ctx:RenderContext) {
+		if( ctx.computingStatic ) {
+			super.emit(ctx);
+			return;
+		}
+
 		if( ctx.pbrLightPass == null )
 			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);
 		ctx.emitPass(ctx.pbrLightPass, this);
 	}

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

@@ -17,6 +17,10 @@ package h3d.scene.pbr;
 		Debug slides
 	*/
 	var Debug = "Debug";
+	/*
+		Distortion
+	*/
+	var Distortion = "Distortion";
 }
 
 @:enum abstract SkyMode(String) {
@@ -34,18 +38,32 @@ package h3d.scene.pbr;
 typedef RenderProps = {
 	var mode : DisplayMode;
 	var env : String;
+	var colorGradingLUT : String;
+	var colorGradingLUTSize : Int;
 	var envPower : Float;
 	var exposure : Float;
 	var sky : SkyMode;
 	var tone : TonemapMap;
 	var emissive : 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 {
 	var slides = new h3d.pass.ScreenFx(new h3d.shader.pbr.Slides());
 	var pbrOut = new h3d.pass.ScreenFx(new h3d.shader.ScreenShader());
 	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 screenLightPass : h3d.pass.ScreenFx<h3d.shader.ScreenShader>;
 	var fxaa = new h3d.pass.FXAA();
@@ -56,14 +74,16 @@ class Renderer extends h3d.scene.Renderer {
 
 	public var skyMode : SkyMode = Hide;
 	public var toneMode : TonemapMap = Reinhard;
+	public var colorgGradingLUT : h3d.mat.Texture;
+	public var colorGradingLUTSize : Int;
 	public var displayMode : DisplayMode = Pbr;
 	public var env : Environment;
 	public var exposure(get,set) : Float;
 	public var debugMode = 0;
-
+	public var shadows = true;
 
 	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"),
 		Vec4([Value("output.normal",3),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([Value("output.normal",3), Value("output.normalStrength",1)]),
 		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) {
 		super();
 		this.env = env;
-		defaultPass = new h3d.pass.Default("default");
+		defaultPass = new h3d.pass.Default("color");
 		slides.addShader(pbrProps);
 		pbrOut.addShader(pbrIndirect);
 		pbrOut.addShader(pbrProps);
@@ -88,6 +110,7 @@ class Renderer extends h3d.scene.Renderer {
 		allPasses.push(output);
 		allPasses.push(defaultPass);
 		allPasses.push(decalsOutput);
+		allPasses.push(new h3d.pass.Shadows(null));
 		refreshProps();
 	}
 
@@ -112,7 +135,7 @@ class Renderer extends h3d.scene.Renderer {
 				to be discarded when the camera is inside its volume.
 			*/
 			pbrLightPass.culling = Front;
-			pbrLightPass.depth(false, Greater);
+			pbrLightPass.depth(false, GreaterEqual);
 			pbrLightPass.enableLights = true;
 		}
 		ctx.pbrLightPass = pbrLightPass;
@@ -128,15 +151,14 @@ class Renderer extends h3d.scene.Renderer {
 		draw("overlay");
 	}
 
-	function drawShadows(){
+	function drawShadows( ls : LightSystem ) {
 		var light = @:privateAccess ctx.lights;
 		var passes = get("shadow");
+		if( !shadows )
+			passes.clear();
 		while( light != null ) {
 			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;
 		}
 	}
@@ -162,12 +184,9 @@ class Renderer extends h3d.scene.Renderer {
 
 	override function render() {
 		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 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("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]);
 		clear(0, 1, 0);
 		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"));
 
 		setTarget(albedo);
@@ -247,7 +279,6 @@ class Renderer extends h3d.scene.Renderer {
 
 		pbrDirect.doDiscard = true;
 
-		var ls = Std.instance(ls, LightSystem);
 		var lpass = screenLightPass;
 		if( lpass == null ) {
 			lpass = new h3d.pass.ScreenFx(new h3d.shader.ScreenShader());
@@ -256,10 +287,18 @@ class Renderer extends h3d.scene.Renderer {
 			lpass.pass.setBlendMode(Add);
 			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;
-		draw("lights");
+		draw(pbrLightPass.name);
 
 		if( renderMode == LightProbe ) {
 			pbrProps.isScreen = true;
@@ -285,22 +324,28 @@ class Renderer extends h3d.scene.Renderer {
 		pbrOut.pass.stencil.setFunc(NotEqual, 0x80, 0x80, 0x80);
 		pbrOut.render();
 
-		draw("BeforeTonemapping");
+		draw("beforeTonemapping");
 
 		apply(AfterHdr);
 
-		var distortion = allocTarget("distortion", true, 1.0, RGBA16F);
+		var distortion = allocTarget("distortion", true, 1.0, RG16F);
 		distortion.clear(0x000000);
 		setTarget(distortion);
-		draw("Distortion");
+		draw("distortion");
 
 		var ldr = allocTarget("ldrOutput");
 		setTarget(ldr);
+		ctx.setGlobal("hdr", hdr);
 		var bloom = ctx.getGlobal("bloom");
 		tonemap.shader.bloom = bloom;
 		tonemap.shader.hasBloom = bloom != null;
 		tonemap.shader.distortion = distortion;
 		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.mode =	switch( toneMode ) {
 									case Linear: 0;
@@ -311,6 +356,7 @@ class Renderer extends h3d.scene.Renderer {
 		tonemap.render();
 
 		postDraw();
+		apply(Final);
 		resetTarget();
 
 		switch( displayMode ) {
@@ -318,8 +364,11 @@ class Renderer extends h3d.scene.Renderer {
 		case Pbr, Env, MatCap:
 			fxaa.apply(ldr);
 
-		case Debug:
+		case Distortion:
+			resetTarget();
+			copy( distortion, null);
 
+		case Debug:
 			var shadowMap = ctx.textures.getNamed("shadowMap");
 			if( shadowMap == null )
 				shadowMap = h3d.mat.Texture.fromColor(0);
@@ -331,7 +380,6 @@ class Renderer extends h3d.scene.Renderer {
 				hasDebugEvent = true;
 				hxd.Window.getInstance().addEventTarget(onEvent);
 			}
-
 		}
 
 		if( hasDebugEvent && displayMode != Debug ) {
@@ -364,12 +412,15 @@ class Renderer extends h3d.scene.Renderer {
 		var props : RenderProps = {
 			mode : Pbr,
 			env : null,
+			colorGradingLUT : null,
+			colorGradingLUTSize : 1,
 			envPower : 1.,
 			emissive : 1.,
 			exposure : 0.,
 			sky : Irrad,
 			tone : Linear,
-			occlusion : 1.
+			occlusion : 1.,
+			shadows: true
 		};
 		return props;
 	}
@@ -391,6 +442,13 @@ class Renderer extends h3d.scene.Renderer {
 		toneMode = props.tone;
 		exposure = props.exposure;
 		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
@@ -406,6 +464,10 @@ class Renderer extends h3d.scene.Renderer {
 						<option value="Reinhard">Reinhard</option>
 					</select>
 				</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>
 				<dd>
 					<select field="mode">
@@ -413,8 +475,10 @@ class Renderer extends h3d.scene.Renderer {
 						<option value="Env">Env</option>
 						<option value="MatCap">MatCap</option>
 						<option value="Debug">Debug</option>
+						<option value="Distortion">Distortion</option>
 					</select>
 				</dd>
+
 				<dt>Env</dt>
 				<dd>
 					<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>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>Shadows</dt><dd><input type="checkbox" field="shadows"></dd>
 			</dl>
 			</div>
 		');

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

@@ -15,10 +15,24 @@ class SpotLight extends Light {
 		pbr = new h3d.shader.pbr.Light.SpotLight();
 		shadows = new h3d.pass.SpotShadowMap(this);
 		super(pbr,parent);
-		range = 10;
 		generatePrim();
 		lightProj = new h3d.Camera();
 		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() {
@@ -81,7 +95,7 @@ class SpotLight extends Light {
 		return absPos.front();
 	}
 
-	override function draw(ctx) {
+	override function draw(ctx:RenderContext) {
 		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) {
 		if( ctx.pbrLightPass == null )
 			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);
 		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);
 	}
 
+	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 {
 
 		if(lightProbeTexture == null)

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

@@ -7,6 +7,7 @@ class Surface {
 	public var tilling = 1.0;
 	public var offset : h3d.Vector;
 	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){
 		this.albedo = albedo;
@@ -15,6 +16,15 @@ class Surface {
 		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() {
 		if(albedo != null) albedo.dispose();
 		if(normal != null) normal.dispose();
@@ -38,6 +48,11 @@ class SurfaceArray {
 		pbr.wrap = Repeat;
 	}
 
+	public function clone() : SurfaceArray {
+		var o = new SurfaceArray(albedo.layerCount, albedo.width);
+		return o;
+	}
+
 	public function dispose() {
 		if(albedo != null) albedo.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 parallaxMaxStep : Int;
 	public var heightBlendStrength : Float;
-	public var heightBlendSharpness : Float;
+	public var blendSharpness : Float;
 	public var copyPass (default, null): h3d.pass.Copy;
 	public var tiles (default, null) : Array<Tile> = [];
 	public var surfaces (default, null) : Array<Surface> = [];
@@ -25,6 +25,36 @@ class Terrain extends Object {
 		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 {
 		var z = 0.0;
 		var t = getTileAtWorldPos(x, y);
@@ -83,7 +113,7 @@ class Terrain extends Object {
 	public function updateSurfaceParams(){
 		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.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 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);
 		this.tileX = x;
 		this.tileY = y;
 		shader = new h3d.shader.pbr.Terrain();
 		material.mainPass.addShader(shader);
 		material.mainPass.culling = None;
+		material.shadows = false;
 		this.x = x * getTerrain().tileSize;
 		this.y = y * getTerrain().tileSize;
 		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;
 		return heightMap = v;
 	}
 
-	inline function getTerrain(){
+	inline function getTerrain() {
 		return Std.instance(parent, Terrain);
 	}
 
-	public function getHeightPixels(){
-		if(needNewPixelCapture || heightmapPixels == null)
+	public function getHeightPixels() {
+		if( needNewPixelCapture || heightmapPixels == null )
 			heightmapPixels = heightMap.capturePixels();
 		needNewPixelCapture = false;
 		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();
 		 	grid = new h3d.prim.Grid( getTerrain().cellCount, getTerrain().cellCount, getTerrain().cellSize, getTerrain().cellSize);
 			primitive = grid;
@@ -56,21 +76,21 @@ class Tile extends h3d.scene.Mesh {
 		computeNormals();
 	}
 
-	public function blendEdges(){
+	public function blendEdges() {
 		var adjTileX = getTerrain().getTile(tileX - 1, tileY);
-		if( adjTileX != null){
+		if( adjTileX != null ) {
 			var flags = new haxe.EnumFlags<Direction>();
         	flags.set(Left);
 			adjTileX.computeEdgesHeight(flags);
 		}
 		var adjTileY = getTerrain().getTile(tileX, tileY - 1);
-		if( adjTileY != null){
+		if( adjTileY != null ) {
 			var flags = new haxe.EnumFlags<Direction>();
         	flags.set(Up);
 			adjTileY.computeEdgesHeight(flags);
 		}
 		var adjTileXY = getTerrain().getTile(tileX - 1, tileY - 1);
-		if( adjTileXY != null){
+		if( adjTileXY != null ) {
 			var flags = new haxe.EnumFlags<Direction>();
         	flags.set(UpLeft);
 			adjTileXY.computeEdgesHeight(flags);
@@ -86,92 +106,92 @@ class Tile extends h3d.scene.Mesh {
 	}
 
 	public function refresh(){
-		if(heightMap == null || heightMap.width != getTerrain().heightMapResolution + 1){
+		if( heightMap == null || heightMap.width != getTerrain().heightMapResolution + 1 ) {
 			var oldHeightMap = heightMap;
 			heightMap = new h3d.mat.Texture(getTerrain().heightMapResolution + 1, getTerrain().heightMapResolution + 1, [Target], RGBA32F );
 			heightMap.wrap = Clamp;
 			heightMap.filter = Linear;
 			heightMap.preventAutoDispose();
 			heightMap.realloc = null;
-			if(oldHeightMap != null){
+			if( oldHeightMap != null ) {
 				getTerrain().copyPass.apply(oldHeightMap, heightMap);
 				oldHeightMap.dispose();
 			}
 			needNewPixelCapture = true;
 		}
 
-		if(surfaceIndexMap == null || surfaceIndexMap.width != getTerrain().weightMapResolution){
+		if( surfaceIndexMap == null || surfaceIndexMap.width != getTerrain().weightMapResolution ) {
 			var oldSurfaceIndexMap = surfaceIndexMap;
 			surfaceIndexMap = new h3d.mat.Texture(getTerrain().weightMapResolution, getTerrain().weightMapResolution, [Target], RGBA);
 			surfaceIndexMap.filter = Nearest;
 			surfaceIndexMap.preventAutoDispose();
 			surfaceIndexMap.realloc = null;
-			if(oldSurfaceIndexMap != null){
+			if( oldSurfaceIndexMap != null ) {
 				getTerrain().copyPass.apply(oldSurfaceIndexMap, surfaceIndexMap);
 				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;
 			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].wrap = Clamp;
 				surfaceWeights[i].preventAutoDispose();
 				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]);
 			}
 			generateWeightArray();
 
-			for(i in 0 ... oldArray.length)
+			for( i in 0 ... oldArray.length )
 				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.wrap = Clamp;
 			surfaceWeightArray.preventAutoDispose();
 			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>){
 
-		if(heightMap == null) return;
+		if( heightMap == null ) return;
 		var pixels : hxd.Pixels.PixelsFloat = getHeightPixels();
 
-		if(flag.has(Left)){
+		if( flag.has(Left) ) {
 			var adjTileX = getTerrain().getTile(tileX + 1, tileY);
 			var adjHeightMapX = adjTileX != null ? adjTileX.heightMap : null;
-			if(adjHeightMapX != null){
+			if( adjHeightMapX != null ) {
 				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) );
 				}
 			}
 		}
-		if(flag.has(Up)){
+		if( flag.has(Up) ) {
 			var adjTileY = getTerrain().getTile(tileX, tileY + 1);
 			var adjHeightMapY = adjTileY != null ? adjTileY.heightMap : null;
-			if(adjHeightMapY != null){
+			if( adjHeightMapY != null ) {
 				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) );
 				}
 			}
 		}
-		if(flag.has(UpLeft)){
+		if( flag.has(UpLeft) ) {
 			var adjTileXY = getTerrain().getTile(tileX + 1, tileY + 1);
 			var adjHeightMapXY = adjTileXY != null ? adjTileXY.heightMap : null;
-			if(adjHeightMapXY != null){
+			if( adjHeightMapXY != null ) {
 				var adjpixels : hxd.Pixels.PixelsFloat = adjTileXY.getHeightPixels();
 				pixels.setPixelF(heightMap.width - 1, heightMap.height - 1, adjpixels.getPixelF(0,0));
 			}
@@ -181,8 +201,8 @@ class Tile extends h3d.scene.Mesh {
 		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 triCount = Std.int(grid.triCount() / grid.width);
 		var vertexCount = grid.width + 1;
@@ -191,7 +211,7 @@ class Tile extends h3d.scene.Mesh {
 		var istep = triCount * 3 - 6;
 		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.z += tile.getHeight(t0.x / getTerrain().tileSize, t0.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 adjDownRightGrid = adjDownRightTile != null ? adjDownRightTile.grid: null;
 
-		if(adjUpGrid != null && adjUpGrid.normals != null){
+		if( adjUpGrid != null && adjUpGrid.normals != null ) {
 			var pos = 0;
-			for( i in 0 ... vertexCount)
+			for( i in 0 ... vertexCount )
 				adjUpGrid.normals[i].set(0,0,0);
 			for( i in 0 ... triCount ) {
 				i0 = adjUpGrid.idx[pos++]; i1 = adjUpGrid.idx[pos++]; i2 = adjUpGrid.idx[pos++];
 				computeVertexPos(adjUpTile);
 				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();
-			for( i in 1 ... vertexCount - 1){
+			for( i in 1 ... vertexCount - 1 ) {
 				var n = grid.normals[s + i].add(adjUpGrid.normals[i]);
 				n.normalize();
 				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;
 			for( i in 0 ... vertexCount)
 				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++];
 				computeVertexPos(adjDownTile);
 				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();
-			for( i in 1 ... vertexCount - 1){
+			for( i in 1 ... vertexCount - 1 ){
 				var n = grid.normals[i].add(adjDownGrid.normals[s + i]);
 				n.normalize();
 				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 istep = triCount * 3 - 6;
 			var needStep = false;
-			for( i in 0 ... vertexCount)
+			for( i in 0 ... vertexCount )
 				adjLeftGrid.normals[i * step].set(0,0,0);
 			for( i in 0 ... triCount ) {
 				i0 = adjLeftGrid.idx[pos++]; i1 = adjLeftGrid.idx[pos++]; i2 = adjLeftGrid.idx[pos++];
 				computeVertexPos(adjLeftTile);
 				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;
 			}
-			for( i in 0 ... vertexCount)
+			for( i in 0 ... vertexCount )
 				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]);
 				n.normalize();
 				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 istep = (triCount - 2) * 3;
 			var needStep = false;
-			for( i in 0 ... vertexCount)
+			for( i in 0 ... vertexCount )
 				adjRightGrid.normals[i * step + (step - 1)].set(0,0,0);
 			for( i in 0 ... triCount ) {
 				i0 = adjRightGrid.idx[pos++]; i1 = adjRightGrid.idx[pos++]; i2 = adjRightGrid.idx[pos++];
 				computeVertexPos(adjRightTile);
 				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;
 			}
-			for( i in 0 ... vertexCount)
+			for( i in 0 ... vertexCount )
 				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)]);
 				n.normalize();
 				grid.normals[i * step].load(n);
@@ -325,7 +345,7 @@ class Tile extends h3d.scene.Mesh {
 		var upRight = step * grid.height;
 
 		var n = new h3d.col.Point();
-		if(adjUpRightGrid != null && adjUpRightGrid.normals != null){
+		if( adjUpRightGrid != null && adjUpRightGrid.normals != null ) {
 			var pos = (triCount) * 3 - 6;
 			i0 = adjUpRightGrid.idx[pos++]; i1 = adjUpRightGrid.idx[pos++]; i2 = adjUpRightGrid.idx[pos++];
 			computeVertexPos(adjUpRightTile);
@@ -335,34 +355,34 @@ class Tile extends h3d.scene.Mesh {
 			n = n.add(computeNormal());
 			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.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);
 
 		n.set(0,0,0);
-		if(adjUpLeftGrid != null && adjUpLeftGrid.normals != null){
+		if( adjUpLeftGrid != null && adjUpLeftGrid.normals != null ) {
 			var pos = 0;
 			i0 = adjUpLeftGrid.idx[pos++]; i1 = adjUpLeftGrid.idx[pos++]; i2 = adjUpLeftGrid.idx[pos++];
 			computeVertexPos(adjUpLeftTile);
 			n = computeNormal();
 			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.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);
 
 		n.set(0,0,0);
-		if(adjDownLeftGrid != null && adjDownLeftGrid.normals != null){
+		if( adjDownLeftGrid != null && adjDownLeftGrid.normals != null ) {
 			var pos = (triCount) * 3 * (adjDownLeftGrid.height - 1) ;
 			i0 = adjDownLeftGrid.idx[pos++]; i1 = adjDownLeftGrid.idx[pos++]; i2 = adjDownLeftGrid.idx[pos++];
 			computeVertexPos(adjDownLeftTile);
@@ -372,48 +392,48 @@ class Tile extends h3d.scene.Mesh {
 			n = n.add(computeNormal());
 			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.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);
 
 		n.set(0,0,0);
-		if(adjDownRightGrid != null && adjDownRightGrid.normals != null){
+		if( adjDownRightGrid != null && adjDownRightGrid.normals != null ) {
 			var pos = triCount * 3 * adjDownRightGrid.width - 3;
 			i0 = adjDownRightGrid.idx[pos++]; i1 = adjDownRightGrid.idx[pos++]; i2 = adjDownRightGrid.idx[pos++];
 			computeVertexPos(adjDownRightTile);
 			n = computeNormal();
 			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.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);
 
-		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;
 	}
 
-	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 = [
-		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];
 			} else
@@ -452,9 +472,9 @@ class Tile extends h3d.scene.Mesh {
 
 	public function getHeight(u : Float, v : Float, ?fast = false) : Float {
 		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;
 			}
 			var px = u * (heightMap.width - 1) + 0.5;
@@ -480,24 +500,24 @@ class Tile extends h3d.scene.Mesh {
 	}
 
 	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 cachedHeightBound : Bool = false;
 	override function emit( ctx:RenderContext ){
-		if(!isReady()) return;
-		if(cachedBounds == null) {
+		if( !isReady() ) return;
+		if( cachedBounds == null ) {
 			cachedBounds = getBounds();
 			cachedBounds.zMax = 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);
 					cachedBounds.zMin = cachedBounds.zMin > h ? h : cachedBounds.zMin;
 					cachedBounds.zMax = cachedBounds.zMax < h ? h : cachedBounds.zMax;
@@ -505,53 +525,62 @@ class Tile extends h3d.scene.Mesh {
 			}
 			cachedHeightBound = true;
 		}
-		if(ctx.camera.frustum.hasBounds(cachedBounds))
+		if( ctx.camera.frustum.hasBounds(cachedBounds) )
 			super.emit(ctx);
 	}
 
 	override function sync(ctx:RenderContext) {
-		if(!isReady()) return;
+		if( !isReady() ) return;
 
 		shader.SHOW_GRID = getTerrain().showGrid;
 		shader.SURFACE_COUNT = getTerrain().surfaces.length;
 		shader.CHECKER = getTerrain().showChecker;
 		shader.COMPLEXITY = getTerrain().showComplexity;
+		shader.PARALLAX = getTerrain().parallaxAmount != 0;
 
 		shader.heightMapSize = heightMap.width;
 		shader.primSize = getTerrain().tileSize;
 		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(){
-		if(primitive == null)
-			return false;
-		if( getTerrain().surfaceArray == null || getTerrain().surfaces.length == 0 || surfaceWeights.length != getTerrain().surfaces.length)
+		if( primitive == null )
 			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 )
 			return false;
-		for( i in 0 ... surfaceWeights.length )
-			if( surfaceWeights[i] == null )
-				return false;
+
 		return true;
 	}
 
-	override function getLocalCollider():h3d.col.Collider {
+	override function getLocalCollider() : h3d.col.Collider {
 		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 = {
 
 		@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;
 
 		@ignore @param var depthTexture : Channel;
@@ -26,6 +26,9 @@ class SAO extends ScreenShader {
 		@ignore @param var screenRatio : Vec2;
 		@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 {
 			// 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)
@@ -76,6 +79,8 @@ class SAO extends ScreenShader {
 			occlusion = 1.0 - occlusion / float(numSamples);
 			occlusion = pow(occlusion, 1.0 + intensity).saturate();
 
+			occlusion *= mix(1, microOcclusion.get(vUV).r, microOcclusionIntensity);
+
 			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 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) {
 
 			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 CHECKER : Bool;
 		@const var COMPLEXITY : Bool;
+		@const var PARALLAX : Bool;
 
 		@param var heightMapSize : Float;
 		@param var primSize : Float;
@@ -24,7 +25,8 @@ class Terrain extends hxsl.Shader {
 		@param var secondSurfaceParams : Array<Vec4, SURFACE_COUNT>;
 
 		@param var heightBlendStrength : Float;
-		@param var heightBlendSharpness : Float;
+		@param var blendSharpness : Float;
+
 		@param var parallaxAmount : Float;
 		@param var minStep : Int;
 		@param var maxStep : Int;
@@ -39,155 +41,78 @@ class Terrain extends hxsl.Shader {
 		var roughnessValue : Float;
 		var occlusionValue : Float;
 
+		var tangentViewPos : Vec3;
+		var tangentFragPos : Vec3;
+
 		function vertex() {
 			calculatedUV = input.position.xy / primSize;
 			var terrainUV = (calculatedUV * (heightMapSize - 1)) / heightMapSize;
 			terrainUV += 0.5 / heightMapSize;
 			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);
+			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 surfaceUV = vec3(res, i);
 			return surfaceUV;
 		}
 
 		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);
 				pixelColor = vec4(mix(vec3(0.4), vec3(0.1), tile), 1.0);
 				transformedNormal = vec3(0,0,1) * TBN;
@@ -196,7 +121,7 @@ class Terrain extends hxsl.Shader {
 				occlusionValue = 1;
 				emissiveValue = 0;
 			}
-			else if(COMPLEXITY){
+			else if( COMPLEXITY ) {
 				var blendCount = 0 + weightTextures.get(vec3(0)).r * 0;
 				for(i in 0 ... SURFACE_COUNT)
 					blendCount += ceil(weightTextures.get(vec3(calculatedUV, i)).r);
@@ -207,7 +132,61 @@ class Terrain extends hxsl.Shader {
 				metalnessValue = 0;
 				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 tileEdgeColor = vec4(1,1,0,1);
 				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;
 		@param var bloom : Sampler2D;
+
 		@const var hasDistortion : Bool;
 		@param var distortion : Sampler2D;
+
+		@const var hasColorGrading : Bool;
+		@param var colorGradingLUT : Sampler2D;
+		@param var lutSize : Float;
+
 		@param var pixelSize : Vec2;
 
 		function fragment() {
 
-			if(hasDistortion){
+			if( hasDistortion)  {
 				var baseUV = calculatedUV;
 				var distortionVal = distortion.get(baseUV).rg;
 				calculatedUV = baseUV + distortionVal ;
@@ -41,6 +47,23 @@ class ToneMapping extends ScreenShader {
 			// gamma correct
 			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;
 		}
 	}

+ 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() {
 		var dir = haxe.macro.Context.definedValue("resourcesPath");
 		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}));
 	}
 

+ 8 - 0
hxd/System.hl.hx

@@ -130,9 +130,17 @@ class System {
 			#if hxtelemetry
 			hxt.advance_frame();
 			#end
+			#if hot_reload
+			check_reload();
+			#end
 		}
 		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 ) {
 		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;
 
 	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 {
@@ -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)
 		window.resize(width, height);
 		#end
+		windowWidth = width;
+		windowHeight = height;
+		for( f in resizeEvents ) f();
 	}
 
 	public function setFullScreen( v : Bool ) : Void {
@@ -327,7 +330,7 @@ class Window {
 			1086 => K.NUMPAD_SUB,
 			1099 => K.NUMPAD_DOT,
 			1084 => K.NUMPAD_DIV,
-			
+
 			39 => K.QWERTY_QUOTE,
 			44 => K.QWERTY_COMMA,
 			45 => K.QWERTY_MINUS,

+ 39 - 14
hxd/Window.js.hx

@@ -12,6 +12,7 @@ class Window {
 	public var mouseLock(get, set) : Bool;
 	public var vsync(get, set) : Bool;
 	public var isFocused(get, never) : Bool;
+	public var propagateKeyEvents : Bool;
 
 	var curMouseX : Float = 0.;
 	var curMouseY : Float = 0.;
@@ -24,23 +25,32 @@ class Window {
 	var curW : Int;
 	var curH : Int;
 
-	var focused = true;
+	var focused : Bool;
 
 	public function new( ?canvas : js.html.CanvasElement, ?globalEvents ) : Void {
+		var customCanvas = canvas != null;
 		eventTargets = new List();
 		resizeEvents = new List();
 
-		element = canvas == null || globalEvents ? js.Browser.window : canvas;
 		if( canvas == null ) {
 			canvas = cast js.Browser.document.getElementById("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.propagateKeyEvents = globalEvents;
+		focused = globalEvents;
+		element = globalEvents ? js.Browser.window : canvas;
 		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("mousemove", onMouseMove);
 		element.addEventListener("mouseup", onMouseUp);
 		element.addEventListener("wheel", onMouseWheel);
 		element.addEventListener("touchstart", onTouchStart);
@@ -51,25 +61,28 @@ class Window {
 		element.addEventListener("keypress", onKeyPress);
 		element.addEventListener("blur", onFocus.bind(false));
 		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) {
 				onMouseDown(e);
 				e.stopPropagation();
 				e.preventDefault();
 			});
-			canvas.oncontextmenu = function(e){
-				e.stopPropagation();
-				e.preventDefault();
-				return false;
-			};
 			element.addEventListener("contextmenu",function(e) {
 				e.stopPropagation();
 				e.preventDefault();
 				return false;
 			});
+		} else {
+			// allow focus
+			if( canvas.getAttribute("tabindex") == null )
+				canvas.setAttribute("tabindex","1");
+			canvas.style.outline = 'none';
 		}
 		curW = this.width;
 		curH = this.height;
@@ -254,18 +267,30 @@ class Window {
 		var ev = new Event(EKeyUp, mouseX, mouseY);
 		ev.keyCode = e.keyCode;
 		event(ev);
+		if( !propagateKeyEvents ) {
+			e.preventDefault();
+			e.stopPropagation();
+		}
 	}
 
 	function onKeyDown(e:js.html.KeyboardEvent) {
 		var ev = new Event(EKeyDown, mouseX, mouseY);
 		ev.keyCode = e.keyCode;
 		event(ev);
+		if( !propagateKeyEvents ) {
+			e.preventDefault();
+			e.stopPropagation();
+		}
 	}
 
 	function onKeyPress(e:js.html.KeyboardEvent) {
 		var ev = new Event(ETextInput, mouseX, mouseY);
 		ev.charCode = e.charCode;
 		event(ev);
+		if( !propagateKeyEvents ) {
+			e.preventDefault();
+			e.stopPropagation();
+		}
 	}
 
 	function onFocus(b: Bool) {

+ 2 - 2
hxd/inspect/ScenePanel.hx

@@ -139,7 +139,7 @@ private class CustomSceneProps extends SceneProps {
 							if( g.m.bits == mid ) {
 								var inf = emap.get(g);
 								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);
 									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) ) {
 					var m = r.toMesh();
 					var inf = extraMap.get(r.name);
-					var npass = Lambda.count({ iterator : m.material.getPasses });
+					var npass = m.material.getPasses().length;
 					if( inf == null ) {
 						inf = { count : 0, pass : npass, tri : 0, name : r.name, e : null, g : null };
 						all.push(inf);

+ 9 - 7
hxd/inspect/SceneProps.hx

@@ -81,11 +81,13 @@ class SceneProps {
 
 			var ls = scene.lightSystem;
 			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 )
 				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(PRange("priority", 0, 10, function() return l.priority, function(p) l.priority = Std.int(p),1));
 		props.push(PBool("enableSpecular", function() return l.enableSpecular, function(b) l.enableSpecular = b));
-		var dl = Std.instance(l, h3d.scene.DirLight);
+		var dl = Std.instance(l, h3d.scene.fwd.DirLight);
 		if( dl != null )
 			props.push(PFloats("direction", function() {
 				var dir = dl.getDirection();
 				return [dl.x, dl.y, dl.z];
 			}, function(fl) dl.setDirection(new h3d.Vector(fl[0], fl[1], fl[2]))));
-		var pl = Std.instance(l, h3d.scene.PointLight);
+		var pl = Std.instance(l, h3d.scene.fwd.PointLight);
 		if( pl != null )
 			props.push(PFloats("params", function() return [pl.params.x, pl.params.y, pl.params.z], function(fl) pl.params.set(fl[0], fl[1], fl[2], fl[3])));
 		return PGroup("Light", props);

+ 1 - 1
hxd/poly2tri/VisiblePolygon.hx

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

部分文件因为文件数量过多而无法显示