瀏覽代碼

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

trethaller 6 年之前
父節點
當前提交
17357cf35e
共有 60 個文件被更改,包括 2414 次插入119 次删除
  1. 1 0
      .gitignore
  2. 6 6
      hide/Ide.hx
  3. 17 17
      hide/comp/SceneEditor.hx
  4. 2 2
      hide/prefab/ContextShared.hx
  5. 2 2
      hide/prefab/EditContext.hx
  6. 3 3
      hide/prefab/HideProps.hx
  7. 1 1
      hide/prefab/PolygonEditor.hx
  8. 3 3
      hide/prefab/terrain/Brush.hx
  9. 4 4
      hide/prefab/terrain/TerrainEditor.hx
  10. 2 2
      hide/view/FXEditor.hx
  11. 1 1
      hide/view/FileTree.hx
  12. 4 4
      hide/view/Model.hx
  13. 4 4
      hide/view/Prefab.hx
  14. 4 4
      hide/view/l3d/Level3D.hx
  15. 5 5
      hide/view/l3d/LightProbeBaker.hx
  16. 1 1
      hide/view/l3d/ProbeBakerProcess.hx
  17. 1 1
      hrt/prefab/Blur.hx
  18. 1 1
      hrt/prefab/Box.hx
  19. 1 1
      hrt/prefab/Constraint.hx
  20. 86 0
      hrt/prefab/Context.hx
  21. 237 0
      hrt/prefab/ContextShared.hx
  22. 1 1
      hrt/prefab/Curve.hx
  23. 77 0
      hrt/prefab/Library.hx
  24. 1 1
      hrt/prefab/Light.hx
  25. 3 4
      hrt/prefab/Material.hx
  26. 2 2
      hrt/prefab/Model.hx
  27. 1 1
      hrt/prefab/Noise.hx
  28. 1 1
      hrt/prefab/Object3D.hx
  29. 413 0
      hrt/prefab/Prefab.hx
  30. 3 3
      hrt/prefab/Reference.hx
  31. 3 3
      hrt/prefab/RenderProps.hx
  32. 17 0
      hrt/prefab/Resource.hx
  33. 1 1
      hrt/prefab/Scene.hx
  34. 1 1
      hrt/prefab/Settings.hx
  35. 2 2
      hrt/prefab/Shader.hx
  36. 1 1
      hrt/prefab/Trail.hx
  37. 26 0
      hrt/prefab/Unknown.hx
  38. 1 1
      hrt/prefab/fx/AnimEvent.hx
  39. 2 2
      hrt/prefab/fx/Emitter.hx
  40. 1 1
      hrt/prefab/fx/Event.hx
  41. 3 3
      hrt/prefab/fx/FX.hx
  42. 2 2
      hrt/prefab/fx/LookAt.hx
  43. 0 2
      hrt/prefab/import.hx
  44. 1 1
      hrt/prefab/l3d/AdvancedDecal.hx
  45. 2 2
      hrt/prefab/l3d/Camera.hx
  46. 1 1
      hrt/prefab/l3d/Decal.hx
  47. 3 3
      hrt/prefab/l3d/Instance.hx
  48. 6 9
      hrt/prefab/l3d/Level3D.hx
  49. 1 1
      hrt/prefab/l3d/MeshGenerator.hx
  50. 1 1
      hrt/prefab/l3d/Polygon.hx
  51. 1 1
      hrt/prefab/l3d/VolumetricLightmap.hx
  52. 71 0
      hrt/prefab/rfx/Bloom.hx
  53. 82 0
      hrt/prefab/rfx/DistanceFog.hx
  54. 24 0
      hrt/prefab/rfx/RendererFX.hx
  55. 86 0
      hrt/prefab/rfx/Sao.hx
  56. 218 0
      hrt/prefab/terrain/Shader.hx
  57. 71 0
      hrt/prefab/terrain/Surface.hx
  58. 6 6
      hrt/prefab/terrain/Terrain.hx
  59. 278 0
      hrt/prefab/terrain/TerrainMesh.hx
  60. 614 0
      hrt/prefab/terrain/Tile.hx

+ 1 - 0
.gitignore

@@ -6,3 +6,4 @@
 *.lnk
 /bin/*.map
 /bin/shaders
+/out.hl

+ 6 - 6
hide/Ide.hx

@@ -645,10 +645,10 @@ class Ide {
 		return str;
 	}
 
-	public function loadPrefab<T:hxd.prefab.Prefab>( file : String, ?cl : Class<T> ) : T {
+	public function loadPrefab<T:hrt.prefab.Prefab>( file : String, ?cl : Class<T> ) : T {
 		if( file == null )
 			return null;
-		var l = hxd.prefab.Library.create(file.split(".").pop().toLowerCase());
+		var l = hrt.prefab.Library.create(file.split(".").pop().toLowerCase());
 		try {
 			l.loadData(parseJSON(sys.io.File.getContent(getPath(file))));
 		} catch( e : Dynamic ) {
@@ -660,13 +660,13 @@ class Ide {
 		return l.get(cl);
 	}
 
-	public function savePrefab( file : String, f : hxd.prefab.Prefab ) {
+	public function savePrefab( file : String, f : hrt.prefab.Prefab ) {
 		var content = f.saveData();
 		sys.io.File.saveContent(getPath(file), toJSON(content));
 	}
 
-	public function filterPrefabs( callb : hxd.prefab.Prefab -> Bool ) {
-		var exts = Lambda.array({iterator : @:privateAccess hxd.prefab.Library.registeredExtensions.keys });
+	public function filterPrefabs( callb : hrt.prefab.Prefab -> Bool ) {
+		var exts = Lambda.array({iterator : @:privateAccess hrt.prefab.Library.registeredExtensions.keys });
 		exts.push("prefab");
 		var todo = [];
 		browseFiles(function(path) {
@@ -956,7 +956,7 @@ class Ide {
 
 	static function main() {
 		h3d.pass.ShaderManager.STRICT = false; // prevent errors with bad renderer
-		hide.tools.Macros.include(["hide.view","h3d.prim","h3d.scene","h3d.pass","hide.prefab","hxd.prefab","hrt"]);
+		hide.tools.Macros.include(["hide.view","h3d.prim","h3d.scene","h3d.pass","hide.prefab","hrt"]);
 		new Ide();
 	}
 

+ 17 - 17
hide/comp/SceneEditor.hx

@@ -3,7 +3,7 @@ package hide.comp;
 import hxd.Key as K;
 import hxd.Math as M;
 
-import hxd.prefab.Prefab as PrefabElement;
+import hrt.prefab.Prefab as PrefabElement;
 import hrt.prefab.Object3D;
 import h3d.scene.Object;
 
@@ -38,12 +38,12 @@ class SceneEditorContext extends hide.prefab.EditContext {
 		}
 	}
 
-	override function getCurrentProps( p : hxd.prefab.Prefab ) {
+	override function getCurrentProps( p : hrt.prefab.Prefab ) {
 		var cur = editor.curEdit;
 		return cur != null && cur.elements[0] == p ? editor.properties.element : new Element();
 	}
 
-	function getContextRec( p : hxd.prefab.Prefab ) {
+	function getContextRec( p : hrt.prefab.Prefab ) {
 		if( p == null )
 			return editor.context;
 		var c = editor.context.shared.contexts.get(p);
@@ -57,7 +57,7 @@ class SceneEditorContext extends hide.prefab.EditContext {
 		editor.selectObjects(elements);
 	}
 
-	override function rebuildPrefab( p : hxd.prefab.Prefab ) {
+	override function rebuildPrefab( p : hrt.prefab.Prefab ) {
 		// refresh all for now
 		editor.refresh();
 	}
@@ -85,7 +85,7 @@ class SceneEditor {
 	public var favTree : hide.comp.IconTree<PrefabElement>;
 	public var scene : hide.comp.Scene;
 	public var properties : hide.comp.PropsEditor;
-	public var context(default,null) : hxd.prefab.Context;
+	public var context(default,null) : hrt.prefab.Context;
 	public var curEdit(default, null) : SceneEditorContext;
 	public var snapToGround = false;
 	public var localTransform = true;
@@ -137,7 +137,7 @@ class SceneEditor {
 			onResize();
 		};
 
-		context = new hxd.prefab.Context();
+		context = new hrt.prefab.Context();
 		context.shared = new hide.prefab.ContextShared(scene);
 		context.shared.currentPath = view.state.path;
 		context.init();
@@ -183,7 +183,7 @@ class SceneEditor {
 
 		// Load display state
 		{
-			var all = sceneData.flatten(hxd.prefab.Prefab);
+			var all = sceneData.flatten(hrt.prefab.Prefab);
 			var list = @:privateAccess view.getDisplayState("hideList");
 			if(list != null) {
 				var m = [for(i in (list:Array<Dynamic>)) i => true];
@@ -426,7 +426,7 @@ class SceneEditor {
 
 	function refreshTree( ?callb ) {
 		tree.refresh(function() {
-			var all = sceneData.flatten(hxd.prefab.Prefab);
+			var all = sceneData.flatten(hrt.prefab.Prefab);
 			for(elt in all) {
 				var el = tree.getElement(elt);
 				if(el == null) continue;
@@ -465,7 +465,7 @@ class SceneEditor {
 		scene.engine.backgroundColor = bgcol;
 		refreshInteractives();
 
-		var all = sceneData.flatten(hxd.prefab.Prefab);
+		var all = sceneData.flatten(hrt.prefab.Prefab);
 		for(elt in all)
 			applySceneStyle(elt);
 		onRefresh();
@@ -947,7 +947,7 @@ class SceneEditor {
 		fillProps(edit, e);
 	}
 
-	function setObjectSelected( p : PrefabElement, ctx : hxd.prefab.Context, b : Bool ) {
+	function setObjectSelected( p : PrefabElement, ctx : hrt.prefab.Context, b : Bool ) {
 		p.setSelected(ctx, b);
 	}
 
@@ -989,7 +989,7 @@ class SceneEditor {
 		setupGizmo();
 	}
 
-	function hasBeenRemoved( e : hxd.prefab.Prefab ) {
+	function hasBeenRemoved( e : hrt.prefab.Prefab ) {
 		while( e != null && e != sceneData ) {
 			if( e.parent != null && e.parent.children.indexOf(e) < 0 )
 				return true;
@@ -1035,7 +1035,7 @@ class SceneEditor {
 			var obj3d : Object3D;
 			var relative = ide.makeRelative(path);
 
-			if(hxd.prefab.Library.getPrefabType(path) != null) {
+			if(hrt.prefab.Library.getPrefabType(path) != null) {
 				var ref = new hrt.prefab.Reference(parent);
 				ref.refpath = "/" + relative;
 				obj3d = ref;
@@ -1156,7 +1156,7 @@ class SceneEditor {
 			view.setClipboard(curEdit.rootElements[0].saveData(), "prefab");
 		}
 		else {
-			var lib = new hxd.prefab.Library();
+			var lib = new hrt.prefab.Library();
 			for(e in curEdit.rootElements) {
 				lib.children.push(e);
 			}
@@ -1171,14 +1171,14 @@ class SceneEditor {
 		}
 		var obj = haxe.Json.parse(haxe.Json.stringify(view.getClipboard("prefab")));
 		if(obj != null) {
-			var p = hxd.prefab.Prefab.loadPrefab(obj, parent);
+			var p = hrt.prefab.Prefab.loadPrefab(obj, parent);
 			autoName(p);
 			refresh();
 		}
 		else {
 			obj = view.getClipboard("library");
 			if(obj != null) {
-				var lib = hxd.prefab.Prefab.loadPrefab(obj);
+				var lib = hrt.prefab.Prefab.loadPrefab(obj);
 				for(c in lib.children) {
 					autoName(c);
 					c.parent = parent;
@@ -1593,7 +1593,7 @@ class SceneEditor {
 	// Override
 	function getNewContextMenu(current: PrefabElement, ?onMake: PrefabElement->Void=null) : Array<hide.comp.ContextMenu.ContextMenuItem> {
 		var newItems = new Array<hide.comp.ContextMenu.ContextMenuItem>();
-		var allRegs = hxd.prefab.Library.getRegistered().copy();
+		var allRegs = hrt.prefab.Library.getRegistered().copy();
 		allRegs.remove("reference");
 		var parent = current == null ? sceneData : current;
 		var allowChildren = null;
@@ -1623,7 +1623,7 @@ class SceneEditor {
 	}
 
 	function getNewTypeMenuItem(ptype: String, parent: PrefabElement, onMake: PrefabElement->Void, ?label: String) : hide.comp.ContextMenu.ContextMenuItem {
-		var pmodel = hxd.prefab.Library.getRegistered().get(ptype);
+		var pmodel = hrt.prefab.Library.getRegistered().get(ptype);
 		return {
 			label : label != null ? label : pmodel.inf.name,
 			click : function() {

+ 2 - 2
hide/prefab/ContextShared.hx

@@ -1,6 +1,6 @@
 package hide.prefab;
 
-class ContextShared extends hxd.prefab.ContextShared {
+class ContextShared extends hrt.prefab.ContextShared {
 	#if editor
 	var scene : hide.comp.Scene;
 
@@ -17,7 +17,7 @@ class ContextShared extends hxd.prefab.ContextShared {
 		hide.Ide.inst.error(e);
 	}
 
-	override function loadPrefab( path : String ) : hxd.prefab.Prefab {
+	override function loadPrefab( path : String ) : hrt.prefab.Prefab {
 		return hide.Ide.inst.loadPrefab(path);
 	}
 

+ 2 - 2
hide/prefab/EditContext.hx

@@ -1,6 +1,6 @@
 package hide.prefab;
-import hxd.prefab.Prefab;
-import hxd.prefab.Context;
+import hrt.prefab.Prefab;
+import hrt.prefab.Context;
 
 class EditContext {
 

+ 3 - 3
hide/prefab/HideProps.hx

@@ -5,8 +5,8 @@ typedef HideProps = {
 	var name : String;
 	@:optional var fileSource : Array<String>;
 	@:optional dynamic function allowChildren( type : String ) : Bool;
-	@:optional dynamic function allowParent( p : hxd.prefab.Prefab ) : Bool;
-	@:optional dynamic function onChildUpdate( p : hxd.prefab.Prefab ) : Void;
-	@:optional dynamic function onChildRemoved( p : hxd.prefab.Prefab ) : Void;
+	@:optional dynamic function allowParent( p : hrt.prefab.Prefab ) : Bool;
+	@:optional dynamic function onChildUpdate( p : hrt.prefab.Prefab ) : Void;
+	@:optional dynamic function onChildRemoved( p : hrt.prefab.Prefab ) : Void;
 	@:optional dynamic function onResourceRenamed( map : (oldPath : String) -> String ) : Void;
 }

+ 1 - 1
hide/prefab/PolygonEditor.hx

@@ -1,6 +1,6 @@
 package hide.prefab;
 import hxd.Key as K;
-import hxd.prefab.Context;
+import hrt.prefab.Context;
 
 enum ColorState{
 	None;

+ 3 - 3
hide/prefab/terrain/Brush.hx

@@ -65,7 +65,7 @@ class BrushMode {
 
 class BrushPreview {
 
-	var terrain : h3d.scene.pbr.terrain.Terrain;
+	var terrain : hrt.prefab.terrain.TerrainMesh;
 	var tiles : Array<TilePreviewMesh> = [];
 	var grid : h3d.prim.Grid;
 
@@ -81,7 +81,7 @@ class BrushPreview {
 			tile.remove();
 	}
 
-	public function addPreviewMeshAt(x : Int, y : Int, brush : Brush, brushPos : h3d.Vector, ctx : hxd.prefab.Context) : TilePreviewMesh {
+	public function addPreviewMeshAt(x : Int, y : Int, brush : Brush, brushPos : h3d.Vector, ctx : hrt.prefab.Context) : TilePreviewMesh {
 		var camera = @:privateAccess ctx.local3d.getScene().camera;
 		var dir = camera.pos.sub(new h3d.Vector(terrain.getAbsPos().tx, terrain.getAbsPos().ty, terrain.getAbsPos().tz));
 		var offsetDir = dir.z < 0 ? -1: 1;
@@ -146,6 +146,6 @@ class TilePreviewMesh extends h3d.scene.Mesh {
 	override function sync(ctx : h3d.scene.RenderContext) {
 		shader.heightMap = heightMap;
 		shader.heightMapSize = heightMap.width;
-		shader.primSize = Std.instance(parent, h3d.scene.pbr.terrain.Terrain).tileSize;
+		shader.primSize = Std.instance(parent, hrt.prefab.terrain.TerrainMesh).tileSize;
 	}
 }

+ 4 - 4
hide/prefab/terrain/TerrainEditor.hx

@@ -2,7 +2,7 @@ package hide.prefab.terrain;
 
 using Lambda;
 import hxd.Key as K;
-import hxd.prefab.Context;
+import hrt.prefab.Context;
 
 enum RenderMode {
 	PBR;
@@ -12,7 +12,7 @@ enum RenderMode {
 
 class TerrainRevertData {
 	public var surfaceIndex : Int;
-	public var surface : h3d.scene.pbr.terrain.Surface;
+	public var surface : hrt.prefab.terrain.Surface;
 	public function new(){
 
 	}
@@ -36,7 +36,7 @@ class TileRevertData {
 class TerrainEditor {
 
 	public var currentBrush : Brush;
-	public var currentSurface : h3d.scene.pbr.terrain.Surface;
+	public var currentSurface : hrt.prefab.terrain.Surface;
 	public var tmpTexPath : String;
 	public var textureType = ["_Albedo", "_Normal", "_MetallicGlossAO"];
 	public var autoCreateTile = false;
@@ -58,7 +58,7 @@ class TerrainEditor {
 
 	var terrainPrefab : hrt.prefab.terrain.Terrain;
 	var undo : hide.ui.UndoHistory;
-	var tileTrashBin : Array<h3d.scene.pbr.terrain.Tile> = [];
+	var tileTrashBin : Array<hrt.prefab.terrain.Tile> = [];
 	var paintRevertDatas : Array<TileRevertData> = [];
 	var uvTexPixels : hxd.Pixels.PixelsFloat;
 	var uvTex : h3d.mat.Texture;

+ 2 - 2
hide/view/FXEditor.hx

@@ -2,7 +2,7 @@ package hide.view;
 using Lambda;
 
 import hide.Element;
-import hxd.prefab.Prefab in PrefabElement;
+import hrt.prefab.Prefab in PrefabElement;
 import hrt.prefab.Curve;
 import hrt.prefab.fx.Event;
 
@@ -48,7 +48,7 @@ private class FXSceneEditor extends hide.comp.SceneEditor {
 		parent.onUpdate(dt);
 	}
 
-	override function setObjectSelected( p : PrefabElement, ctx : hxd.prefab.Context, b : Bool ) {
+	override function setObjectSelected( p : PrefabElement, ctx : hrt.prefab.Context, b : Bool ) {
 		if( p.getParent(hrt.prefab.fx.Emitter) != null )
 			return;
 		super.setObjectSelected(p, ctx, b);

+ 1 - 1
hide/view/FileTree.hx

@@ -176,7 +176,7 @@ class FileTree extends FileView {
 		var isDir = sys.FileSystem.isDirectory(ide.getPath(path));
 		if( isDir )
 			throw "TODO : rename directory";
-		ide.filterPrefabs(function(p:hxd.prefab.Prefab) {
+		ide.filterPrefabs(function(p:hrt.prefab.Prefab) {
 			var changed = false;
 			function filter(p:String) {
 				if( p == null )

+ 4 - 4
hide/view/Model.hx

@@ -10,7 +10,7 @@ class Model extends FileView {
 	var overlay : Element;
 	var eventList : Element;
 
-	var plight : hxd.prefab.Prefab;
+	var plight : hrt.prefab.Prefab;
 	var light : h3d.scene.Object;
 	var lightDirection : h3d.Vector;
 
@@ -24,7 +24,7 @@ class Model extends FileView {
 	var cameraMove : Void -> Void;
 	var scene(get,never) : hide.comp.Scene;
 	var rootPath : String;
-	var root : hxd.prefab.Prefab;
+	var root : hrt.prefab.Prefab;
 
 	override function save() {
 		if(!modified) return;
@@ -107,10 +107,10 @@ class Model extends FileView {
 			rootPath = config.get("scene.renderProps");
 
 		if( rootPath != null )
-			root = ide.loadPrefab(rootPath, hxd.prefab.Library);
+			root = ide.loadPrefab(rootPath, hrt.prefab.Library);
 
 		if( root == null ) {
-			var def = new hxd.prefab.Library();
+			var def = new hrt.prefab.Library();
 			new hrt.prefab.RenderProps(def).name = "renderer";
 			var l = new hrt.prefab.Light(def);
 			l.name = "sunLight";

+ 4 - 4
hide/view/Prefab.hx

@@ -1,6 +1,6 @@
 package hide.view;
 
-import hxd.prefab.Prefab in PrefabElement;
+import hrt.prefab.Prefab in PrefabElement;
 
 @:access(hide.view.Prefab)
 private class PrefabSceneEditor extends hide.comp.SceneEditor {
@@ -26,7 +26,7 @@ private class PrefabSceneEditor extends hide.comp.SceneEditor {
 class Prefab extends FileView {
 
 	var sceneEditor : PrefabSceneEditor;
-	var data : hxd.prefab.Library;
+	var data : hrt.prefab.Library;
 	var tabs : hide.comp.Tabs;
 
 	var tools : hide.comp.Toolbar;
@@ -46,7 +46,7 @@ class Prefab extends FileView {
 	var currentSign : String;
 
 	override function getDefaultContent() {
-		return haxe.io.Bytes.ofString(ide.toJSON(new hxd.prefab.Library().saveData()));
+		return haxe.io.Bytes.ofString(ide.toJSON(new hrt.prefab.Library().saveData()));
 	}
 
 	override function onFileChanged(wasDeleted:Bool) {
@@ -69,7 +69,7 @@ class Prefab extends FileView {
 
 	override function onDisplay() {
 		saveDisplayKey = "Prefab:" + getPath().split("\\").join("/").substr(0,-1);
-		data = new hxd.prefab.Library();
+		data = new hrt.prefab.Library();
 		var content = sys.io.File.getContent(getPath());
 		data.loadData(haxe.Json.parse(content));
 		currentSign = haxe.crypto.Md5.encode(content);

+ 4 - 4
hide/view/l3d/Level3D.hx

@@ -4,7 +4,7 @@ using Lambda;
 import hxd.Math;
 import hxd.Key as K;
 
-import hxd.prefab.Prefab as PrefabElement;
+import hrt.prefab.Prefab as PrefabElement;
 import hrt.prefab.Object3D;
 import hrt.prefab.l3d.Instance;
 import h3d.scene.Object;
@@ -432,7 +432,7 @@ class Level3D extends FileView {
 		for( p in passes )
 			p.isStatic = true;
 
-		function isDynamic(elt: hxd.prefab.Prefab) {
+		function isDynamic(elt: hrt.prefab.Prefab) {
 			var p = elt;
 			while(p != null) {
 				if(p.name == "dynamic")
@@ -609,7 +609,7 @@ class Level3D extends FileView {
 
 	function applySceneFilter(typeid: String, visible: Bool) {
 		saveDisplayState("sceneFilters/" + typeid, visible);
-		var all = data.flatten(hxd.prefab.Prefab);
+		var all = data.flatten(hrt.prefab.Prefab);
 		for(p in all) {
 			if(p.type == typeid || getCdbTypeId(p) == typeid) {
 				sceneEditor.applySceneStyle(p);
@@ -749,7 +749,7 @@ class Level3D extends FileView {
 		}
 	}
 
-	public static function getCdbModel(e:hxd.prefab.Prefab):cdb.Sheet {
+	public static function getCdbModel(e:hrt.prefab.Prefab):cdb.Sheet {
 		var typeName : String = getCdbTypeId(e);
 		if(typeName == null)
 			return null;

+ 5 - 5
hide/view/l3d/LightProbeBaker.hx

@@ -7,7 +7,7 @@ class LightProbeBaker {
 	public var useGPU = false;
 	public var environment : h3d.scene.pbr.Environment;
 
-	var context : hxd.prefab.Context;
+	var context : hrt.prefab.Context;
 	var offScreenScene : h3d.scene.Scene = null;
 	var prim : h3d.prim.Plane2D;
 
@@ -38,7 +38,7 @@ class LightProbeBaker {
 		customCamera.screenRatio = 1.0;
 		customCamera.fovY = 90;
 		customCamera.zFar = 100;
-		context = new hxd.prefab.Context();
+		context = new hrt.prefab.Context();
 	}
 
 	public function dispose() {
@@ -58,7 +58,7 @@ class LightProbeBaker {
 		if(offScreenScene != null) offScreenScene.dispose();
 	}
 
-	public function initScene( sceneData : hxd.prefab.Prefab, shared : hide.prefab.ContextShared, scene : hide.comp.Scene , env : h3d.scene.pbr.Environment) {
+	public function initScene( sceneData : hrt.prefab.Prefab, shared : hide.prefab.ContextShared, scene : hide.comp.Scene , env : h3d.scene.pbr.Environment) {
 		if(offScreenScene != null) offScreenScene.dispose();
 		offScreenScene = new h3d.scene.Scene();
 
@@ -70,12 +70,12 @@ class LightProbeBaker {
 		context.local3d = offScreenScene;
 
 		var whiteList = [ "level3d", "object", "model", "material", "light"];
-		function keep( p : hxd.prefab.Prefab ) {
+		function keep( p : hrt.prefab.Prefab ) {
 			for( f in whiteList )
 				if( f == p.type ) return true;
 			return false;
 		}
-		function filter( p : hxd.prefab.Prefab ) {
+		function filter( p : hrt.prefab.Prefab ) {
 			for( c in p.children ) {
 				if(!keep(c))
 					sceneData.children.remove(c);

+ 1 - 1
hide/view/l3d/ProbeBakerProcess.hx

@@ -18,7 +18,7 @@ class ProbeBakerProcess {
 		lightProbeBaker.useGPU = useGPU;
 	}
 
-	public function init( env : h3d.scene.pbr.Environment, sceneData : hxd.prefab.Prefab , shared : hide.prefab.ContextShared, scene : hide.comp.Scene) {
+	public function init( env : h3d.scene.pbr.Environment, sceneData : hrt.prefab.Prefab , shared : hide.prefab.ContextShared, scene : hide.comp.Scene) {
 		lightProbeBaker.initScene(sceneData, shared, scene, env);
 	}
 

+ 1 - 1
hrt/prefab/Blur.hx

@@ -116,6 +116,6 @@ class Blur extends Prefab {
 	}
 	#end
 
-	static var _ = hxd.prefab.Library.register("blur", Blur);
+	static var _ = Library.register("blur", Blur);
 
 }

+ 1 - 1
hrt/prefab/Box.hx

@@ -66,5 +66,5 @@ class Box extends Object3D {
 	}
 	#end
 
-	static var _ = hxd.prefab.Library.register("box", Box);
+	static var _ = Library.register("box", Box);
 }

+ 1 - 1
hrt/prefab/Constraint.hx

@@ -69,6 +69,6 @@ class Constraint extends Prefab {
 	}
 	#end
 
-	static var _ = hxd.prefab.Library.register("constraint", Constraint);
+	static var _ = Library.register("constraint", Constraint);
 
 }

+ 86 - 0
hrt/prefab/Context.hx

@@ -0,0 +1,86 @@
+package hrt.prefab;
+
+@:final class Context {
+
+	public var local2d : h2d.Object;
+	public var local3d : h3d.scene.Object;
+	public var shared : ContextShared;
+	public var cleanup : Void -> Void;
+	public var custom : Dynamic;
+	public var isRef : Bool = false;
+
+	public function new() {
+	}
+
+	public function init() {
+		if( shared == null )
+			shared = new ContextShared();
+		local2d = shared.root2d;
+		local3d = shared.root3d;
+	}
+
+	public function clone( p : Prefab ) {
+		var c = new Context();
+		c.shared = shared;
+		c.local2d = local2d;
+		c.local3d = local3d;
+		c.custom = custom;
+		c.isRef = isRef;
+		if( p != null ) {
+			if(!isRef)
+				shared.contexts.set(p, c);
+			else {
+				if(!shared.references.exists(p))
+					shared.references.set(p, [c])
+				else
+					shared.references[p].push(c);
+			}
+		}
+		return c;
+	}
+
+	public function loadModel( path : String ) {
+		return shared.loadModel(path);
+	}
+
+	public function loadAnimation( path : String ) {
+		return shared.loadAnimation(path);
+	}
+
+	public function loadTexture( path : String ) {
+		return shared.loadTexture(path);
+	}
+
+	public function loadShader( name : String ) {
+		return shared.loadShader(name);
+	}
+
+	public function locateObject( path : String ) {
+		if( path == null )
+			return null;
+		var parts = path.split(".");
+		var root = shared.root3d;
+		while( parts.length > 0 ) {
+			var v = null;
+			var pname = parts.shift();
+			for( o in root )
+				if( o.name == pname ) {
+					v = o;
+					break;
+				}
+			if( v == null ) {
+				v = root.getObjectByName(pname);
+				//if( v != null && v.parent != root ) v = null; ??
+			}
+			if( v == null ) {
+				var parts2 = path.split(".");
+				for( i in 0...parts.length ) parts2.pop();
+				shared.onError("Object not found " + parts2.join("."));
+				return null;
+			}
+			root = v;
+		}
+		return root;
+	}
+
+}

+ 237 - 0
hrt/prefab/ContextShared.hx

@@ -0,0 +1,237 @@
+package hrt.prefab;
+
+typedef ShaderDef = {
+	var shader : hxsl.SharedShader;
+	var inits : Array<{ v : hxsl.Ast.TVar, e : hxsl.Ast.TExpr }>;
+}
+
+typedef ShaderDefCache = Map<String, ShaderDef>;
+
+class ContextShared {
+	public var root2d : h2d.Object;
+	public var root3d : h3d.scene.Object;
+	public var contexts : Map<Prefab,Context>;
+	public var references : Map<Prefab,Array<Context>>;
+	public var currentPath : String;
+	public var editorDisplay : Bool;
+
+	var cache : h3d.prim.ModelCache;
+	var shaderCache : ShaderDefCache;
+	var bakedData : Map<String, haxe.io.Bytes>;
+
+	public function new() {
+		root2d = new h2d.Object();
+		root3d = new h3d.scene.Object();
+		contexts = new Map();
+		references = new Map();
+		cache = new h3d.prim.ModelCache();
+		shaderCache = new ShaderDefCache();
+	}
+
+	public function onError( e : Dynamic ) {
+		throw e;
+	}
+
+	public function elements() {
+		return [for(e in contexts.keys()) e];
+	}
+
+	public function getContexts(p: Prefab) {
+		var ret : Array<Context> = [];
+		var ctx = contexts.get(p);
+		if(ctx != null)
+			ret.push(ctx);
+		var ctxs = references.get(p);
+		if(ctxs != null)
+			return ret.concat(ctxs);
+		return ret;
+	}
+
+	public function loadDir(p : String, ?dir : String ) : Array<hxd.res.Any> {
+		var datPath = new haxe.io.Path(currentPath);
+		datPath.ext = "dat";
+		var path = datPath.toString() + "/" + p;
+		if(dir != null) path += "/" + dir;
+		return try hxd.res.Loader.currentInstance.dir(path) catch( e : hxd.res.NotFound ) null;
+	}
+
+	public function loadPrefabDat(file : String, ext : String, p : String) : hxd.res.Any {
+		var datPath = new haxe.io.Path(currentPath);
+		datPath.ext = "dat";
+		var path = new haxe.io.Path(datPath.toString() + "/" + p + "/" + file);
+		path.ext = ext;
+		return try hxd.res.Loader.currentInstance.load(path.toString()) catch( e : hxd.res.NotFound ) null;
+	}
+
+	public function savePrefabDat(file : String, ext : String, p : String, bytes : haxe.io.Bytes ) {
+		throw "Not implemented";
+	}
+
+	public function loadPrefab( path : String ) : Prefab {
+		throw "Not implemented";
+	}
+
+	public function loadShader( path : String ) : ShaderDef {
+		var r = shaderCache.get(path);
+		if(r != null)
+			return r;
+		var cl = Type.resolveClass(path.split("/").join("."));
+		if(cl == null) return null;
+		var shader = new hxsl.SharedShader(Reflect.field(cl, "SRC"));
+		r = {
+			shader: shader,
+			inits: []
+		};
+		shaderCache.set(path, r);
+		return r;
+	}
+
+	public function loadModel( path : String ) {
+		return cache.loadModel(hxd.res.Loader.currentInstance.load(path).toModel());
+	}
+
+	public function loadAnimation( path : String ) {
+		return @:privateAccess cache.loadAnimation(hxd.res.Loader.currentInstance.load(path).toModel());
+	}
+
+	public function loadTexture( path : String ) {
+		return cache.loadTexture(null, path);
+	}
+
+	public function loadBytes( file : String) : haxe.io.Bytes {
+		return try hxd.res.Loader.currentInstance.load(file).entry.getBytes() catch( e : hxd.res.NotFound ) null;
+	}
+
+	public function loadBakedBytes( file : String ) {
+		if( bakedData == null ) loadBakedData();
+		return bakedData.get(file);
+	}
+
+	public function saveBakedBytes( file : String, bytes : haxe.io.Bytes ) {
+		if( bakedData == null ) loadBakedData();
+		if( bytes == null ) {
+			if( !bakedData.remove(file) )
+				return;
+		} else
+			bakedData.set(file, bytes);
+		var keys = Lambda.array({ iterator : bakedData.keys });
+		if( keys.length == 0 ) {
+			saveBakedFile(null);
+			return;
+		}
+		var bytes = new haxe.io.BytesOutput();
+		bytes.writeString("BAKE");
+		bytes.writeInt32(keys.length);
+		var headerSize = 8;
+		for( name in keys )
+			headerSize += 2 + name.length + 8;
+		for( name in keys ) {
+			bytes.writeUInt16(name.length);
+			bytes.writeString(name);
+			bytes.writeInt32(headerSize);
+			var len = bakedData.get(name).length;
+			bytes.writeInt32(len);
+			headerSize += len + 1;
+		}
+		for( name in keys ) {
+			bytes.write(bakedData.get(name));
+			bytes.writeByte(0xFE); // stop
+		}
+		saveBakedFile(bytes.getBytes());
+	}
+
+	public function saveTexture( file : String, bytes : haxe.io.Bytes , dir : String, ext : String) {
+		throw "Don't know how to save texture";
+	}
+
+	function saveBakedFile( bytes : haxe.io.Bytes ) {
+		throw "Don't know how to save baked file";
+	}
+
+	function loadBakedFile() {
+		var path = new haxe.io.Path(currentPath);
+		path.ext = "bake";
+		return try hxd.res.Loader.currentInstance.load(path.toString()).entry.getBytes() catch( e : hxd.res.NotFound ) null;
+	}
+
+	function loadBakedData() {
+		bakedData = new Map();
+		var data = loadBakedFile();
+		if( data == null )
+			return;
+		if( data.getString(0,4) != "BAKE" )
+			throw "Invalid bake file";
+		var count = data.getInt32(4);
+		var pos = 8;
+		for( i in 0...count ) {
+			var len = data.getUInt16(pos);
+			pos += 2;
+			var name = data.getString(pos, len);
+			pos += len;
+			var bytesPos = data.getInt32(pos);
+			pos += 4;
+			var bytesLen = data.getInt32(pos);
+			pos += 4;
+			bakedData.set(name,data.sub(bytesPos,bytesLen));
+			if( data.get(bytesPos+bytesLen) != 0xFE )
+				throw "Corrupted bake file";
+		}
+	}
+
+	function getChildrenRoots( base : h3d.scene.Object, p : Prefab, out : Array<h3d.scene.Object> ) {
+		for( c in p.children ) {
+			var ctx = contexts.get(c);
+			if( ctx == null ) continue;
+			if( ctx.local3d == base )
+				getChildrenRoots(base, c, out);
+			else
+				out.push(ctx.local3d);
+		}
+		return out;
+	}
+
+	public function getObjects<T:h3d.scene.Object>( p : Prefab, c: Class<T> ) : Array<T> {
+		var ctx = contexts.get(p);
+		if( ctx == null )
+			return [];
+		var root = ctx.local3d;
+		var childObjs = getChildrenRoots(root, p, []);
+		var ret = [];
+		function rec(o : h3d.scene.Object) {
+			var m = Std.instance(o, c);
+			if(m != null) ret.push(m);
+			for( child in o )
+				if( childObjs.indexOf(child) < 0 )
+					rec(child);
+		}
+		rec(root);
+		return ret;
+	}
+
+	public function getMaterials( p : Prefab ) {
+		var ctx = contexts.get(p);
+		if( ctx == null )
+			return [];
+		var root = ctx.local3d;
+		var childObjs = getChildrenRoots(root, p, []);
+		var ret = [];
+		function rec(o : h3d.scene.Object) {
+			if( o.isMesh() ) {
+				var m = o.toMesh();
+				var multi = Std.instance(m, h3d.scene.MultiMaterial);
+				if( multi != null ) {
+					for( m in multi.materials )
+						if( m != null )
+							ret.push(m);
+				} else if( m.material != null )
+					ret.push(m.material);
+			}
+			for( child in o )
+				if( childObjs.indexOf(child) < 0 )
+					rec(child);
+		}
+		rec(root);
+		return ret;
+	}
+
+}

+ 1 - 1
hrt/prefab/Curve.hx

@@ -339,5 +339,5 @@ class Curve extends Prefab {
 			a != null ? VCurve(a) : VConst(1.0));
 	}
 
-	static var _ = hxd.prefab.Library.register("curve", Curve);
+	static var _ = Library.register("curve", Curve);
 }

+ 77 - 0
hrt/prefab/Library.hx

@@ -0,0 +1,77 @@
+package hrt.prefab;
+
+class Library extends Prefab {
+
+	public function new() {
+		super(null);
+		type = "prefab";
+	}
+
+	override function load( obj : Dynamic ) {
+	}
+
+	override function save() {
+		return {};
+	}
+
+	/**
+		Returns the prefab within children that matches the given absolute path
+	**/
+	public function getFromPath( path : String ) : Prefab {
+		var parts = path.split(".");
+		var cur : Prefab = this;
+		for( p in parts ) {
+			var found = false;
+			for( c in cur.children )
+				if( c.name == p ) {
+					found = true;
+					cur = c;
+					break;
+				}
+			if( !found ) return null;
+		}
+		return cur;
+	}
+
+	static var registeredElements = new Map<String,{ cl : Class<Prefab> #if editor, inf : hide.prefab.HideProps #end }>();
+	static var registeredExtensions = new Map<String,String>();
+
+	public static function getRegistered() {
+		return registeredElements;
+	}
+
+	public static function isOfType( prefabKind : String, cl : Class<Prefab> ) {
+		var inf = registeredElements.get(prefabKind);
+		if( inf == null ) return false;
+		var c : Class<Dynamic> = inf.cl;
+		while( c != null ) {
+			if( c == cl ) return true;
+			c = Type.getSuperClass(c);
+		}
+		return false;
+	}
+
+	public static function register( type : String, cl : Class<Prefab>, ?extension : String ) {
+		registeredElements.set(type, { cl : cl #if editor, inf : Type.createEmptyInstance(cl).getHideProps() #end });
+		if( extension != null ) registeredExtensions.set(extension, type);
+		return true;
+	}
+
+	public static function create( extension : String ) {
+		var type = getPrefabType(extension);
+		var p : hrt.prefab.Prefab;
+		if( type == null )
+			p = new Library();
+		else
+			p = Type.createInstance(registeredElements.get(type).cl,[]);
+		return p;
+	}
+
+	public static function getPrefabType(path: String) {
+		var extension = path.split(".").pop().toLowerCase();
+		return registeredExtensions.get(extension);
+	}
+
+	static var _ = Library.register("prefab", Library, "prefab");
+
+}

+ 1 - 1
hrt/prefab/Light.hx

@@ -538,5 +538,5 @@ class Light extends Object3D {
 	}
 	#end
 
-	static var _ = hxd.prefab.Library.register("light", Light);
+	static var _ = Library.register("light", Light);
 }

+ 3 - 4
hrt/prefab/Material.hx

@@ -1,6 +1,5 @@
 package hrt.prefab;
 
-
 class Material extends Prefab {
 
 	public var wrapRepeat = false;
@@ -95,7 +94,7 @@ class Material extends Prefab {
 			return;
 
 		var obj = ctx.local3d;
-		if(parent != null && Type.getClass(parent) == hrt.prefab.Object3D) {
+		if(parent != null && Type.getClass(parent) == Object3D) {
 			for(i in 0...obj.numChildren) {
 				updateObject(ctx, obj.getChildAt(i));
 			}
@@ -155,10 +154,10 @@ class Material extends Prefab {
 	public static function hasOverride(p: Prefab) {
 		if(Lambda.exists(p.children, c -> Std.is(c, Material) && c.enabled))
 			return true;
-		if(Type.getClass(p.parent) == hrt.prefab.Object3D)
+		if(Type.getClass(p.parent) == Object3D)
 			return Lambda.exists(p.parent.children, c -> Std.is(c, Material) && c.enabled);
 		return false;
 	}
 
-	static var _ = hxd.prefab.Library.register("material", Material);
+	static var _ = Library.register("material", Material);
 }

+ 2 - 2
hrt/prefab/Model.hx

@@ -122,12 +122,12 @@ class Model extends Object3D {
 	override function getHideProps() : HideProps {
 		return {
 			icon : "cube", name : "Model", fileSource : ["fbx","hmd"],
-			allowChildren : function(t) return hxd.prefab.Library.isOfType(t,Object3D) || ["material", "shader"].indexOf(t) >= 0,
+			allowChildren : function(t) return Library.isOfType(t,Object3D) || ["material", "shader"].indexOf(t) >= 0,
 			onResourceRenamed : function(f) animation = f(animation),
 		};
 	}
 	#end
 
-	static var _ = hxd.prefab.Library.register("model", Model);
+	static var _ = Library.register("model", Model);
 
 }

+ 1 - 1
hrt/prefab/Noise.hx

@@ -237,7 +237,7 @@ class Noise extends Prefab {
 
 	#end
 
-	static var _ = hxd.prefab.Library.register("noise", Noise);
+	static var _ = Library.register("noise", Noise);
 
 }
 

+ 1 - 1
hrt/prefab/Object3D.hx

@@ -160,6 +160,6 @@ class Object3D extends Prefab {
 		return type == "object" ? "group" : super.getDefaultName();
 	}
 
-	static var _ = hxd.prefab.Library.register("object", Object3D);
+	static var _ = Library.register("object", Object3D);
 
 }

+ 413 - 0
hrt/prefab/Prefab.hx

@@ -0,0 +1,413 @@
+package hrt.prefab;
+
+/**
+	Prefab is an data-oriented tree container capable of creating instances of Heaps objects.
+**/
+@:keepSub
+class Prefab {
+
+	/**
+		The type of prefab, allows to identify which class it should be loaded with.
+	**/
+	public var type(default, null) : String;
+
+	/**
+		The name of the prefab in the tree view
+	**/
+	public var name(default, set) : String;
+
+	/**
+		The parent of the prefab in the tree view
+	**/
+	public var parent(default, set) : Prefab;
+
+	/**
+		The associated source file (an image, a 3D model, etc.) if the prefab type needs it.
+	**/
+	public var source(default, set) : String;
+
+	/**
+		The list of children prefab in the tree view
+	**/
+	public var children(default, null) : Array<Prefab>;
+
+	/**
+		Tells if the prefab will create an instance when calling make() or be ignored. Also apply to this prefab children.
+	**/
+	public var enabled : Bool = true;
+
+
+	/**
+		A storage for some extra properties
+	**/
+	public var props : Any;
+
+	/**
+		Creates a new prefab with the given parent.
+	**/
+	public function new(?parent) {
+		this.parent = parent;
+		children = [];
+	}
+
+	function set_name(n) {
+		return name = n;
+	}
+
+	function set_source(f) {
+		return source = f;
+	}
+
+	function set_parent(p) {
+		if( parent != null )
+			parent.children.remove(this);
+		parent = p;
+		if( parent != null )
+			parent.children.push(this);
+		return p;
+	}
+
+	#if editor
+
+	/**
+		Allows to customize how the prefab object is edited within Hide
+	**/
+	public function edit( ctx : hide.prefab.EditContext ) {
+	}
+
+	/**
+		Allows to customize how the prefab object is displayed / handled within Hide
+	**/
+	public function getHideProps() : hide.prefab.HideProps {
+		return { icon : "question-circle", name : "Unknown" };
+	}
+
+	/**
+		Allows to customize how the prefab instance changes when selected/unselected within Hide
+	**/
+	public function setSelected( ctx : Context, b : Bool ) {
+		var materials = ctx.shared.getMaterials(this);
+
+		if( !b ) {
+			for( m in materials ) {
+				m.mainPass.stencil = null;
+				m.removePass(m.getPass("highlight"));
+			}
+			return;
+		}
+
+		var shader = new h3d.shader.FixedColor(0xffffff);
+		for( m in materials ) {
+			var p = m.allocPass("highlight");
+			p.culling = None;
+			p.depthWrite = false;
+			p.addShader(shader);
+		}
+	}
+	#end
+
+	/**
+		Iterate over children prefab
+	**/
+	public inline function iterator() : Iterator<Prefab> {
+		return children.iterator();
+	}
+
+	/**
+		Override to implement your custom prefab data loading
+	**/
+	function load( v : Dynamic ) {
+		throw "Not implemented";
+	}
+
+	/**
+		Override to implement your custom prefab data saving
+	**/
+	function save() : {} {
+		throw "Not implemented";
+		return null;
+	}
+
+	/**
+		Creates an instance for this prefab only (and not its children).
+		Use make(ctx) to creates the whole instances tree;
+	**/
+	public function makeInstance( ctx : Context ) : Context {
+		return ctx;
+	}
+
+	/**
+		Allows to customize how an instance gets updated when a property name changes.
+		You can also call updateInstance(ctx) in order to force whole instance synchronization against current prefab data.
+	**/
+	public function updateInstance( ctx : Context, ?propName : String ) {
+	}
+
+	/**
+		Removes the created instance for this prefab only (not is children).
+		If false is returned, the instance could not be removed and the whole context scene needs to be rebuilt
+	**/
+	public function removeInstance( ctx : Context ) : Bool {
+		return false;
+	}
+
+	/**
+		Save the whole prefab data and its children.
+	**/
+	@:final public function saveData() : {} {
+		var obj : Dynamic = save();
+		obj.type = type;
+		if( !enabled )
+			obj.enabled = false;
+		if( name != null )
+			obj.name = name;
+		if( source != null )
+			obj.source = source;
+		if( children.length > 0 )
+			obj.children = [for( s in children ) s.saveData()];
+		if( props != null && obj.props == null )
+			obj.props = props;
+		return obj;
+	}
+
+	/**
+		Load the whole prefab data and creates its children.
+	**/
+	@:final public function loadData( v : Dynamic ) {
+		type = v.type;
+		name = v.name;
+		enabled = v.enabled == null ? true : v.enabled;
+		props = v.props;
+		source = v.source;
+		load(v);
+		if( children.length > 0 )
+			children = [];
+		var children : Array<Dynamic> = v.children;
+		if( children != null )
+			for( v in children )
+				loadPrefab(v, this);
+	}
+
+	/**
+		Updates in-place the whole prefab data and its children.
+	**/
+	public function reload( p : Dynamic ) {
+		name = p.name;
+		enabled = p.enabled == null ? true : p.enabled;
+		props = p.props;
+		source = p.source;
+		load(p);
+		var childData : Array<Dynamic> = p.children;
+		if( childData == null ) {
+			if( this.children.length > 0 ) this.children = [];
+			return;
+		}
+		var curChild = new Map();
+		for( c in children )
+			curChild.set(c.name, c);
+		var newchild = [];
+		for( v in childData ) {
+			var name : String = v.name;
+			var prev = curChild.get(name);
+			if( prev != null && prev.type == v.type ) {
+				curChild.remove(name);
+				prev.reload(v);
+				newchild.push(prev);
+			} else {
+				newchild.push(loadPrefab(v,this));
+			}
+		}
+		children = newchild;
+	}
+
+	/**
+		Creates the correct prefab based on v.type and load its data and children.
+		If one the prefab in the tree is not registered, a hxd.prefab.Unkown is created instead.
+	**/
+	public static function loadPrefab( v : Dynamic, ?parent : Prefab ) {
+		var pcl = @:privateAccess Library.registeredElements.get(v.type);
+		var pcl = pcl == null ? null : pcl.cl;
+		if( pcl == null ) pcl = Unknown;
+		var p = Type.createInstance(pcl, [parent]);
+		p.loadData(v);
+		return p;
+	}
+
+	/**
+		Creates an instance for this prefab and its children.
+	**/
+	public function make( ctx : Context ) : Context {
+		if( !enabled )
+			return ctx;
+		if( ctx == null ) {
+			ctx = new Context();
+			ctx.init();
+		}
+		ctx = makeInstance(ctx);
+		for( c in children )
+			c.make(ctx);
+		return ctx;
+	}
+
+	#if castle
+	/**
+		Returns which CDB model this prefab props represents
+	**/
+	public function getCdbModel( ?p : Prefab ) : cdb.Sheet {
+		if( p == null )
+			p = this;
+		if( parent != null )
+			return parent.getCdbModel(p);
+		return null;
+	}
+	#end
+
+	/**
+		Search the prefab tree for the prefab matching the given name, returns null if not found
+	**/
+	public function getPrefabByName( name : String ) {
+		if( this.name == name )
+			return this;
+		for( c in children ) {
+			var p = c.getPrefabByName(name);
+			if( p != null )
+				return p;
+		}
+		return null;
+	}
+
+	/**
+		Simlar to get() but returns null if not found.
+	**/
+	public function getOpt<T:Prefab>( cl : Class<T>, ?name : String ) : T {
+		if( name == null || this.name == name ) {
+			var cval = to(cl);
+			if( cval != null ) return cval;
+		}
+		for( c in children ) {
+			var p = c.getOpt(cl, name);
+			if( p != null )
+				return p;
+		}
+		return null;
+	}
+
+	/**
+		Search the prefab tree for the prefab matching the given prefab class (and name, if specified).
+		Throw an exception if not found. Uses getOpt() to return null instead.
+	**/
+	public function get<T:Prefab>( cl : Class<T>, ?name : String ) : T {
+		var v = getOpt(cl, name);
+		if( v == null )
+			throw "Missing prefab " + (name == null ? Type.getClassName(cl) : (cl == null ? name : name+"(" + Type.getClassName(cl) + ")"));
+		return v;
+	}
+
+	/**
+		Return all prefabs in the tree matching the given prefab class.
+	**/
+	public function getAll<T:Prefab>( cl : Class<T>, ?arr: Array<T> ) : Array<T> {
+		return findAll(function(p) return p.to(cl));
+	}
+
+	/**
+		Find a single prefab in the tree by calling `f` on each and returning the first not-null value returned, or null if not found.
+	**/
+	public function find<T>( f : Prefab -> Null<T> ) : Null<T> {
+		var v = f(this);
+		if( v != null )
+			return v;
+		for( p in children ) {
+			var v = p.find(f);
+			if( v != null ) return v;
+		}
+		return null;
+	}
+
+	/**
+		Find several prefabs in the tree by calling `f` on each and returning all the not-null values returned.
+	**/
+	public function findAll<T>( f : Prefab -> Null<T>, ?arr : Array<T> ) : Array<T> {
+		if( arr == null ) arr = [];
+		var v = f(this);
+		if( v != null )
+			arr.push(v);
+		for( o in children )
+			o.findAll(f,arr);
+		return arr;
+	}
+
+	/**
+		Returns all prefabs in the tree matching the specified class.
+	**/
+	public function flatten<T:Prefab>( ?cl : Class<T>, ?arr: Array<T> ) : Array<T> {
+		if(arr == null)
+			arr = [];
+		if( cl == null )
+			arr.push(cast this);
+		else {
+			var i = to(cl);
+			if(i != null)
+				arr.push(i);
+		}
+		for(c in children)
+			c.flatten(cl, arr);
+		return arr;
+	}
+
+	/**
+		Returns the first parent in the tree matching the specified class or null if not found.
+	**/
+	public function getParent<T:Prefab>( c : Class<T> ) : Null<T> {
+		var p = parent;
+		while(p != null) {
+			var inst = p.to(c);
+			if(inst != null) return inst;
+			p = p.parent;
+		}
+		return null;
+	}
+
+	/**
+		Converts the prefab to another prefab class.
+		Returns null if not of this type.
+	**/
+	public function to<T:Prefab>( c : Class<T> ) : Null<T> {
+		return Std.instance(this, c);
+	}
+
+	/**
+		Returns the absolute name path for this prefab
+	**/
+	public function getAbsPath() {
+		var p = this;
+		var path = [];
+		while(p.parent != null) {
+			var n = p.name;
+			if( n == null ) n = getDefaultName();
+			path.unshift(n);
+			p = p.parent;
+		}
+		return path.join('.');
+	}
+
+	/**
+		Returns the default name for this prefab
+	**/
+	public function getDefaultName() : String {
+		if(source != null) {
+			var f = new haxe.io.Path(source).file;
+			f = f.split(" ")[0].split("-")[0];
+			return f;
+		}
+		return type.split(".").pop();
+	}
+
+	/**
+		Clone this prefab and all its children
+	**/
+	public function clone() : Prefab {
+		var obj = saveData();
+		return loadPrefab(haxe.Json.parse(haxe.Json.stringify(obj)));
+	}
+}

+ 3 - 3
hrt/prefab/Reference.hx

@@ -27,7 +27,7 @@ class Reference extends Object3D {
 		refpath = o.refpath;
 	}
 
-	public function resolveRef(shared : hxd.prefab.ContextShared) {
+	public function resolveRef(shared : hrt.prefab.ContextShared) {
 		if(ref != null)
 			return ref;
 		if(refpath == null)
@@ -45,7 +45,7 @@ class Reference extends Object3D {
 			return ref;
 		}
 		else {
-			var lib = getParent(hxd.prefab.Library);
+			var lib = getParent(hrt.prefab.Library);
 			if(lib == null)
 				return null;
 			var all = lib.getAll(Prefab);
@@ -143,5 +143,5 @@ class Reference extends Object3D {
 	}
 	#end
 
-	static var _ = hxd.prefab.Library.register("reference", Reference);
+	static var _ = Library.register("reference", Reference);
 }

+ 3 - 3
hrt/prefab/RenderProps.hx

@@ -62,7 +62,7 @@ class RenderProps extends Prefab {
 			fx.dispose();
 		renderer.effects = [];
 		for( s in children ) {
-			var fx = Std.instance(s, hxd.prefab.rfx.RendererFX);
+			var fx = Std.instance(s, hrt.prefab.rfx.RendererFX);
 			if( fx != null )
 				renderer.effects.push(fx);
 		}
@@ -92,10 +92,10 @@ class RenderProps extends Prefab {
 	}
 
 	override function getHideProps() : HideProps {
-		return { icon : "sun-o", name : "RenderProps", allowChildren : function(t) return hxd.prefab.Library.isOfType(t,hxd.prefab.rfx.RendererFX) };
+		return { icon : "sun-o", name : "RenderProps", allowChildren : function(t) return Library.isOfType(t,hrt.prefab.rfx.RendererFX) };
 	}
 
 	#end
 
-	static var _ = hxd.prefab.Library.register("renderProps", RenderProps);
+	static var _ = Library.register("renderProps", RenderProps);
 }

+ 17 - 0
hrt/prefab/Resource.hx

@@ -0,0 +1,17 @@
+package hrt.prefab;
+
+class Resource extends hxd.res.Resource {
+
+	var lib : Prefab;
+
+	public function load() : Prefab {
+		if( lib != null )
+			return lib;
+		var data = haxe.Json.parse(entry.getText());
+		lib = Library.create(entry.extension);
+		lib.loadData(data);
+		watch(function() lib.reload(haxe.Json.parse(entry.getText())));
+		return lib;
+	}
+
+}

+ 1 - 1
hrt/prefab/Scene.hx

@@ -37,6 +37,6 @@ class Scene extends Prefab {
 	}
 	#end
 
-	static var _ = hxd.prefab.Library.register("scene", Scene);
+	static var _ = Library.register("scene", Scene);
 
 }

+ 1 - 1
hrt/prefab/Settings.hx

@@ -110,6 +110,6 @@ class Settings extends Prefab {
 
 	#end
 
-	static var _ = hxd.prefab.Library.register("settings", Settings);
+	static var _ = Library.register("settings", Settings);
 
 }

+ 2 - 2
hrt/prefab/Shader.hx

@@ -2,7 +2,7 @@ package hrt.prefab;
 
 class Shader extends Prefab {
 
-	public var shaderDef : hxd.prefab.ContextShared.ShaderDef;
+	public var shaderDef : hrt.prefab.ContextShared.ShaderDef;
 
 	public function new(?parent) {
 		super(parent);
@@ -190,5 +190,5 @@ class Shader extends Prefab {
 		return null;
 	}
 
-	static var _ = hxd.prefab.Library.register("shader", Shader);
+	static var _ = Library.register("shader", Shader);
 }

+ 1 - 1
hrt/prefab/Trail.hx

@@ -68,6 +68,6 @@ class Trail extends Object3D {
 
 	#end
 
-	static var _ = hxd.prefab.Library.register("trail", Trail);
+	static var _ = Library.register("trail", Trail);
 
 }

+ 26 - 0
hrt/prefab/Unknown.hx

@@ -0,0 +1,26 @@
+package hrt.prefab;
+
+class Unknown extends Prefab {
+
+	var data : Dynamic;
+
+	public function getPrefabType() {
+		return data.type;
+	}
+
+	override function load(v:Dynamic) {
+		this.data = v;
+	}
+
+	override function save() {
+		return data;
+	}
+
+	#if editor
+	override function edit(ctx:hide.prefab.EditContext) {
+		ctx.properties.add(new hide.Element('<font color="red">Unknown prefab $type</font>'));
+	}
+	#end
+
+
+}

+ 1 - 1
hrt/prefab/fx/AnimEvent.hx

@@ -116,6 +116,6 @@ class AnimEvent extends hrt.prefab.fx.Event {
 	}
 	#end
 
-	static var _ = hxd.prefab.Library.register("animEvent", AnimEvent);
+	static var _ = Library.register("animEvent", AnimEvent);
 
 }

+ 2 - 2
hrt/prefab/fx/Emitter.hx

@@ -304,7 +304,7 @@ class EmitterObject extends h3d.scene.Object {
 
 	var random: hxd.Rand;
 	var randomSeed = 0;
-	var context : hxd.prefab.Context;
+	var context : hrt.prefab.Context;
 	var emitCount = 0;
 	var lastTime = -1.0;
 	var curTime = 0.0;
@@ -1076,6 +1076,6 @@ class Emitter extends Object3D {
 	}
 	#end
 
-	static var _ = hxd.prefab.Library.register("emitter", Emitter);
+	static var _ = Library.register("emitter", Emitter);
 
 }

+ 1 - 1
hrt/prefab/fx/Event.hx

@@ -6,7 +6,7 @@ typedef EventInstance = {
 	setTime: Float->Void
 };
 
-class Event extends hxd.prefab.Prefab {
+class Event extends hrt.prefab.Prefab {
 	public var time: Float = 0.0;
 
 	override function save() : {} {

+ 3 - 3
hrt/prefab/fx/FX.hx

@@ -1,6 +1,6 @@
 package hrt.prefab.fx;
 import hrt.prefab.Curve;
-import hxd.prefab.Prefab as PrefabElement;
+import hrt.prefab.Prefab as PrefabElement;
 
 typedef ShaderParam = {
 	def: hxsl.Ast.TVar,
@@ -216,7 +216,7 @@ class FXAnimation extends h3d.scene.Object {
 	}
 }
 
-class FX extends hxd.prefab.Library {
+class FX extends hrt.prefab.Library {
 
 	public var duration : Float;
 	public var loopAnims : Bool;
@@ -490,5 +490,5 @@ class FX extends hxd.prefab.Library {
 	}
 	#end
 
-	static var _ = hxd.prefab.Library.register("fx", FX, "fx");
+	static var _ = Library.register("fx", FX, "fx");
 }

+ 2 - 2
hrt/prefab/fx/LookAt.hx

@@ -111,7 +111,7 @@ class LookAt extends Object3D {
 		return obj;
 	}
 
-	override function updateInstance(ctx:hxd.prefab.Context, ?propName:String) {
+	override function updateInstance(ctx:hrt.prefab.Context, ?propName:String) {
 		super.updateInstance(ctx, propName);
 		var targetObj = null;
 		if(target != "camera")
@@ -162,5 +162,5 @@ class LookAt extends Object3D {
 	}
 	#end
 
-	static var _ = hxd.prefab.Library.register("lookAt", LookAt);
+	static var _ = Library.register("lookAt", LookAt);
 }

+ 0 - 2
hrt/prefab/import.hx

@@ -1,7 +1,5 @@
 package hrt.prefab;
 
-import hxd.prefab.Prefab;
-import hxd.prefab.Context;
 #if editor
 import hide.prefab.EditContext;
 import hide.prefab.HideProps;

+ 1 - 1
hrt/prefab/l3d/AdvancedDecal.hx

@@ -223,6 +223,6 @@ class AdvancedDecal extends Object3D {
 	}
 	#end
 
-	static var _ = hxd.prefab.Library.register("advancedDecal", AdvancedDecal);
+	static var _ = Library.register("advancedDecal", AdvancedDecal);
 
 }

+ 2 - 2
hrt/prefab/l3d/Camera.hx

@@ -1,6 +1,6 @@
 package hrt.prefab.l3d;
-import hxd.prefab.Context;
-import hxd.prefab.Library;
+import hrt.prefab.Context;
+import hrt.prefab.Library;
 
 class Camera extends Object3D {
 

+ 1 - 1
hrt/prefab/l3d/Decal.hx

@@ -106,6 +106,6 @@ class Decal extends Object3D {
 	}
 	#end
 
-	static var _ = hxd.prefab.Library.register("decal", Decal);
+	static var _ = Library.register("decal", Decal);
 
 }

+ 3 - 3
hrt/prefab/l3d/Instance.hx

@@ -19,7 +19,7 @@ class Instance extends Object3D {
 		var modelPath = findModelPath(kind.sheet, kind.idx.obj);
 		if(modelPath != null) {
 			try {
-				if(hxd.prefab.Library.getPrefabType(modelPath) != null) {
+				if(hrt.prefab.Library.getPrefabType(modelPath) != null) {
 					var ref = ctx.shared.loadPrefab(modelPath);
 					var ctx = ctx.clone(this);
 					ctx.isRef = true;
@@ -96,7 +96,7 @@ class Instance extends Object3D {
 		function filter(f: String) {
 			if(f != null) {
 				var lower = f.toLowerCase();
-				if(StringTools.endsWith(lower, ".fbx") || hxd.prefab.Library.getPrefabType(lower) != null)
+				if(StringTools.endsWith(lower, ".fbx") || hrt.prefab.Library.getPrefabType(lower) != null)
 					return f;
 			}
 			return null;
@@ -163,5 +163,5 @@ class Instance extends Object3D {
 	}
 	#end
 
-	static var _ = hxd.prefab.Library.register("instance", Instance);
+	static var _ = Library.register("instance", Instance);
 }

+ 6 - 9
hrt/prefab/l3d/Level3D.hx

@@ -1,6 +1,6 @@
 package hrt.prefab.l3d;
 
-class Level3D extends hxd.prefab.Library {
+class Level3D extends hrt.prefab.Library {
 
 	public var width : Int = 100;
 	public var height : Int = 100;
@@ -23,15 +23,12 @@ class Level3D extends hxd.prefab.Library {
 		height = obj.height == null ? 100 : obj.height;
 	}
 
-	override function getCdbModel(?p:hxd.prefab.Prefab):cdb.Sheet {
-		#if (editor && castle)
+	#if editor
+
+	override function getCdbModel(?p:hrt.prefab.Prefab) : cdb.Sheet {
 		return hide.view.l3d.Level3D.getCdbModel(p);
-		#end
-		return null;
 	}
 
-	#if editor
-
 	override function edit( ctx : EditContext ) {
 		var props = new hide.Element('
 			<div class="group" name="Level">
@@ -47,10 +44,10 @@ class Level3D extends hxd.prefab.Library {
 	}
 
 	override function getHideProps() : HideProps {
-		return { icon : "cube", name : "Level3D", allowChildren : function(t) return hxd.prefab.Library.isOfType(t,Object3D), allowParent: _ -> false};
+		return { icon : "cube", name : "Level3D", allowChildren : function(t) return Library.isOfType(t,Object3D), allowParent: _ -> false};
 	}
 
 	#end
 
-	static var _ = hxd.prefab.Library.register("level3d", Level3D, "l3d");
+	static var _ = Library.register("level3d", Level3D, "l3d");
 }

+ 1 - 1
hrt/prefab/l3d/MeshGenerator.hx

@@ -463,5 +463,5 @@ class MeshGenerator extends Object3D {
 	}
 	#end
 
-	static var _ = hxd.prefab.Library.register("meshGenerator", MeshGenerator);
+	static var _ = Library.register("meshGenerator", MeshGenerator);
 }

+ 1 - 1
hrt/prefab/l3d/Polygon.hx

@@ -353,5 +353,5 @@ class Polygon extends Object3D {
 
 	#end
 
-	static var _ = hxd.prefab.Library.register("polygon", Polygon);
+	static var _ = Library.register("polygon", Polygon);
 }

+ 1 - 1
hrt/prefab/l3d/VolumetricLightmap.hx

@@ -311,5 +311,5 @@ class VolumetricLightmap extends Object3D {
 
 	#end
 
-	static var _ = hxd.prefab.Library.register("volumetricLightmap", VolumetricLightmap);
+	static var _ = Library.register("volumetricLightmap", VolumetricLightmap);
 }

+ 71 - 0
hrt/prefab/rfx/Bloom.hx

@@ -0,0 +1,71 @@
+package hrt.prefab.rfx;
+
+typedef BloomProps = {
+	var size : Float;
+	var threshold : Float;
+	var intensity : Float;
+	var blur : Float;
+	var saturation : Float;
+	var blurQuality : Float;
+	var blurLinear : Float;
+}
+
+class Bloom extends RendererFX {
+
+	var bloomPass = new h3d.pass.ScreenFx(new h3d.shader.pbr.Bloom());
+	var bloomBlur = new h3d.pass.Blur();
+
+	public function new(?parent) {
+		super(parent);
+		props = ({
+			size : 0.5,
+			blur : 3,
+			intensity : 1.,
+			threshold : 0.5,
+			saturation: 0,
+			blurQuality: 1.0,
+			blurLinear : 0.0,
+		} : BloomProps);
+	}
+
+	override function apply(r:h3d.scene.Renderer, step:h3d.impl.RendererFX.Step) {
+		if( step == BeforeTonemapping ) {
+			var pb : BloomProps = props;
+			var bloom = r.allocTarget("bloom", false, pb.size, RGBA16F);
+			var ctx = r.ctx;
+			ctx.engine.pushTarget(bloom);
+			bloomPass.shader.hdr = ctx.getGlobal("hdr");
+			bloomPass.shader.threshold = pb.threshold;
+			bloomPass.shader.intensity = pb.intensity;
+			bloomPass.shader.colorMatrix.identity();
+			bloomPass.shader.colorMatrix.colorSaturate(pb.saturation);
+			bloomPass.render();
+			ctx.engine.popTarget();
+
+			bloomBlur.radius = pb.blur;
+			bloomBlur.quality = pb.blurQuality;
+			bloomBlur.linear = pb.blurLinear;
+			bloomBlur.apply(ctx, bloom);
+			ctx.setGlobal("bloom",bloom);
+		}
+	}
+
+	#if editor
+	override function edit( ctx : hide.prefab.EditContext ) {
+		ctx.properties.add(new hide.Element('
+			<dl>
+			<dt>Intensity</dt><dd><input type="range" min="0" max="2" field="intensity"/></dd>
+			<dt>Threshold</dt><dd><input type="range" min="0" max="1" field="threshold"/></dd>
+			<dt>Size</dt><dd><input type="range" min="0" max="1" field="size"/></dd>
+			<dt>Blur</dt><dd><input type="range" min="0" max="20" field="blur"/></dd>
+			<dt>Saturation</dt><dd><input type="range" min="-1" max="1" field="saturation"/></dd>
+			<dt>Blur Quality</dt><dd><input type="range" min="0" max="1" field="blurQuality"/></dd>
+			<dt>Blur Linear</dt><dd><input type="range" min="0" max="1" field="blurLinear"/></dd>
+			</dl>
+		'),props);
+	}
+	#end
+
+	static var _ = Library.register("rfx.bloom", Bloom);
+
+}

+ 82 - 0
hrt/prefab/rfx/DistanceFog.hx

@@ -0,0 +1,82 @@
+package hrt.prefab.rfx;
+
+typedef DistanceFogProps = {
+ 	var startDistance : Float;
+	var endDistance : Float;
+	var startOpacity : Float;
+	var endOpacity : Float;
+
+	var startColor : Int;
+	var endColor : Int;
+	var startColorDistance : Float;
+	var endColorDistance : Float;
+}
+
+class DistanceFog extends RendererFX {
+
+	var fogPass = new h3d.pass.ScreenFx(new h3d.shader.DistanceFog());
+
+	public function new(?parent) {
+		super(parent);
+		props = ({
+			startDistance : 0,
+			endDistance : 100,
+			startOpacity : 0,
+			endOpacity : 1,
+		 	startColor : 0xffffff,
+	    	endColor : 0xffffff,
+			startColorDistance : 0,
+			endColorDistance : 100,
+		} : DistanceFogProps);
+
+		fogPass.pass.setBlendMode(Alpha);
+	}
+
+	override function apply(r:h3d.scene.Renderer, step:h3d.impl.RendererFX.Step) {
+		if( step == BeforeTonemapping ) {
+			var p : DistanceFogProps = props;
+			var ctx = r.ctx;
+			var depth : hxsl.ChannelTexture = ctx.getGlobal("depthMap");
+
+			fogPass.shader.startDistance = p.startDistance;
+			fogPass.shader.endDistance = p.endDistance;
+			fogPass.shader.startOpacity = p.startOpacity;
+			fogPass.shader.endOpacity = p.endOpacity;
+			fogPass.shader.startColorDistance = p.startColorDistance;
+			fogPass.shader.endColorDistance = p.endColorDistance;
+			fogPass.shader.startColor = h3d.Vector.fromColor(p.startColor);
+			fogPass.shader.endColor = h3d.Vector.fromColor(p.endColor);
+			fogPass.shader.depthTextureChannel = depth.channel;
+			fogPass.shader.depthTexture = depth.texture;
+
+			fogPass.shader.cameraPos = ctx.camera.pos;
+			fogPass.shader.cameraInverseViewProj.load(ctx.camera.getInverseViewProj());
+
+			fogPass.render();
+		}
+	}
+
+	#if editor
+	override function edit( ctx : hide.prefab.EditContext ) {
+		ctx.properties.add(new hide.Element('
+			<dl>
+				<div class="group" name="Opacity">
+					<dt>Start Distance</dt><dd><input type="range" min="0" max="100" field="startDistance"/></dd>
+					<dt>End Distance</dt><dd><input type="range" min="0" max="100" field="endDistance"/></dd>
+					<dt>Start Opacity</dt><dd><input type="range" min="0" max="1" field="startOpacity"/></dd>
+					<dt>End Opacity</dt><dd><input type="range" min="0" max="1" field="endOpacity"/></dd>
+				</div>
+				<div class="group" name="Color">
+					<dt>Start Distance</dt><dd><input type="range" min="0" max="100" field="startColorDistance"/></dd>
+					<dt>End Distance</dt><dd><input type="range" min="0" max="100" field="endColorDistance"/></dd>
+					<dt>Start Color</dt><dd><input type="color" field="startColor"/></dd>
+					<dt>End Color</dt><dd><input type="color" field="endColor"/></dd>
+				</div>
+			</dl>
+		'),props);
+	}
+	#end
+
+	static var _ = Library.register("rfx.distanceFog", DistanceFog);
+
+}

+ 24 - 0
hrt/prefab/rfx/RendererFX.hx

@@ -0,0 +1,24 @@
+package hrt.prefab.rfx;
+
+class RendererFX extends Prefab implements h3d.impl.RendererFX {
+
+	public function apply( r : h3d.scene.Renderer, step : h3d.impl.RendererFX.Step ) {
+	}
+
+	override function save() {
+		return {};
+	}
+
+	override function load(v:Dynamic) {
+	}
+
+	public function dispose() {
+	}
+
+	#if editor
+	override function getHideProps() : hide.prefab.HideProps {
+		return { name : Type.getClassName(Type.getClass(this)).split(".").pop(), icon : "plus-circle" };
+	}
+	#end
+
+}

+ 86 - 0
hrt/prefab/rfx/Sao.hx

@@ -0,0 +1,86 @@
+package hrt.prefab.rfx;
+
+typedef SaoProps = {
+	var size : Float;
+	var blur : Float;
+	var samples : Int;
+	var radius : Float;
+	var intensity : Float;
+	var bias : Float;
+	var microIntensity : Float;
+	var useWorldUV : Bool;
+}
+
+class Sao extends RendererFX {
+
+	var sao : h3d.pass.ScalableAO;
+	var saoBlur = new h3d.pass.Blur();
+	var saoCopy = new h3d.pass.Copy();
+
+	public function new(?parent) {
+		super(parent);
+		props = ({
+			size : 1,
+			blur : 5,
+			samples : 30,
+			radius : 1,
+			intensity : 1,
+			bias : 0.1,
+			microIntensity : 1.0,
+			useWorldUV : false,
+		} : SaoProps);
+	}
+
+	override function apply( r : h3d.scene.Renderer, step : h3d.impl.RendererFX.Step ) {
+		if( step == BeforeLighting ) {
+			if( sao == null ) sao = new h3d.pass.ScalableAO();
+			var props : SaoProps = props;
+			var ctx = r.ctx;
+			var saoTex = r.allocTarget("sao",false, props.size);
+			var microOcclusion = r.allocTarget("sao",false, props.size);
+			var normal : hxsl.ChannelTexture = ctx.getGlobal("normalMap");
+			var depth : hxsl.ChannelTexture = ctx.getGlobal("depthMap");
+			var occlu : hxsl.ChannelTexture = ctx.getGlobal("occlusionMap");
+			ctx.engine.pushTarget(saoTex);
+			sao.shader.numSamples = props.samples;
+			sao.shader.sampleRadius	= props.radius;
+			sao.shader.intensity = props.intensity;
+			sao.shader.bias = props.bias * props.bias;
+			sao.shader.depthTextureChannel = depth.channel;
+			sao.shader.normalTextureChannel = normal.channel;
+			sao.shader.useWorldUV = props.useWorldUV;
+			sao.shader.microOcclusion = occlu.texture;
+			sao.shader.microOcclusionChannel = occlu.channel;
+			sao.shader.microOcclusionIntensity = props.microIntensity;
+			sao.apply(depth.texture,normal.texture,ctx.camera);
+			ctx.engine.popTarget();
+
+			saoBlur.radius = props.blur;
+			saoBlur.quality = 0.5;
+			saoBlur.apply(ctx, saoTex);
+
+			saoCopy.pass.setColorChannel(occlu.channel);
+			saoCopy.apply(saoTex, occlu.texture);
+		}
+	}
+
+	#if editor
+	override function edit( ctx : hide.prefab.EditContext ) {
+		ctx.properties.add(new hide.Element('
+			<dl>
+			<dt>Intensity</dt><dd><input type="range" min="0" max="10" field="intensity"/></dd>
+			<dt>Radius</dt><dd><input type="range" min="0" max="10" field="radius"/></dd>
+			<dt>Bias</dt><dd><input type="range" min="0" max="0.5" field="bias"/></dd>
+			<dt>Size</dt><dd><input type="range" min="0" max="1" field="size"/></dd>
+			<dt>Blur</dt><dd><input type="range" min="0" max="20" field="blur"/></dd>
+			<dt>Samples</dt><dd><input type="range" min="3" max="256" field="samples" step="1"/></dd>
+			<dt>Micro Intensity</dt><dd><input type="range" min="0" max="1" field="microIntensity"/></dd>
+			<dt>Use World UV</dt><dd><input type="checkbox" field="useWorldUV"/></dd>
+			</dl>
+		'),props);
+	}
+	#end
+
+	static var _ = Library.register("rfx.sao", Sao);
+
+}

+ 218 - 0
hrt/prefab/terrain/Shader.hx

@@ -0,0 +1,218 @@
+package hrt.prefab.terrain;
+
+class Shader extends hxsl.Shader {
+
+	static var SRC = {
+
+		@:import h3d.shader.BaseMesh;
+		@const var SHOW_GRID : Bool;
+		@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;
+		@param var cellSize : Float;
+
+		@param var albedoTextures : Sampler2DArray;
+		@param var normalTextures : Sampler2DArray;
+		@param var pbrTextures : Sampler2DArray;
+		@param var weightTextures : Sampler2DArray;
+		@param var surfaceIndexMap : Sampler2D;
+		@param var heightMap : Sampler2D;
+		@param var surfaceParams : Array<Vec4, SURFACE_COUNT>;
+		@param var secondSurfaceParams : Array<Vec4, SURFACE_COUNT>;
+
+		@param var heightBlendStrength : Float;
+		@param var blendSharpness : Float;
+
+		@param var parallaxAmount : Float;
+		@param var minStep : Int;
+		@param var maxStep : Int;
+		@param var tileIndex : Vec2;
+
+		var calculatedUV : Vec2;
+		var terrainUV : Vec2;
+		var TBN : Mat3;
+
+		var emissiveValue : Float;
+		var metalnessValue : Float;
+		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 = camera.position * TBN;
+			tangentFragPos = transformedPosition * TBN;
+		}
+
+		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;
+		}
+
+		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;
+			if( w.y > 0 ) depth.y = pbrTextures.getLod(getsurfaceUV(i.y, uv), 0).a;
+			if( w.z > 0 ) depth.z = pbrTextures.getLod(getsurfaceUV(i.z, uv), 0).a;
+			return 1 - depth;
+		}
+
+		var w : Vec3;
+		var i : 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;
+					i = surfaceIndexMap.getLod(curUV, 0).rgb * 255;
+					w = getWeight(i, curUV);
+					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;
+			}
+		}
+
+		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 % 1, i);
+			return surfaceUV;
+		}
+
+		function fragment() {
+
+			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;
+				roughnessValue = mix(0.9, 0.6, tile);
+				metalnessValue = mix(0.4, 0, tile);
+				occlusionValue = 1;
+				emissiveValue = 0;
+			}
+			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);
+				pixelColor = vec4(mix(vec3(0,1,0), vec3(1,0,0), blendCount / 3.0) , 1);
+				transformedNormal = vec3(0,0,1) * TBN;
+				emissiveValue = 1;
+				roughnessValue = 1;
+				metalnessValue = 0;
+				occlusionValue = 1;
+			}
+			else {
+				i = surfaceIndexMap.get(calculatedUV).rgb * 255;
+				w = getWeight(i, calculatedUV);
+				var pomUV = getPOMUV(i, calculatedUV);
+				if( PARALLAX ) {
+					i = surfaceIndexMap.get(pomUV).rgb * 255;
+					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 = vec4(0), pbr2 = vec4(0), pbr3 = vec4(0);
+				if( w.x > 0 ) pbr1 = pbrTextures.get(surfaceUV1).rgba;
+				if( w.y > 0 ) pbr2 = pbrTextures.get(surfaceUV2).rgba;
+				if( w.z > 0 ) pbr3 = pbrTextures.get(surfaceUV3).rgba;
+
+				// Height Blend
+				var h = vec3( 	secondSurfaceParams[i.x].x + pbr1.a * (secondSurfaceParams[i.x].y - secondSurfaceParams[i.x].x),
+								secondSurfaceParams[i.y].x + pbr2.a * (secondSurfaceParams[i.y].y - secondSurfaceParams[i.y].x),
+								secondSurfaceParams[i.z].x + pbr3.a * (secondSurfaceParams[i.z].y - 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);
+				var pbr = vec4(0);
+				if( w.x > 0 ) {
+					albedo += albedoTextures.get(surfaceUV1).rgb * w.x;
+					normal += normalTextures.get(surfaceUV1).rgba * w.x;
+					pbr += pbr1 * w.x;
+				}
+				if( w.y > 0 ) {
+					albedo += albedoTextures.get(surfaceUV2).rgb * w.y;
+					normal += normalTextures.get(surfaceUV2).rgba * w.y;
+					pbr += pbr2 * w.y;
+				}
+				if( w.z > 0 ) {
+					albedo += albedoTextures.get(surfaceUV3).rgb * w.z;
+					normal += normalTextures.get(surfaceUV3).rgba * w.z;
+					pbr += pbr3 * w.z;
+				}
+				var wSum = w.x + w.y + w.z;
+				albedo /= wSum;
+				pbr /= 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;
+				grid = ceil(max(vec2(0), abs(grid) - 0.9));
+				var tileEdge = max( (1 - ceil(input.position.xy / primSize - 0.1 / (primSize / cellSize) )), floor(input.position.xy / primSize + 0.1 / (primSize / cellSize)));
+				emissiveValue = max(max(grid.x, grid.y), max(tileEdge.x, tileEdge.y));
+				pixelColor = mix( pixelColor, gridColor, clamp(0,1,max(grid.x, grid.y)));
+				pixelColor = mix( pixelColor, tileEdgeColor, clamp(0,1,max(tileEdge.x, tileEdge.y)));
+				metalnessValue =  mix(metalnessValue, 0, emissiveValue);
+				roughnessValue = mix(roughnessValue, 1, emissiveValue);
+				occlusionValue = mix(occlusionValue, 1, emissiveValue);
+				transformedNormal = mix(transformedNormal, vec3(0,1,0), emissiveValue);
+			}
+		}
+	};
+
+}
+

+ 71 - 0
hrt/prefab/terrain/Surface.hx

@@ -0,0 +1,71 @@
+package hrt.prefab.terrain;
+
+class Surface {
+	public var albedo : h3d.mat.Texture;
+	public var normal : h3d.mat.Texture;
+	public var pbr : h3d.mat.Texture;
+	public var tilling = 1.0;
+	public var offset : h3d.Vector;
+	public var angle = 0.0;
+	public var minHeight = 0.0;
+	public var maxHeight = 1.0;
+
+	public function new( ?albedo : h3d.mat.Texture, ?normal : h3d.mat.Texture, ?pbr : h3d.mat.Texture ) {
+		this.albedo = albedo;
+		this.normal = normal;
+		this.pbr = pbr;
+		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.minHeight = minHeight;
+		o.maxHeight = maxHeight;
+		return o;
+	}
+
+	public function dispose() {
+		if( albedo != null ) albedo.dispose();
+		if( normal != null ) normal.dispose();
+		if( pbr != null ) pbr.dispose();
+	}
+}
+
+class SurfaceArray {
+	public var albedo : h3d.mat.TextureArray;
+	public var normal : h3d.mat.TextureArray;
+	public var pbr : h3d.mat.TextureArray;
+	public var surfaceCount : Int;
+	public var params : Array<h3d.Vector> = [];
+	public var secondParams : Array<h3d.Vector> = [];
+
+	public function new( count, res ) {
+		surfaceCount = count;
+		albedo = new h3d.mat.TextureArray(res, res, count, [Target], RGBA);
+		normal = new h3d.mat.TextureArray(res, res, count, [Target], RGBA);
+		pbr = new h3d.mat.TextureArray(res, res, count, [Target], RGBA);
+		albedo.wrap = Repeat;
+		albedo.realloc = null;
+		albedo.preventAutoDispose();
+		normal.wrap = Repeat;
+		normal.realloc = null;
+		normal.preventAutoDispose();
+		pbr.wrap = Repeat;
+		pbr.realloc = null;
+		pbr.preventAutoDispose();
+	}
+
+	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();
+		if( pbr != null ) pbr.dispose();
+	}
+}

+ 6 - 6
hrt/prefab/terrain/Terrain.hx

@@ -21,7 +21,7 @@ class Terrain extends Object3D {
 	public var weightMapResolution : Int = 20;
 	public var autoCreateTile = false;
 	var tmpSurfacesProps : Array<SurfaceProps> = [];
-	public var terrain : h3d.scene.pbr.terrain.Terrain;
+	public var terrain : TerrainMesh;
 	var parallaxAmount = 0.0;
 	var parallaxMinStep : Int = 1;
 	var parallaxMaxStep : Int = 16;
@@ -35,7 +35,7 @@ class Terrain extends Object3D {
 	#if editor
 	var packWeight = new h3d.pass.ScreenFx(new PackWeight());
 	var editor : hide.prefab.terrain.TerrainEditor;
-	var cachedInstance : h3d.scene.pbr.terrain.Terrain;
+	var cachedInstance : TerrainMesh;
 	public var showChecker = false;
 	#end
 
@@ -269,7 +269,7 @@ class Terrain extends Object3D {
 
 	public function loadBinary( ctx : Context ) {
 
-		terrain.surfaceArray = new h3d.scene.pbr.terrain.Surface.SurfaceArray(surfaceCount, surfaceSize);
+		terrain.surfaceArray = new Surface.SurfaceArray(surfaceCount, surfaceSize);
 
 		var resDir = ctx.shared.loadDir(name);
 		if( resDir == null ) return;
@@ -335,11 +335,11 @@ class Terrain extends Object3D {
 			return ctx;
 		}
 		else {
-			terrain = new h3d.scene.pbr.terrain.Terrain(ctx.local3d.getScene());
+			terrain = new TerrainMesh(ctx.local3d.getScene());
 			cachedInstance = terrain;
 		}
 		#else
-		terrain = new h3d.scene.pbr.terrain.Terrain(ctx.local3d);
+		terrain = new TerrainMesh(ctx.local3d);
 		#end
 		terrain.cellCount = getCellCount();
 		terrain.cellSize = getCellSize();
@@ -471,5 +471,5 @@ class Terrain extends Object3D {
 	}
 	#end
 
-	static var _ = hxd.prefab.Library.register("terrain", Terrain);
+	static var _ = Library.register("terrain", Terrain);
 }

+ 278 - 0
hrt/prefab/terrain/TerrainMesh.hx

@@ -0,0 +1,278 @@
+package hrt.prefab.terrain;
+
+class TerrainMesh extends h3d.scene.Object {
+
+	public var tileSize : Float;
+	public var cellSize : Float;
+	public var cellCount : Int;
+	public var heightMapResolution : Int;
+	public var weightMapResolution : Int;
+	public var showGrid : Bool;
+	public var showChecker : Bool;
+	public var showComplexity : Bool;
+	public var parallaxAmount : Float;
+	public var parallaxMinStep : Int;
+	public var parallaxMaxStep : Int;
+	public var heightBlendStrength : Float;
+	public var blendSharpness : Float;
+	public var tiles : Array<Tile> = [];
+	public var surfaces : Array<Surface> = [];
+	public var surfaceArray : Surface.SurfaceArray;
+	public var copyPass : h3d.pass.Copy;
+
+	public function new(?parent){
+		super(parent);
+		copyPass = new h3d.pass.Copy();
+	}
+
+	override function onRemove() {
+		super.onRemove();
+		for( s in surfaces )
+			s.dispose();
+		if( surfaceArray != null )
+			surfaceArray.dispose();
+	}
+
+	public override function clone( ?o : h3d.scene.Object ) : h3d.scene.Object {
+		var o = new TerrainMesh();
+		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);
+		if( t != null ) {
+			tmpVec.set(x, y);
+			var pos = t.globalToLocal(tmpVec);
+			z = t.getHeight(pos.x / tileSize, pos.y / tileSize);
+		}
+		return z;
+	}
+
+	public function getSurface( i : Int ) : Surface {
+		if(i < surfaces.length)
+				return surfaces[i];
+		return null;
+	}
+
+	public function getSurfaceFromTex( albedo, ?normal, ?pbr ) : Surface {
+		for( s in surfaces ) {
+			var valid = false;
+			valid = s.albedo.name == albedo;
+			valid = valid && !( normal != null && s.normal.name != normal );
+			valid = valid && !( pbr != null && s.pbr.name != pbr );
+			if( valid ) return s;
+		}
+		return null;
+	}
+
+	public function addSurface( albedo, normal, pbr ) : Surface {
+		surfaces.push(new Surface(albedo, normal, pbr));
+		return surfaces[surfaces.length - 1];
+	}
+
+	public function addEmptySurface() : Surface {
+		surfaces.push( new Surface() );
+		return surfaces[surfaces.length - 1];
+	}
+
+	public function generateSurfaceArray() {
+		if( surfaces.length == 0 ) return;
+		var surfaceSize = 1;
+		for( i in 0 ... surfaces.length )
+			if( surfaces[i].albedo != null ) surfaceSize = hxd.Math.ceil(hxd.Math.max(surfaces[i].albedo.width, surfaceSize));
+
+		if(surfaceArray != null) surfaceArray.dispose();
+		surfaceArray = new Surface.SurfaceArray(surfaces.length, surfaceSize);
+		for( i in 0 ... surfaces.length ) {
+			if( surfaces[i].albedo != null ) copyPass.apply(surfaces[i].albedo, surfaceArray.albedo, null, null, i);
+			if( surfaces[i].normal != null ) copyPass.apply(surfaces[i].normal, surfaceArray.normal, null, null, i);
+			if( surfaces[i].pbr != null ) copyPass.apply(surfaces[i].pbr, surfaceArray.pbr, null, null, i);
+		}
+		updateSurfaceParams();
+		refreshTex();
+	}
+
+	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(surfaces[i].minHeight, surfaces[i].maxHeight, 0, 0);
+		}
+	}
+
+	public function refreshTiles() {
+		for( tile in tiles )
+			if( tile.needAlloc ) {
+				tile.grid.alloc(h3d.Engine.getCurrent());
+				tile.needAlloc = false;
+			}
+	}
+
+	public function refreshMesh() {
+		for( tile in tiles ) {
+			tile.x = tile.tileX * tileSize;
+			tile.y = tile.tileY * tileSize;
+			tile.refreshMesh();
+		}
+		for( tile in tiles )
+			tile.blendEdges();
+	}
+
+	public function refreshTex() {
+		for( tile in tiles ) {
+			tile.refresh();
+		}
+	}
+
+	public function refresh() {
+		refreshMesh();
+		refreshTex();
+	}
+
+	public function createEmptyTile(x : Int, y : Int) : Tile {
+		var tile = getTile(x,y);
+		if(tile == null){
+			tile = new Tile(x, y, this);
+			tile.refreshMesh();
+			tiles.push(tile);
+		}
+		return tile;
+	}
+
+	public function createTile( x : Int, y : Int ) : Tile {
+		var tile = getTile(x,y);
+		if( tile == null ) {
+			tile = new Tile(x, y, this);
+			tile.refreshMesh();
+			tile.refresh();
+			tiles.push(tile);
+		}
+		return tile;
+	}
+
+	public function addTile( tile : Tile, ?replace = false ) {
+		for( t in tiles ) {
+			if( tile == t ) return;
+			if( tile.tileX == t.tileX && tile.tileY == t.tileY ) {
+				if( replace ) {
+					removeTile(t);
+					break;
+				}else
+					return;
+			}
+		}
+		tile.parent = this;
+		tiles.push(tile);
+		addChild(tile);
+	}
+
+	public function removeTileAt( x : Int, y : Int ) : Bool {
+		var t = getTile(x,y);
+		if( t == null ) {
+			removeTile(t);
+			return true;
+		}
+		return false;
+	}
+
+	public function removeTile( t : Tile ) : Bool {
+		if( t == null ) return false;
+		var r = tiles.remove(t);
+		if( r ) t.remove();
+		return r;
+	}
+
+	public function getTileIndex( t : Tile ) : Int {
+		for( i in 0 ... tiles.length )
+			if( t == tiles[i] ) return i;
+		return -1;
+	}
+
+	public function getTile( x : Int, y : Int ) : Tile {
+		var result : Tile = null;
+		for( tile in tiles )
+			if( tile.tileX == x && tile.tileY == y ) result = tile;
+		return result;
+	}
+
+	public function getTileAtWorldPos( x : Float, y : Float ) : Tile {
+		var pos = toLocalPos(x, y);
+		var result : Tile = null;
+		var tileX = Math.floor(pos.x / tileSize);
+		var tileY = Math.floor(pos.y / tileSize);
+		for( tile in tiles )
+			if( tile.tileX == tileX && tile.tileY == tileY ) result = tile;
+		return result;
+	}
+
+	public function createTileAtWorldPos( x : Float, y : Float ) : Tile {
+		var pos = toLocalPos(x, y);
+		var tileX = Math.floor(pos.x / tileSize);
+		var tileY = Math.floor(pos.y / tileSize);
+		var result = getTile(tileX, tileY);
+		return result == null ? createTile(tileX, tileY) : result;
+	}
+
+	public function getTiles( x : Float, y : Float, range : Float, ?create = false ) : Array<Tile> {
+		var pos = toLocalPos(x, y);
+		if( create != null && create ) {
+			var maxTileX = Math.floor((pos.x + range)/ tileSize);
+			var minTileX = Math.floor((pos.x - range)/ tileSize);
+			var maxTileY = Math.floor((pos.y + range)/ tileSize);
+			var minTileY = Math.floor((pos.y - range)/ tileSize);
+			for( x in minTileX ... maxTileX + 1)
+				for( y in minTileY...maxTileY + 1)
+					createTile(x, y);
+		}
+		var result : Array<Tile> = [];
+		for( tile in tiles)
+			if( Math.abs(pos.x - (tile.tileX * tileSize + tileSize * 0.5)) <= range + (tileSize * 0.5)
+			&& Math.abs(pos.y - (tile.tileY * tileSize + tileSize * 0.5)) <= range + (tileSize * 0.5) )
+				result.push(tile);
+		return result;
+	}
+
+	public function getVisibleTiles( c : h3d.Camera ) : Array<Tile> {
+		var res = [];
+		for( tile in tiles ) {
+			var bounds = tile.getBounds();
+			if( c.frustum.hasBounds(bounds) )
+				res.push(tile);
+		}
+		return res;
+	}
+
+	static var tmpVec = new h3d.Vector();
+	inline function toLocalPos( x : Float, y : Float ) {
+		tmpVec.set(x, y);
+		globalToLocal(tmpVec);
+		return tmpVec;
+	}
+}
+
+

+ 614 - 0
hrt/prefab/terrain/Tile.hx

@@ -0,0 +1,614 @@
+package hrt.prefab.terrain;
+
+enum Direction{
+	Up; Down; Left; Right; UpLeft; UpRight; DownLeft; DownRight;
+}
+
+class Tile extends h3d.scene.Mesh {
+
+	public var tileX (default, null) : Int;
+	public var tileY (default, null) : Int;
+	public var heightMap(default, set) : h3d.mat.Texture;
+	public var surfaceIndexMap : h3d.mat.Texture;
+	public var surfaceWeights : Array<h3d.mat.Texture> = [];
+	public var surfaceWeightArray (default, null) : h3d.mat.TextureArray;
+	public var grid (default, null) : h3d.prim.Grid;
+	public var needAlloc = false;
+	public var needNewPixelCapture = false;
+	public var insideFrustrum = false;
+	var heightmapPixels : hxd.Pixels.PixelsFloat;
+	var shader : Shader;
+
+	public function new( x : Int, y : Int , ?parent ) {
+		super(null, null, parent);
+		this.tileX = x;
+		this.tileY = y;
+		shader = new Shader();
+		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;
+	}
+
+	override function onRemove() {
+		if( heightMap != null )
+			heightMap.dispose();
+		if( surfaceIndexMap != null )
+			surfaceIndexMap.dispose();
+		for( i in 0 ... surfaceWeights.length )
+			if( surfaceWeights[i] != null ) surfaceWeights[i].dispose();
+		if( surfaceWeightArray != null )
+			surfaceWeightArray.dispose();
+	}
+
+	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() {
+		return Std.instance(parent, TerrainMesh);
+	}
+
+	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 ) {
+			if(grid != null) grid.dispose();
+		 	grid = new h3d.prim.Grid( getTerrain().cellCount, getTerrain().cellCount, getTerrain().cellSize, getTerrain().cellSize);
+			primitive = grid;
+			//grid.addUVs(); // Not currently used
+		}
+		computeNormals();
+	}
+
+	public function blendEdges() {
+		var adjTileX = getTerrain().getTile(tileX - 1, tileY);
+		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 ) {
+			var flags = new haxe.EnumFlags<Direction>();
+        	flags.set(Up);
+			adjTileY.computeEdgesHeight(flags);
+		}
+		var adjTileXY = getTerrain().getTile(tileX - 1, tileY - 1);
+		if( adjTileXY != null ) {
+			var flags = new haxe.EnumFlags<Direction>();
+        	flags.set(UpLeft);
+			adjTileXY.computeEdgesHeight(flags);
+		}
+		var flags = new haxe.EnumFlags<Direction>();
+        flags.set(Left);
+		flags.set(Up);
+		flags.set(UpLeft);
+		computeEdgesHeight(flags);
+		computeNormals();
+
+		computeEdgesNormals();
+	}
+
+	function refreshHeightMap() {
+		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.name = "terrainHeightMap";
+			heightMap.wrap = Clamp;
+			heightMap.filter = Linear;
+			heightMap.preventAutoDispose();
+			heightMap.realloc = null;
+			if( oldHeightMap != null ) {
+				getTerrain().copyPass.apply(oldHeightMap, heightMap);
+				oldHeightMap.dispose();
+			}
+			needNewPixelCapture = true;
+		}
+	}
+
+	function refreshIndexMap() {
+		if( surfaceIndexMap == null || surfaceIndexMap.width != getTerrain().weightMapResolution ) {
+			var oldSurfaceIndexMap = surfaceIndexMap;
+			surfaceIndexMap = new h3d.mat.Texture(getTerrain().weightMapResolution, getTerrain().weightMapResolution, [Target], RGBA);
+			surfaceIndexMap.name = "terrainSurfaceIndexMap";
+			surfaceIndexMap.filter = Nearest;
+			surfaceIndexMap.preventAutoDispose();
+			surfaceIndexMap.realloc = null;
+			if( oldSurfaceIndexMap != null ) {
+				getTerrain().copyPass.apply(oldSurfaceIndexMap, surfaceIndexMap);
+				oldSurfaceIndexMap.dispose();
+			}
+		}
+	}
+
+	function refreshSurfaceWeights() {
+		if( getTerrain().surfaceArray.surfaceCount > 0 && (surfaceWeights.length != getTerrain().surfaceArray.surfaceCount || surfaceWeights[0].width != getTerrain().weightMapResolution) ) {
+				var oldArray = surfaceWeights;
+				surfaceWeights = new Array<h3d.mat.Texture>();
+				surfaceWeights = [for( i in 0...getTerrain().surfaceArray.surfaceCount ) null];
+				for( i in 0 ... surfaceWeights.length ) {
+					surfaceWeights[i] = new h3d.mat.Texture(getTerrain().weightMapResolution, getTerrain().weightMapResolution, [Target], R8);
+					surfaceWeights[i].name = "terrainSurfaceWeight"+i;
+					surfaceWeights[i].wrap = Clamp;
+					surfaceWeights[i].preventAutoDispose();
+					surfaceWeights[i].realloc = null;
+					if( i < oldArray.length && oldArray[i] != null )
+						getTerrain().copyPass.apply(oldArray[i], surfaceWeights[i]);
+				}
+				for( t in oldArray )
+					if( t != null)
+						t.dispose();
+		}
+	}
+
+	public function refresh() {
+		refreshHeightMap();
+		refreshIndexMap();
+		refreshSurfaceWeights();
+		generateWeightArray();
+	}
+
+	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.name = "terrainSurfaceWeightArray";
+			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);
+	}
+
+	public function computeEdgesHeight( flag : haxe.EnumFlags<Direction> ) {
+
+		if( heightMap == null ) return;
+		var pixels : hxd.Pixels.PixelsFloat = getHeightPixels();
+
+		if( flag.has(Left) ) {
+			var adjTileX = getTerrain().getTile(tileX + 1, tileY);
+			var adjHeightMapX = adjTileX != null ? adjTileX.heightMap : null;
+			if( adjHeightMapX != null ) {
+				var adjpixels : hxd.Pixels.PixelsFloat = adjTileX.getHeightPixels();
+				for( i in 0 ... heightMap.height - 1 ) {
+					pixels.setPixelF(heightMap.width - 1, i, adjpixels.getPixelF(0,i) );
+				}
+			}
+		}
+		if( flag.has(Up) ) {
+			var adjTileY = getTerrain().getTile(tileX, tileY + 1);
+			var adjHeightMapY = adjTileY != null ? adjTileY.heightMap : null;
+			if( adjHeightMapY != null ) {
+				var adjpixels : hxd.Pixels.PixelsFloat = adjTileY.getHeightPixels();
+				for( i in 0 ... heightMap.width - 1) {
+					pixels.setPixelF(i, heightMap.height - 1, adjpixels.getPixelF(i,0) );
+				}
+			}
+		}
+		if( flag.has(UpLeft) ) {
+			var adjTileXY = getTerrain().getTile(tileX + 1, tileY + 1);
+			var adjHeightMapXY = adjTileXY != null ? adjTileXY.heightMap : null;
+			if( adjHeightMapXY != null ) {
+				var adjpixels : hxd.Pixels.PixelsFloat = adjTileXY.getHeightPixels();
+				pixels.setPixelF(heightMap.width - 1, heightMap.height - 1, adjpixels.getPixelF(0,0));
+			}
+		}
+		heightmapPixels = pixels;
+		heightMap.uploadPixels(pixels);
+		needNewPixelCapture = false;
+	}
+
+	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;
+		var step = hxd.Math.floor(grid.normals.length / grid.width) - 1;
+		var s = hxd.Math.floor(grid.normals.length - grid.normals.length / grid.width + 2);
+		var istep = triCount * 3 - 6;
+		var i0, i1, i2 : Int = 0;
+
+		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);
+			t2.z += tile.getHeight(t2.x / getTerrain().tileSize, t2.y / getTerrain().tileSize);
+		}
+
+		inline function computeNormal() : h3d.col.Point {
+			var n1 = t1.sub(t0);
+			n1.normalize();
+			var n2 = t2.sub(t0);
+			n2.normalize();
+			return n1.cross(n2);
+		}
+
+		var adjUpTile = getTerrain().getTile(tileX, tileY + 1);
+		var adjUpGrid = adjUpTile != null ? adjUpTile.grid: null;
+		var adjDownTile = getTerrain().getTile(tileX, tileY - 1);
+		var adjDownGrid = adjDownTile != null ? adjDownTile.grid: null;
+		var adjLeftTile = getTerrain().getTile(tileX + 1, tileY);
+		var adjLeftGrid = adjLeftTile != null ? adjLeftTile.grid: null;
+		var adjRightTile = getTerrain().getTile(tileX - 1, tileY);
+		var adjRightGrid = adjRightTile != null ? adjRightTile.grid: null;
+		var adjUpRightTile = getTerrain().getTile(tileX - 1, tileY + 1);
+		var adjUpRightGrid = adjUpRightTile != null ? adjUpRightTile.grid: null;
+		var adjUpLeftTile = getTerrain().getTile(tileX + 1, tileY + 1);
+		var adjUpLeftGrid = adjUpLeftTile != null ? adjUpLeftTile.grid: null;
+		var adjDownLeftTile = getTerrain().getTile(tileX + 1, tileY - 1);
+		var adjDownLeftGrid = adjDownLeftTile != null ? adjDownLeftTile.grid: null;
+		var adjDownRightTile = getTerrain().getTile(tileX - 1, tileY - 1);
+		var adjDownRightGrid = adjDownRightTile != null ? adjDownRightTile.grid: null;
+
+		if( adjUpGrid != null && adjUpGrid.normals != null ) {
+			var pos = 0;
+			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;}
+			}
+			for( i in 0 ... vertexCount )
+				adjUpGrid.normals[i].normalize();
+			for( i in 1 ... vertexCount - 1 ) {
+				var n = grid.normals[s + i].add(adjUpGrid.normals[i]);
+				n.normalize();
+				grid.normals[s + i].load(n);
+				adjUpGrid.normals[i].load(n);
+			}
+		}
+
+		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);
+			for( i in 0 ... triCount ) {
+				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;}
+			}
+			for( i in 1 ... vertexCount - 1 )
+				adjDownGrid.normals[s + i].normalize();
+			for( i in 1 ... vertexCount - 1 ){
+				var n = grid.normals[i].add(adjDownGrid.normals[s + i]);
+				n.normalize();
+				grid.normals[i].load(n);
+				adjDownGrid.normals[s + i].load(n);
+			}
+		}
+
+		if( adjLeftGrid != null && adjLeftGrid.normals != null ) {
+			var pos = 0;
+			var istep = triCount * 3 - 6;
+			var needStep = false;
+			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;
+				needStep = !needStep;
+			}
+			for( i in 0 ... vertexCount )
+				adjLeftGrid.normals[i * step].normalize();
+			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);
+				adjLeftGrid.normals[i * step].load(n);
+			}
+		}
+
+		if( adjRightGrid != null && adjRightGrid.normals != null ) {
+			var pos = (triCount - 2) * 3;
+			var istep = (triCount - 2) * 3;
+			var needStep = false;
+			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;
+				needStep = !needStep;
+			}
+			for( i in 0 ... vertexCount )
+				adjRightGrid.normals[i * step + (step - 1)].normalize();
+			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);
+				adjRightGrid.normals[i * step + (step - 1)].load(n);
+			}
+		}
+
+		var topLeft = grid.points.length - 1;
+		var downLeft= step - 1;
+		var downRight = 0;
+		var upRight = step * grid.height;
+
+		var n = new h3d.col.Point();
+		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);
+			n = computeNormal();
+			i0 = adjUpRightGrid.idx[pos++]; i1 = adjUpRightGrid.idx[pos++]; i2 = adjUpRightGrid.idx[pos++];
+			computeVertexPos(adjUpRightTile);
+			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]);
+		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);
+		grid.normals[upRight].load(n);
+
+		n.set(0,0,0);
+		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]);
+		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);
+		grid.normals[topLeft].load(n);
+
+		n.set(0,0,0);
+		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);
+			n = computeNormal();
+			i0 = adjDownLeftGrid.idx[pos++]; i1 = adjDownLeftGrid.idx[pos++]; i2 = adjDownLeftGrid.idx[pos++];
+			computeVertexPos(adjDownLeftTile);
+			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]);
+		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);
+		grid.normals[downLeft].load(n);
+
+		n.set(0,0,0);
+		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]);
+		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);
+		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;
+		this.needAlloc = true;
+	}
+
+	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 ) {
+				grid.normals[i].set(0,0,0);
+				grid.normals[i];
+			} else
+				new h3d.col.Point();
+		}];
+
+		var t0 = new h3d.col.Point(); var t1 = new h3d.col.Point(); var t2 = new h3d.col.Point();
+		var pos = 0;
+		for( i in 0...grid.triCount() ) {
+			var i0, i1, i2;
+			if( grid.idx == null ) {
+				i0 = pos++; i1 = pos++; i2 = pos++;
+			} else {
+				i0 = grid.idx[pos++]; i1 = grid.idx[pos++]; i2 = grid.idx[pos++];
+			}
+			t0.load(grid.points[i0]); t1.load(grid.points[i1]); t2.load(grid.points[i2]);
+			if(heightMap != null){
+				t0.z += getHeight(t0.x / getTerrain().tileSize, t0.y / getTerrain().tileSize);
+				t1.z += getHeight(t1.x / getTerrain().tileSize, t1.y / getTerrain().tileSize);
+				t2.z += getHeight(t2.x / getTerrain().tileSize, t2.y / getTerrain().tileSize);
+			}
+			var n1 = t1.sub(t0);
+			n1.normalizeFast();
+			var n2 = t2.sub(t0);
+			n2.normalizeFast();
+			var n = n1.cross(n2);
+			grid.normals[i0].x += n.x; grid.normals[i0].y += n.y; grid.normals[i0].z += n.z;
+			grid.normals[i1].x += n.x; grid.normals[i1].y += n.y; grid.normals[i1].z += n.z;
+			grid.normals[i2].x += n.x; grid.normals[i2].y += n.y; grid.normals[i2].z += n.z;
+		}
+		for( n in grid.normals )
+			n.normalize();
+
+		needAlloc = true;
+	}
+
+	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) {
+				return pixels.getPixelF(Std.int(hxd.Math.clamp(u, 0, pixels.width - 1)), Std.int(hxd.Math.clamp(v, 0, pixels.height - 1))).r;
+			}
+			var px = u * (heightMap.width - 1) + 0.5;
+            var py = v * (heightMap.width - 1) + 0.5;
+			var pxi = hxd.Math.floor(px);
+            var pyi = hxd.Math.floor(py);
+			var c00 = getPix(pxi, pyi);
+			var c10 = getPix(pxi + 1, pyi);
+			var c01 = getPix(pxi, pyi + 1);
+			var c11 = getPix(pxi + 1, pyi + 1);
+			var wx = px - pxi;
+			var wy = py - pyi;
+			var a = c00 * (1 - wx) + c10 * wx;
+			var b = c01 * (1 - wx) + c11 * wx;
+			return a * (1 - wy) + b * wy;
+
+		}
+		else{
+			var x = hxd.Math.floor(u * (heightMap.width - 1) + 0.5);
+			var y = hxd.Math.floor(v * (heightMap.height - 1) + 0.5);
+			return pixels.getPixelF(x, y).r;
+		}
+	}
+	var cachedBounds : h3d.col.Bounds;
+	var cachedHeightBound : Bool = false;
+	function computeBounds() {
+		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 ) {
+					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;
+				}
+			}
+			var absPos = getAbsPos();
+			cachedBounds.zMax += absPos.tz;
+			cachedBounds.zMin += absPos.tz;
+			cachedHeightBound = true;
+		}
+	}
+
+	public dynamic function beforeEmit() : Bool { return true; };
+	override function emit( ctx:h3d.scene.RenderContext ) {
+		if( !isReady() ) return;
+		computeBounds();
+		insideFrustrum = ctx.camera.frustum.hasBounds(cachedBounds);
+		var b = beforeEmit();
+		if( b && insideFrustrum )
+			super.emit(ctx);
+	}
+
+	override function sync(ctx:h3d.scene.RenderContext) {
+		if( !isReady() ) return;
+
+		shader.SHOW_GRID = getTerrain().showGrid;
+		shader.SURFACE_COUNT = getTerrain().surfaceArray.surfaceCount;
+		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;
+
+		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);
+			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().showChecker || getTerrain().showComplexity ) {
+			if( getTerrain().surfaceArray == null || getTerrain().surfaceArray.surfaceCount == 0 || surfaceWeights.length != getTerrain().surfaceArray.surfaceCount )
+				return false;
+
+			for( i in 0 ... surfaceWeights.length )
+				if( surfaceWeights[i] == null || surfaceWeights[i].isDisposed() )
+					return false;
+		}
+
+		if( heightMap == null || heightMap.isDisposed() )
+			return false;
+
+		return true;
+	}
+
+	override function getLocalCollider() : h3d.col.Collider {
+		return null;
+	}
+}