Преглед изворни кода

Update Terrain, add normalMap

ShiroSmith пре 5 година
родитељ
комит
f282869496

+ 2 - 2
hide/prefab/terrain/CustomUV.hx

@@ -15,9 +15,9 @@ class CustomUV extends hxsl.Shader {
 		var terrainUV : Vec2;
 		var terrainUV : Vec2;
 
 
 		function vertex() {
 		function vertex() {
-			terrainUV = input.position.xy / primSize.xy * (heightMapSize.xy - 1) / heightMapSize.xy + 0.5 / heightMapSize.xy;
 			calculatedUV = input.position.xy / primSize.xy;
 			calculatedUV = input.position.xy / primSize.xy;
-			transformedPosition += (vec3(0,0, heightMap.get(terrainUV).r) * global.modelView.mat3());
+			transformedPosition += (vec3(0,0, heightMap.get(calculatedUV).r) * global.modelView.mat3());
+			transformedNormal = vec3(0,0,0);
 		}
 		}
 
 
 		function fragment() {
 		function fragment() {

+ 172 - 37
hide/prefab/terrain/TerrainEditor.hx

@@ -1,6 +1,7 @@
 package hide.prefab.terrain;
 package hide.prefab.terrain;
-
 using Lambda;
 using Lambda;
+import hxd.Pixels;
+import hrt.prefab.terrain.Tile;
 import hxd.Key as K;
 import hxd.Key as K;
 import hrt.prefab.Context;
 import hrt.prefab.Context;
 
 
@@ -83,7 +84,7 @@ class TerrainEditor {
 
 
 		brushPreview = new hide.prefab.terrain.Brush.BrushPreview(terrainPrefab.terrain);
 		brushPreview = new hide.prefab.terrain.Brush.BrushPreview(terrainPrefab.terrain);
 
 
-		heightStrokeBufferArray = new hide.prefab.terrain.StrokeBuffer.StrokeBufferArray(RGBA32F,new h2d.col.IPoint(terrainPrefab.terrain.heightMapResolution.x + 1, terrainPrefab.terrain.heightMapResolution.y + 1));
+		heightStrokeBufferArray = new hide.prefab.terrain.StrokeBuffer.StrokeBufferArray(R32F,new h2d.col.IPoint(terrainPrefab.terrain.heightMapResolution.x, terrainPrefab.terrain.heightMapResolution.y));
 		weightStrokeBufferArray = new hide.prefab.terrain.StrokeBuffer.StrokeBufferArray(R8, terrainPrefab.terrain.weightMapResolution);
 		weightStrokeBufferArray = new hide.prefab.terrain.StrokeBuffer.StrokeBufferArray(R8, terrainPrefab.terrain.weightMapResolution);
 		customScene.renderer = customRenderer;
 		customScene.renderer = customRenderer;
 		#if debug
 		#if debug
@@ -207,6 +208,7 @@ class TerrainEditor {
 						tile.material.mainPass.stencil.setFunc(Always, 0x01, 0x01, 0x01);
 						tile.material.mainPass.stencil.setFunc(Always, 0x01, 0x01, 0x01);
 						tile.material.mainPass.stencil.setOp(Keep, Keep, Replace);
 						tile.material.mainPass.stencil.setOp(Keep, Keep, Replace);
 						tile.refreshHeightMap();
 						tile.refreshHeightMap();
+						tile.refreshNormalMap();
 						tile.refreshIndexMap();
 						tile.refreshIndexMap();
 						tile.refreshSurfaceWeightArray();
 						tile.refreshSurfaceWeightArray();
 						tile.heightMap.uploadPixels(t.prevHeightMapPixels);
 						tile.heightMap.uploadPixels(t.prevHeightMapPixels);
@@ -223,22 +225,172 @@ class TerrainEditor {
 		}
 		}
 	}
 	}
 
 
-	function resetStrokeBuffers() {
-		heightStrokeBufferArray.reset();
-		weightStrokeBufferArray.reset();
-	}
+	function blendEdges( modifiedTile : Array<Tile> ) {
+
+		// Adjust the edge for each modified tiles and their neighbors
+		var tiles : Array<Tile> = [];
+		for( t in modifiedTile ) {
+			for( i in -1 ... 2 ) {
+				for( j in - 1 ... 2 ) {
+					var adj = t.terrain.getTile(t.tileX + i, t.tileY + j);
+					if( adj != null && tiles.indexOf(adj) == -1 )
+						tiles.push(adj);
+				}	
+			}
+		}
 
 
-	function refreshTileAlloc() {
-		for( tile in terrainPrefab.terrain.tiles ) {
-			if( tile.needAlloc ) {
-				tile.grid.alloc(h3d.Engine.getCurrent());
-				tile.needAlloc = false;
+		// Adjust the height to avoid seams
+		for( t in tiles ) {
+			var pixels : hxd.Pixels.PixelsFloat = t.heightMapPixels;
+			if( pixels == null )
+				throw("Try to blend the edges of a null heightmap.");
+			var adjTileX = t.terrain.getTile(t.tileX + 1, t.tileY);
+			var adjHeightMapX = adjTileX != null ? adjTileX.heightMap : null;
+			if( adjHeightMapX != null ) {
+				var adjpixels : hxd.Pixels.PixelsFloat = adjTileX.heightMapPixels;
+				for( i in 0 ... t.heightMap.height ) {
+					pixels.setPixelF(t.heightMap.width - 1, i, adjpixels.getPixelF(0,i) );
+				}
+			}
+		
+			var adjTileY = t.terrain.getTile(t.tileX, t.tileY + 1);
+			var adjHeightMapY = adjTileY != null ? adjTileY.heightMap : null;
+			if( adjHeightMapY != null ) {
+				var adjpixels : hxd.Pixels.PixelsFloat = adjTileY.heightMapPixels;
+				for( i in 0 ... t.heightMap.width ) {
+					pixels.setPixelF(i, t.heightMap.height - 1, adjpixels.getPixelF(i,0) );
+				}
 			}
 			}
+		
+			var adjTileXY = t.terrain.getTile(t.tileX + 1, t.tileY + 1);
+			var adjHeightMapXY = adjTileXY != null ? adjTileXY.heightMap : null;
+			if( adjHeightMapXY != null ) {
+				var adjpixels : hxd.Pixels.PixelsFloat = adjTileXY.heightMapPixels;
+				pixels.setPixelF(t.heightMap.width - 1, t.heightMap.height - 1, adjpixels.getPixelF(0,0));
+			}
+			
+			t.heightMapPixels = pixels;
+			t.heightMap.uploadPixels(pixels);
+			t.needNewPixelCapture = false;
+		}
+
+		// Compute the normal for each tile, but there's seam on the edge
+		for( t in tiles )  {
+			t.bakeNormal();
+			t.normalMapPixels = t.normalMap.capturePixels();
 		}
 		}
+		
+		// Compute the average normal on edge 
+		for( t in tiles ) {
+
+			var pixelsAfterBlend = t.normalMapPixels;
+			var left = t.terrain.getTile(t.tileX - 1, t.tileY);
+			var right = t.terrain.getTile(t.tileX + 1, t.tileY);
+			var up = t.terrain.getTile(t.tileX, t.tileY - 1);
+			var down = t.terrain.getTile(t.tileX, t.tileY + 1);
+			var upRight = t.terrain.getTile(t.tileX + 1, t.tileY - 1);
+			var upLeft = t.terrain.getTile(t.tileX - 1, t.tileY - 1);
+			var downRight = t.terrain.getTile(t.tileX + 1, t.tileY + 1);
+			var downLeft = t.terrain.getTile(t.tileX - 1, t.tileY + 1);
+			var width = pixelsAfterBlend.width - 1;
+			var height = pixelsAfterBlend.height - 1;
+
+			// Return the normal of tile t at (x,y) without any blend
+			function getNormalFromHeightMap(t : Tile, x : Int, y : Int ) {
+				var h = t.heightMapPixels.getPixelF(x, y).x;
+				var h1 = y + 1 >= t.heightMapPixels.height ? h : t.heightMapPixels.getPixelF(x, y + 1).x;
+				var h2 = x + 1 >= t.heightMapPixels.width ? h : t.heightMapPixels.getPixelF(x + 1, y).x;
+				var h3 = y - 1 < 0 ? h : t.heightMapPixels.getPixelF(x, y - 1).x;
+				var h4 = x - 1 < 0 ? h : t.heightMapPixels.getPixelF(x - 1, y).x;
+				var v1 = new h3d.Vector(0, 1, h1 - h);
+				var v2 = new h3d.Vector(1, 0, h2 - h);
+				var v3 = new h3d.Vector(0, -1, h3 - h);
+				var v4 = new h3d.Vector(-1, 0, h4 - h);
+				var n = v1.cross(v2).add(v2.cross(v3).add(v3.cross(v4).add(v4.cross(v1))));
+				n.scale3(-1.0);
+				n.normalize();
+				return n;
+			}
+
+			inline function packNormal( n : h3d.Vector ) {
+				n.scale3(0.5);
+				return n.add(new h3d.Vector(0.5, 0.5, 0.5)).toColor();
+			}
+
+			inline function unpackNormal( n : Int ) {
+				var n = h3d.Vector.fromColor(n);
+				n = n.add(new h3d.Vector(-0.5, -0.5, -0.5));
+				n.scale3(2.0);
+				return n;
+			}
+
+			for( i in 1 ... pixelsAfterBlend.width - 1 ) {
+				if( up != null ) {
+					var n = unpackNormal(pixelsAfterBlend.getPixel(i, 0)).add(getNormalFromHeightMap(up, i, height));
+					n.normalize();
+					pixelsAfterBlend.setPixel(i, 0, packNormal(n));
+				}
+				if( down != null ) {
+					var n = unpackNormal(pixelsAfterBlend.getPixel(i, height)).add(getNormalFromHeightMap(down, i, 0));
+					n.normalize();
+					pixelsAfterBlend.setPixel(i, height, packNormal(n));
+				}
+			}
+			for( i in 1 ... pixelsAfterBlend.height - 1 ) {
+				if( left != null ) {
+					var n = unpackNormal(pixelsAfterBlend.getPixel(0, i)).add(getNormalFromHeightMap(left, width, i));
+					n.normalize();
+					pixelsAfterBlend.setPixel(0, i, packNormal(n));
+				}
+				if( right != null ) {
+					var n = unpackNormal(pixelsAfterBlend.getPixel(width, i)).add(getNormalFromHeightMap(right, 0, i));
+					n.normalize();
+					pixelsAfterBlend.setPixel(width, i, packNormal(n));
+				}
+			}
+
+			var n = unpackNormal(pixelsAfterBlend.getPixel(0, 0));
+			if( up != null ) n = n.add(getNormalFromHeightMap(up, 0, height));
+			if( left != null ) n = n.add(getNormalFromHeightMap(left, width, 0));
+			if( upLeft != null ) n = n.add(getNormalFromHeightMap(upLeft, width, height));
+			n.normalize();
+			pixelsAfterBlend.setPixel(0, 0, packNormal(n));
+
+			var n = unpackNormal(pixelsAfterBlend.getPixel(width, 0));
+			if( up != null ) n = n.add(getNormalFromHeightMap(up, width, height));
+			if( right != null ) n = n.add(getNormalFromHeightMap(right, 0, 0));
+			if( upRight != null ) n = n.add(getNormalFromHeightMap(upRight, 0, height));
+			n.normalize();
+			pixelsAfterBlend.setPixel(width, 0, packNormal(n));
+
+			var n = unpackNormal(pixelsAfterBlend.getPixel(0, height));
+			if( down != null ) n = n.add(getNormalFromHeightMap(down, 0, 0));
+			if( left != null ) n = n.add(getNormalFromHeightMap(left, width, height));
+			if( downLeft != null ) n = n.add(getNormalFromHeightMap(downLeft, width, 0));
+			n.normalize();
+			pixelsAfterBlend.setPixel(0, height, packNormal(n));
+
+			var n = unpackNormal(pixelsAfterBlend.getPixel(width, height));
+			if( down != null ) n = n.add(getNormalFromHeightMap(down, width, 0));
+			if( right != null ) n = n.add(getNormalFromHeightMap(right, 0, height));
+			if( downRight != null ) n = n.add(getNormalFromHeightMap(downRight, 0, 0));
+			n.normalize();
+			pixelsAfterBlend.setPixel(width, height, packNormal(n));
+			
+			t.normalMap.uploadPixels(pixelsAfterBlend);
+			t.needNormalBake = false;
+		}
+	}
+
+	function resetStrokeBuffers() {
+		heightStrokeBufferArray.reset();
+		weightStrokeBufferArray.reset();
 	}
 	}
 
 
 	function applyStrokeBuffers() {
 	function applyStrokeBuffers() {
+		
 		var revertDatas = new Array<TileRevertData>();
 		var revertDatas = new Array<TileRevertData>();
+
 		for( strokeBuffer in heightStrokeBufferArray.strokeBuffers ) {
 		for( strokeBuffer in heightStrokeBufferArray.strokeBuffers ) {
 			if( strokeBuffer.used == true ) {
 			if( strokeBuffer.used == true ) {
 				var tile = terrainPrefab.terrain.getTile(strokeBuffer.x, strokeBuffer.y);
 				var tile = terrainPrefab.terrain.getTile(strokeBuffer.x, strokeBuffer.y);
@@ -246,12 +398,11 @@ class TerrainEditor {
 				tile.heightMap = strokeBuffer.prevTex;
 				tile.heightMap = strokeBuffer.prevTex;
 				strokeBuffer.prevTex = null;
 				strokeBuffer.prevTex = null;
 				var revert = new TileRevertData(strokeBuffer.x, strokeBuffer.y);
 				var revert = new TileRevertData(strokeBuffer.x, strokeBuffer.y);
-				revert.prevHeightMapPixels = tile.getHeightPixels();
+				revert.prevHeightMapPixels = tile.heightMapPixels;
 
 
 				switch( currentBrush.brushMode.mode ) {
 				switch( currentBrush.brushMode.mode ) {
 					case AddSub :
 					case AddSub :
 						copyPass.apply(strokeBuffer.tex, tile.heightMap, currentBrush.brushMode.subAction ? Sub : Add);
 						copyPass.apply(strokeBuffer.tex, tile.heightMap, currentBrush.brushMode.subAction ? Sub : Add);
-						tile.needNewPixelCapture = true;
 					case Set :
 					case Set :
 						copyPass.apply(tile.heightMap, strokeBuffer.tempTex);
 						copyPass.apply(tile.heightMap, strokeBuffer.tempTex);
 						setHeight.shader.prevHeight = strokeBuffer.tempTex;
 						setHeight.shader.prevHeight = strokeBuffer.tempTex;
@@ -269,26 +420,14 @@ class TerrainEditor {
 						smoothHeight.render();
 						smoothHeight.render();
 					default:
 					default:
 				}
 				}
-
-				tile.needNewPixelCapture = true;
-				revert.nextHeightMapPixels = tile.getHeightPixels();
 				revertDatas.push(revert);
 				revertDatas.push(revert);
+				tile.heightMapPixels = tile.heightMap.capturePixels();
+				revert.nextHeightMapPixels = tile.heightMapPixels;
+				tile.cachedBounds = null;
 			}
 			}
 		}
 		}
-		for( strokeBuffer in heightStrokeBufferArray.strokeBuffers ) {
-			if( strokeBuffer.used == true ) {
-				var tile = terrainPrefab.terrain.getTile(strokeBuffer.x, strokeBuffer.y);
-				tile.refreshGrid();
-			}
-		}
-		for( strokeBuffer in heightStrokeBufferArray.strokeBuffers ) {
-			if( strokeBuffer.used == true ) {
-				var tile = terrainPrefab.terrain.getTile(strokeBuffer.x, strokeBuffer.y);
-				tile.blendEdges();
-			}
-		}
-
-		refreshTileAlloc();
+		var tiles = [ for( sb in heightStrokeBufferArray.strokeBuffers ) { if( sb.used ) terrainPrefab.terrain.getTile(sb.x, sb.y); }];
+		blendEdges(tiles);
 
 
 		if( revertDatas.length > 0 ) {
 		if( revertDatas.length > 0 ) {
 			undo.change(Custom(function(undo) {
 			undo.change(Custom(function(undo) {
@@ -296,14 +435,10 @@ class TerrainEditor {
 					var tile = terrainPrefab.terrain.getTile(revertData.x, revertData.y);
 					var tile = terrainPrefab.terrain.getTile(revertData.x, revertData.y);
 					if( tile == null ) continue;
 					if( tile == null ) continue;
 					tile.heightMap.uploadPixels(undo ? revertData.prevHeightMapPixels : revertData.nextHeightMapPixels);
 					tile.heightMap.uploadPixels(undo ? revertData.prevHeightMapPixels : revertData.nextHeightMapPixels);
-					tile.needNewPixelCapture = true;
-				}
-				for( revertData in revertDatas ) {
-					var tile = terrainPrefab.terrain.getTile(revertData.x, revertData.y);
-					if( tile == null ) continue;
-					tile.blendEdges();
+					tile.heightMapPixels = undo ? revertData.prevHeightMapPixels : revertData.nextHeightMapPixels;
 				}
 				}
-				refreshTileAlloc();
+				var tiles = [ for( rd in revertDatas ) { terrainPrefab.terrain.getTile(rd.x, rd.y); }];
+				blendEdges(tiles);	
 			}));
 			}));
 		}
 		}
 
 

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

@@ -28,9 +28,6 @@ class Surface {
 	}
 	}
 
 
 	public function dispose() {
 	public function dispose() {
-		if( albedo != null ) albedo.dispose();
-		if( normal != null ) normal.dispose();
-		if( pbr != null ) pbr.dispose();
 	}
 	}
 }
 }
 
 

+ 194 - 198
hrt/prefab/terrain/Terrain.hx

@@ -1,5 +1,4 @@
 package hrt.prefab.terrain;
 package hrt.prefab.terrain;
-import hxd.Pixels.PixelsFloat;
 using Lambda;
 using Lambda;
 
 
 typedef SurfaceProps = {
 typedef SurfaceProps = {
@@ -15,6 +14,7 @@ typedef SurfaceProps = {
 };
 };
 
 
 @:access(hrt.prefab.terrain.TerrainMesh)
 @:access(hrt.prefab.terrain.TerrainMesh)
+@:access(hrt.prefab.terrain.Tile)
 class Terrain extends Object3D {
 class Terrain extends Object3D {
 
 
 	public var terrain : TerrainMesh;
 	public var terrain : TerrainMesh;
@@ -45,18 +45,14 @@ class Terrain extends Object3D {
 	#if editor
 	#if editor
 	var packWeight = new h3d.pass.ScreenFx(new PackWeight());
 	var packWeight = new h3d.pass.ScreenFx(new PackWeight());
 	var editor : hide.prefab.terrain.TerrainEditor;
 	var editor : hide.prefab.terrain.TerrainEditor;
-	var cachedInstance : TerrainMesh;
 	public var showChecker = false;
 	public var showChecker = false;
 	public var autoCreateTile = false;
 	public var autoCreateTile = false;
 	public var brushOpacity : Float;
 	public var brushOpacity : Float;
 	var myContext : Context;
 	var myContext : Context;
 	#end
 	#end
 
 
-	// Backward Compatibility
-	var oldHeightMapResolution : Int = -1;
-	var oldWeightMapResolution : Int = -1;
-	var oldCellSize : Float = -1;
-	var needFormatUpdate = false;
+	static final version : Int = 1;
+	var currentVersion : Int;
 
 
 	public function new( ?parent ) {
 	public function new( ?parent ) {
 		super(parent);
 		super(parent);
@@ -65,21 +61,15 @@ class Terrain extends Object3D {
 
 
 	override function load( obj : Dynamic ) {
 	override function load( obj : Dynamic ) {
 		super.load(obj);
 		super.load(obj);
-		// Backward Compatibility
-		if( obj.cellSize != null ) oldCellSize = obj.cellSize;
-		if( obj.heightMapResolution != null ) oldHeightMapResolution = hxd.Math.ceil(obj.heightMapResolution);
-		if( obj.weightMapResolution != null ) oldWeightMapResolution = hxd.Math.ceil(obj.weightMapResolution);
-		if( obj.tileSize != null ) {
-			tileSizeX = obj.tileSize;
-			tileSizeY = obj.tileSize;
-		}
-		else {
-			tileSizeX = obj.tileSizeX == null ? 1 : obj.tileSizeX;
-			tileSizeY = obj.tileSizeY == null ? 1 : obj.tileSizeY;
-		}
+
+		currentVersion = obj.currentVersion == null ? 0 : obj.currentVersion;
+
+		if( obj.tileSizeX != null ) tileSizeX = obj.tileSizeX;
+		if( obj.tileSizeY != null ) tileSizeY = obj.tileSizeY;
 		if( obj.vertexPerMeter != null ) vertexPerMeter = obj.vertexPerMeter;
 		if( obj.vertexPerMeter != null ) vertexPerMeter = obj.vertexPerMeter;
 		if( obj.weightMapPixelPerMeter != null ) weightMapPixelPerMeter = obj.weightMapPixelPerMeter;
 		if( obj.weightMapPixelPerMeter != null ) weightMapPixelPerMeter = obj.weightMapPixelPerMeter;
 		if( obj.surfaces != null ) tmpSurfacesProps = obj.surfaces;
 		if( obj.surfaces != null ) tmpSurfacesProps = obj.surfaces;
+
 		parallaxAmount = obj.parallaxAmount == null ? 0.0 : obj.parallaxAmount;
 		parallaxAmount = obj.parallaxAmount == null ? 0.0 : obj.parallaxAmount;
 		parallaxMinStep = obj.parallaxMinStep == null ? 1 : obj.parallaxMinStep;
 		parallaxMinStep = obj.parallaxMinStep == null ? 1 : obj.parallaxMinStep;
 		parallaxMaxStep = obj.parallaxMaxStep == null ? 1 : obj.parallaxMaxStep;
 		parallaxMaxStep = obj.parallaxMaxStep == null ? 1 : obj.parallaxMaxStep;
@@ -97,6 +87,7 @@ class Terrain extends Object3D {
 
 
 	override function save() {
 	override function save() {
 		var obj : Dynamic = super.save();
 		var obj : Dynamic = super.save();
+		obj.currentVersion = currentVersion;
 		obj.tileSizeX = tileSizeX;
 		obj.tileSizeX = tileSizeX;
 		obj.tileSizeY = tileSizeY;
 		obj.tileSizeY = tileSizeY;
 		obj.vertexPerMeter = vertexPerMeter;
 		obj.vertexPerMeter = vertexPerMeter;
@@ -149,9 +140,54 @@ class Terrain extends Object3D {
 		return obj;
 		return obj;
 	}
 	}
 
 
-	function loadTiles( ctx : Context, height = true, index = true , weight = true ) {
-		var resDir = ctx.shared.loadDir(name);
+	override function localRayIntersection(ctx:Context, ray:h3d.col.Ray):Float {
+		if( ray.lz > 0 )
+			return -1; // only from top
+		if( ray.lx == 0 && ray.ly == 0 ) {
+			var z = terrain.getLocalHeight(ray.px, ray.py);
+			if( z == null || z > ray.pz ) return -1;
+			return ray.pz - z;
+		}
 
 
+		var b = new h3d.col.Bounds();
+		for( t in terrain.tiles ) {
+			var cb = t.getCachedBounds();
+			if( cb != null )
+				b.add(cb);
+			else {
+				b.addPos(t.x, t.y, -10000);
+				b.addPos(t.x + terrain.cellSize.x * terrain.cellCount.x, t.y + terrain.cellSize.y * terrain.cellCount.y, 10000);
+			}
+		}
+
+		var dist = b.rayIntersection(ray, false);
+		if( dist < 0 )
+			return -1;
+		var pt = ray.getPoint(dist);
+		var m = this.vertexPerMeter;
+		var prevH = pt.z;
+		while( true ) {
+			pt.x += ray.lx * m;
+			pt.y += ray.ly * m;
+			pt.z += ray.lz * m;
+			if( !b.contains(pt) )
+				break;
+			var h = terrain.getLocalHeight(pt.x, pt.y);
+			if( pt.z < h ) {
+				var k = 1 - (prevH - (pt.z - ray.lz * m)) / (ray.lz * m - (h - prevH));
+				pt.x -= k * ray.lx * m;
+				pt.y -= k * ray.ly * m;
+				pt.z -= k * ray.lz * m;
+				return pt.sub(ray.getPos()).length();
+			}
+			prevH = h;
+		}
+		return -1;
+	}
+
+	function loadTiles( ctx : Context ) {
+
+		var resDir = ctx.shared.loadDir(name);
 		if( resDir == null )
 		if( resDir == null )
 			return;
 			return;
 
 
@@ -160,7 +196,12 @@ class Terrain extends Object3D {
 
 
 		// Avoid texture alloc for unpacking
 		// Avoid texture alloc for unpacking
 		var tmpPackedWeightTexture = new h3d.mat.Texture(terrain.weightMapResolution.x, terrain.weightMapResolution.y, [Target]);
 		var tmpPackedWeightTexture = new h3d.mat.Texture(terrain.weightMapResolution.x, terrain.weightMapResolution.y, [Target]);
+		var bakeHeightAndNormalInGeometry = #if editor false #else true #end;
 
 
+		var heightData = [];
+		var weightData = [];
+		var normalData = [];
+		var indexData = [];
 		for( res in resDir ) {
 		for( res in resDir ) {
 			var fileInfos = res.name.split(".");
 			var fileInfos = res.name.split(".");
 			var ext = fileInfos[1];
 			var ext = fileInfos[1];
@@ -170,92 +211,108 @@ class Terrain extends Object3D {
 			var y = Std.parseInt(coords[1]);
 			var y = Std.parseInt(coords[1]);
 			if( x == null || y == null ) continue;
 			if( x == null || y == null ) continue;
 			var type = coords[2];
 			var type = coords[2];
+			var data = { res : res, x : x, y : y, ext : ext };
+			switch( type ) {
+				case "n": normalData.push(data);
+				case "h": heightData.push(data);
+				case "w": weightData.push(data);
+				case "i": indexData.push(data);
+			}
+
 			var tile = terrain.createTile(x, y, false);
 			var tile = terrain.createTile(x, y, false);
 			tile.material.shadows = castShadows;
 			tile.material.shadows = castShadows;
-
-			#if editor
 			tile.material.mainPass.stencil = new h3d.mat.Stencil();
 			tile.material.mainPass.stencil = new h3d.mat.Stencil();
 			tile.material.mainPass.stencil.setFunc(Always, 0x01, 0x01, 0x01);
 			tile.material.mainPass.stencil.setFunc(Always, 0x01, 0x01, 0x01);
 			tile.material.mainPass.stencil.setOp(Keep, Keep, Replace);
 			tile.material.mainPass.stencil.setOp(Keep, Keep, Replace);
+		}
+
+		// NORMAL
+		for( nd in normalData ) {
+			var t = terrain.getTile(nd.x, nd.y);
+			var bytes = nd.res.entry.getBytes();
+			var pixels = new hxd.Pixels(terrain.heightMapResolution.x, terrain.heightMapResolution.y, bytes, RGBA);
+			t.normalMapPixels = pixels;
+			if( !bakeHeightAndNormalInGeometry ) {
+				t.refreshNormalMap();
+				t.normalMap.uploadPixels(pixels);
+				t.needNormalBake = false;
+			}
+		}
+
+		// INDEX
+		for( id in indexData ) {
+			var t = terrain.getTile(id.x, id.y);
+			if( t.surfaceIndexMap == null )
+				@:privateAccess t.refreshIndexMap();
+			if( id.ext == "png" ) { // Retro-compatibility
+				var indexAsPNG = id.res.toTexture();
+				h3d.pass.Copy.run(indexAsPNG, t.surfaceIndexMap);
+				t.indexMapPixels = t.surfaceIndexMap.capturePixels();
+				indexAsPNG.dispose();
+			}
+			else {
+				var pixels : hxd.Pixels = new hxd.Pixels(terrain.weightMapResolution.x, terrain.weightMapResolution.y, id.res.entry.getBytes(), RGBA);
+				t.indexMapPixels = pixels;
+				t.surfaceIndexMap.uploadPixels(pixels);
+			}
+		}
+
+		// WEIGHT
+		for( wd in weightData ) {
+			var t = terrain.getTile(wd.x, wd.y);
+			var pixels : hxd.Pixels = new hxd.Pixels(terrain.weightMapResolution.x, terrain.weightMapResolution.y, wd.res.entry.getBytes(), RGBA);
+			tmpPackedWeightTexture.uploadPixels(pixels);
+			t.packedWeightMapPixel = pixels;
+			
+			// Notice that we need the surfaceIndexMap loaded before doing the unpacking
+			var engine = h3d.Engine.getCurrent();
+			#if editor
+			// Unpack weight from RGBA texture into a array of texture of R8, and create the TextureArray
+			if( t.surfaceWeights.length == 0 )
+				@:privateAccess t.refreshSurfaceWeightArray();
+			for( i in 0 ... t.surfaceWeights.length ) {
+				engine.pushTarget(t.surfaceWeights[i]);
+				unpackWeight.shader.indexMap = t.surfaceIndexMap;
+				unpackWeight.shader.packedWeightTexture = tmpPackedWeightTexture;
+				unpackWeight.shader.index = i;
+				unpackWeight.render();
+				engine.popTarget();
+			}
+			t.generateWeightTextureArray();
+			#else
+			// Unpack weight from RGBA texture directly into the TextureArray of R8
+			t.generateWeightTextureArray();
+			for( i in 0 ... terrain.surfaceArray.surfaceCount ) {
+				engine.pushTarget(t.surfaceWeightArray, i);
+				unpackWeight.shader.indexMap = t.surfaceIndexMap;
+				unpackWeight.shader.packedWeightTexture = tmpPackedWeightTexture;
+				unpackWeight.shader.index = i;
+				unpackWeight.render();
+				engine.popTarget();
+			}
 			#end
 			#end
+		}
 
 
-			switch( type ) {
-				case "n":
-				#if !editor
-				var bytes = res.entry.getBytes();
-				tile.createBigPrim(bytes);
-				#end
-				case "h":
-				if( height ) {
-					var bytes = res.entry.getBytes();
-					var pixels : hxd.Pixels.PixelsFloat = new hxd.Pixels(terrain.heightMapResolution.x + 1, terrain.heightMapResolution.y + 1, bytes, RGBA32F);
-					@:privateAccess tile.heightmapPixels = pixels;
-					#if editor
-					// Need heightmap texture for editing
-					@:privateAccess tile.refreshHeightMap();
-					tile.heightMap.uploadPixels(pixels);
-					tile.needNewPixelCapture = false;
-					tile.refreshGrid();
-					#end
-				}
-				case "w":
-				if( weight ) {
-					if( ext == "png" ) { // Retro-compatibility
-						var weightAsPNG = res.toTexture();
-						h3d.pass.Copy.run(weightAsPNG, tmpPackedWeightTexture);
-						tile.packedWeightMapPixel = tmpPackedWeightTexture.capturePixels();
-						weightAsPNG.dispose();
-					} else {
-						var pixels : hxd.Pixels = new hxd.Pixels(terrain.weightMapResolution.x, terrain.weightMapResolution.y, res.entry.getBytes(), RGBA);
-						tmpPackedWeightTexture.uploadPixels(pixels);
-						tile.packedWeightMapPixel = pixels;
-					}
-
-					// Notice that we need the surfaceIndexMap loaded before doing the unpacking
-					var engine = h3d.Engine.getCurrent();
-					#if editor
-					// Unpack weight from RGBA texture into a array of texture of R8, and create the TextureArray
-					if( tile.surfaceWeights.length == 0 )
-						@:privateAccess tile.refreshSurfaceWeightArray();
-					for( i in 0 ... tile.surfaceWeights.length ) {
-						engine.pushTarget(tile.surfaceWeights[i]);
-						unpackWeight.shader.indexMap = tile.surfaceIndexMap;
-						unpackWeight.shader.packedWeightTexture = tmpPackedWeightTexture;
-						unpackWeight.shader.index = i;
-						unpackWeight.render();
-						engine.popTarget();
-					}
-					tile.generateWeightTextureArray();
-					#else
-					// Unpack weight from RGBA texture directly into the TextureArray of R8
-					tile.generateWeightTextureArray();
-					for( i in 0 ... terrain.surfaceArray.surfaceCount ) {
-						engine.pushTarget(tile.surfaceWeightArray, i);
-						unpackWeight.shader.indexMap = tile.surfaceIndexMap;
-						unpackWeight.shader.packedWeightTexture = tmpPackedWeightTexture;
-						unpackWeight.shader.index = i;
-						unpackWeight.render();
-						engine.popTarget();
-					}
-					#end
-				}
-				case"i":
-				if( index ) {
-					if( tile.surfaceIndexMap == null ) @:privateAccess tile.refreshIndexMap();
-					if( ext == "png" ) { // Retro-compatibility
-						var indexAsPNG = res.toTexture();
-						h3d.pass.Copy.run(indexAsPNG, tile.surfaceIndexMap);
-						tile.indexMapPixels = tile.surfaceIndexMap.capturePixels();
-						indexAsPNG.dispose();
-					}
-					else {
-						var pixels : hxd.Pixels = new hxd.Pixels(terrain.weightMapResolution.x, terrain.weightMapResolution.y, res.entry.getBytes(), RGBA);
-						tile.indexMapPixels = pixels;
-						tile.surfaceIndexMap.uploadPixels(pixels);
-					}
-				}
+		// HEIGHT
+		for( hd in heightData ) {
+			var t = terrain.getTile(hd.x, hd.y);
+			var bytes = hd.res.entry.getBytes();
+			var pixels : hxd.Pixels.PixelsFloat = new hxd.Pixels(terrain.heightMapResolution.x, terrain.heightMapResolution.y, bytes, R32F);
+			t.heightMapPixels = pixels;
+
+			if( !bakeHeightAndNormalInGeometry ) {
+				// Need heightmap texture for editing
+				t.refreshHeightMap();
+				t.heightMap.uploadPixels(pixels);
+				t.needNewPixelCapture = false;
+			}
+		}
+
+		// BAKE HEIGHT & NORMAL
+		if( bakeHeightAndNormalInGeometry ) {
+			for( t in terrain.tiles ) {
+				t.createBigPrim();
 			}
 			}
-			tmpPackedWeightTexture.dispose();
 		}
 		}
 
 
 		#if editor
 		#if editor
@@ -265,11 +322,13 @@ class Terrain extends Object3D {
 				continue;
 				continue;
 			}
 			}
 			if( t.heightMap == null ) trace("Missing heightmap for tile" + terrain.tiles.indexOf(t));
 			if( t.heightMap == null ) trace("Missing heightmap for tile" + terrain.tiles.indexOf(t));
+			if( t.normalMap == null ) trace("Missing normalmap for tile" + terrain.tiles.indexOf(t));
 			if( t.surfaceIndexMap == null ) trace("Missing surfaceIndexMap for tile" + terrain.tiles.indexOf(t));
 			if( t.surfaceIndexMap == null ) trace("Missing surfaceIndexMap for tile" + terrain.tiles.indexOf(t));
 			if( t.surfaceWeightArray == null ) trace("Missing surfaceWeightArray for tile" + terrain.tiles.indexOf(t));
 			if( t.surfaceWeightArray == null ) trace("Missing surfaceWeightArray for tile" + terrain.tiles.indexOf(t));
 		}
 		}
 		#end
 		#end
 
 
+		tmpPackedWeightTexture.dispose();
 		@:privateAccess hxd.res.Image.ENABLE_AUTO_WATCH = prevWatch;
 		@:privateAccess hxd.res.Image.ENABLE_AUTO_WATCH = prevWatch;
 	}
 	}
 
 
@@ -307,69 +366,36 @@ class Terrain extends Object3D {
 		}
 		}
 	}
 	}
 
 
-	public function initTerrain( ctx : Context, height = true, surface = true ) {
+	public function initTerrain( ctx : Context ) {
+
+		terrain.createBigPrimitive();
 
 
 		// Fix terrain being reloaded after a scene modification
 		// Fix terrain being reloaded after a scene modification
 		if( terrain.surfaceArray != null )
 		if( terrain.surfaceArray != null )
 			return;
 			return;
 
 
-		//#if editor
-		if( surface ) {
-			var initDone = false;
-			function waitAll() {
+		#if editor
+		var shared : hide.prefab.ContextShared = cast myContext.shared;
+		@:privateAccess shared.scene.setCurrent();
+		#end
 
 
-				if( initDone )
-					return;
+		var initDone = false;
+		function waitAll() {
 
 
-				for( surface in terrain.surfaces ) {
-					if( surface == null || surface.albedo == null || surface.normal == null || surface.pbr == null )
-						return;
-				}
-				terrain.generateSurfaceArray();
+			if( initDone )
+				return;
 
 
-				loadTiles(ctx, height, surface, surface);
+			for( surface in terrain.surfaces ) {
+				if( surface == null || surface.albedo == null || surface.normal == null || surface.pbr == null )
+					return;
+			}
+			terrain.generateSurfaceArray();
 
 
-				#if editor
-				for( t in terrain.tiles )
-					t.computeEdgesNormals();
-				#end
+			loadTiles(ctx);
 
 
-				initDone = true;
-			}
-			loadSurfaces(ctx, waitAll);
-		}
-		else {
-			loadTiles(ctx, height, surface, surface);
-			for( t in terrain.tiles )
-				t.computeEdgesNormals();
-		}
-		//#else
-		//loadBinary(ctx);
-		//#end
-
-		// Backward Compatibility
-		if( needFormatUpdate ) {
-			for( s in terrain.surfaces )
-				s.tilling /= tileSizeX;
-			terrain.updateSurfaceParams();
-			/* // Disable auto-upgrade for now
-			#if editor
-			// Need to create a terrain with the new params before saving
-			terrain.weightMapResolution = new h2d.col.IPoint(Math.round(tileSizeX * weightMapPixelPerMeter), Math.round(tileSizeY * weightMapPixelPerMeter));
-			terrain.cellCount = new h2d.col.IPoint(Math.ceil(tileSizeX * vertexPerMeter), Math.ceil(tileSizeY * vertexPerMeter) );
-			terrain.cellSize = new h2d.col.Point(tileSizeX / terrain.cellCount.x, tileSizeY / terrain.cellCount.y );
-			terrain.heightMapResolution = new h2d.col.IPoint(terrain.cellCount.x + 1, terrain.cellCount.y + 1);
-			terrain.refreshAllGrids();
-			terrain.refreshAllTex();
-			for( tile in terrain.tiles )
-				tile.blendEdges();
-			modified = true;
-			var shared : hide.prefab.ContextShared = cast myContext.shared;
-			@:privateAccess shared.scene.editor.view.save();
-			trace("Terrain : " + name +  " is now up to date.");
-			#end
-			*/
+			initDone = true;
 		}
 		}
+		loadSurfaces(ctx, waitAll);
 	}
 	}
 
 
 	#if editor
 	#if editor
@@ -384,7 +410,7 @@ class Terrain extends Object3D {
 		clearSavedTextures(ctx);
 		clearSavedTextures(ctx);
 		saveWeightTextures(ctx);
 		saveWeightTextures(ctx);
 		saveHeightTextures(ctx);
 		saveHeightTextures(ctx);
-		saveNormals(ctx);
+		saveNormalTextures(ctx);
 		return;
 		return;
 	}
 	}
 
 
@@ -451,30 +477,17 @@ class Terrain extends Object3D {
 
 
 	public function saveHeightTextures( ctx : Context ) {
 	public function saveHeightTextures( ctx : Context ) {
 		for( tile in terrain.tiles ) {
 		for( tile in terrain.tiles ) {
-			var pixels : PixelsFloat = tile.heightMap.capturePixels();
+			var pixels : hxd.Pixels.PixelsFloat = tile.heightMap.capturePixels();
 			var fileName = tile.tileX + "_" + tile.tileY + "_" + "h";
 			var fileName = tile.tileX + "_" + tile.tileY + "_" + "h";
 			ctx.shared.savePrefabDat(fileName, "bin", name, pixels.bytes);
 			ctx.shared.savePrefabDat(fileName, "bin", name, pixels.bytes);
 		}
 		}
 	}
 	}
 
 
-	public function saveNormals( ctx : Context ) {
+	public function saveNormalTextures( ctx : Context ) {
 		for( tile in terrain.tiles ) {
 		for( tile in terrain.tiles ) {
-			if( tile.grid == null || tile.grid.normals == null || tile.grid.tangents == null ) continue;
-			var normals = tile.grid.normals;
-			var tangents = tile.grid.tangents;
+			var pixels : hxd.Pixels = tile.normalMap.capturePixels();
 			var fileName = tile.tileX + "_" + tile.tileY + "_" + "n";
 			var fileName = tile.tileX + "_" + tile.tileY + "_" + "n";
-			var stride = 3 * 4 + 3 * 4; // Normal + Tangent
-			var vertexCount = normals.length;
-			var bytes = haxe.io.Bytes.alloc(vertexCount * stride);
-			for( i in 0 ... normals.length ) {
-				bytes.setFloat(i*stride, normals[i].x);
-				bytes.setFloat(i*stride+4, normals[i].y);
-				bytes.setFloat(i*stride+8, normals[i].z);
-				bytes.setFloat(i*stride+12, tangents[i].x);
-				bytes.setFloat(i*stride+16, tangents[i].y);
-				bytes.setFloat(i*stride+20, tangents[i].z);
-			}
-			ctx.shared.savePrefabDat(fileName, "bin", name, bytes);
+			ctx.shared.savePrefabDat(fileName, "bin", name, pixels.bytes);
 		}
 		}
 	}
 	}
 
 
@@ -499,39 +512,22 @@ class Terrain extends Object3D {
 
 
 	#end
 	#end
 
 
+	function createTerrain( ctx : Context ) {
+		return new TerrainMesh(ctx.local3d);
+	}
+
 	override function makeInstance( ctx : Context ) : Context {
 	override function makeInstance( ctx : Context ) : Context {
 		ctx = ctx.clone(this);
 		ctx = ctx.clone(this);
 		#if editor
 		#if editor
 		myContext = ctx;
 		myContext = ctx;
 		#end
 		#end
 
 
-		terrain = new TerrainMesh(ctx.local3d);
+		terrain = createTerrain(ctx);
 		terrain.tileSize = new h2d.col.Point(tileSizeX, tileSizeY);
 		terrain.tileSize = new h2d.col.Point(tileSizeX, tileSizeY);
-
-		// Backward Compatibility
-		if( oldHeightMapResolution != -1 && oldCellSize != -1 ) {
-			terrain.heightMapResolution = new h2d.col.IPoint(oldHeightMapResolution, oldHeightMapResolution);
-			var resolution = Math.max(0.1, oldCellSize);
-			var cellCount = Math.ceil(Math.min(1000, tileSizeX / resolution));
-			var finalCellSize = tileSizeX / cellCount;
-			terrain.cellCount = new h2d.col.IPoint(cellCount, cellCount);
-			terrain.cellSize = new h2d.col.Point(finalCellSize, finalCellSize);
-			vertexPerMeter = terrain.cellCount.x / tileSizeX;
-			needFormatUpdate = true;
-		}
-		else {
-			terrain.cellCount = new h2d.col.IPoint(Math.ceil(tileSizeX * vertexPerMeter), Math.ceil(tileSizeY * vertexPerMeter) );
-			terrain.cellSize = new h2d.col.Point(tileSizeX / terrain.cellCount.x, tileSizeY / terrain.cellCount.y );
-			terrain.heightMapResolution = new h2d.col.IPoint(terrain.cellCount.x + 1, terrain.cellCount.y + 1);
-		}
-		if( oldWeightMapResolution != -1 ) {
-			terrain.weightMapResolution = new h2d.col.IPoint(oldWeightMapResolution, oldWeightMapResolution);
-			weightMapPixelPerMeter = oldWeightMapResolution / tileSizeX;
-			needFormatUpdate = true;
-		}
-		else
-			terrain.weightMapResolution = new h2d.col.IPoint(Math.round(tileSizeX * weightMapPixelPerMeter), Math.round(tileSizeY * weightMapPixelPerMeter));
-
+		terrain.cellCount = new h2d.col.IPoint(Math.ceil(tileSizeX * vertexPerMeter), Math.ceil(tileSizeY * vertexPerMeter) );
+		terrain.cellSize = new h2d.col.Point(tileSizeX / terrain.cellCount.x, tileSizeY / terrain.cellCount.y );
+		terrain.heightMapResolution = new h2d.col.IPoint(terrain.cellCount.x + 1, terrain.cellCount.y + 1);
+		terrain.weightMapResolution = new h2d.col.IPoint(Math.round(tileSizeX * weightMapPixelPerMeter), Math.round(tileSizeY * weightMapPixelPerMeter));
 		terrain.parallaxAmount = parallaxAmount;
 		terrain.parallaxAmount = parallaxAmount;
 		terrain.parallaxMinStep = parallaxMinStep;
 		terrain.parallaxMinStep = parallaxMinStep;
 		terrain.parallaxMaxStep = parallaxMaxStep;
 		terrain.parallaxMaxStep = parallaxMaxStep;
@@ -640,10 +636,10 @@ class Terrain extends Object3D {
 			terrain.heightMapResolution = new h2d.col.IPoint(terrain.cellCount.x + 1, terrain.cellCount.y + 1);
 			terrain.heightMapResolution = new h2d.col.IPoint(terrain.cellCount.x + 1, terrain.cellCount.y + 1);
 			terrain.refreshAllGrids();
 			terrain.refreshAllGrids();
 			terrain.refreshAllTex();
 			terrain.refreshAllTex();
-			for( tile in terrain.tiles )
-				tile.blendEdges();
-			if( editor != null )
+			if( editor != null ) {
 				editor.refresh();
 				editor.refresh();
+				@:privateAccess editor.blendEdges(terrain.tiles);
+			}
 			modified = true;
 			modified = true;
 		});
 		});
 
 

+ 66 - 24
hrt/prefab/terrain/TerrainMesh.hx

@@ -1,8 +1,12 @@
 package hrt.prefab.terrain;
 package hrt.prefab.terrain;
 
 
+import h3d.mat.Texture;
+
 @:access(hrt.prefab.terrain.Tile)
 @:access(hrt.prefab.terrain.Tile)
 class TerrainMesh extends h3d.scene.Object {
 class TerrainMesh extends h3d.scene.Object {
 
 
+	var primitive : h3d.prim.BigPrimitive;
+
 	// Resolution Vertexes/Pixels
 	// Resolution Vertexes/Pixels
 	public var tileSize : h2d.col.Point;
 	public var tileSize : h2d.col.Point;
 	public var cellSize : h2d.col.Point;
 	public var cellSize : h2d.col.Point;
@@ -29,11 +33,9 @@ class TerrainMesh extends h3d.scene.Object {
 	var tiles : Array<Tile> = [];
 	var tiles : Array<Tile> = [];
 	var surfaces : Array<Surface> = [];
 	var surfaces : Array<Surface> = [];
 	var surfaceArray : Surface.SurfaceArray;
 	var surfaceArray : Surface.SurfaceArray;
-	var copyPass : h3d.pass.Copy;
 
 
 	public function new(?parent){
 	public function new(?parent){
 		super(parent);
 		super(parent);
-		copyPass = new h3d.pass.Copy();
 	}
 	}
 
 
 	override function onRemove() {
 	override function onRemove() {
@@ -42,15 +44,24 @@ class TerrainMesh extends h3d.scene.Object {
 			surfaceArray.dispose();
 			surfaceArray.dispose();
 	}
 	}
 
 
-	public function getHeight( x : Float, y : Float, fast = false) : Float {
-		var z = 0.0;
+	public function getLocalHeight( x : Float, y : Float, fast = false) : Null<Float> {
+		var xi = hxd.Math.floor(x/tileSize.x);
+		var yi = hxd.Math.floor(y/tileSize.y);
+		var t = getTile(xi, yi);
+		if( t != null ) {
+			return t.getHeight((x - xi * tileSize.x) / tileSize.x, (y - yi * tileSize.y) / tileSize.y, fast);
+		}
+		return null;
+	}
+
+	public function getHeight( x : Float, y : Float, fast = false) : Null<Float> {
 		var t = getTileAtWorldPos(x, y);
 		var t = getTileAtWorldPos(x, y);
 		if( t != null ) {
 		if( t != null ) {
 			tmpVec.set(x, y);
 			tmpVec.set(x, y);
 			var pos = t.globalToLocal(tmpVec);
 			var pos = t.globalToLocal(tmpVec);
-			z = t.getHeight(pos.x / tileSize.x, pos.y / tileSize.y, fast);
+			return t.getHeight(pos.x / tileSize.x, pos.y / tileSize.y, fast);
 		}
 		}
-		return z;
+		return null;
 	}
 	}
 
 
 	public function getSurface( i : Int ) : Surface {
 	public function getSurface( i : Int ) : Surface {
@@ -89,23 +100,23 @@ class TerrainMesh extends h3d.scene.Object {
 		if(surfaceArray != null) surfaceArray.dispose();
 		if(surfaceArray != null) surfaceArray.dispose();
 		surfaceArray = new Surface.SurfaceArray(surfaces.length, surfaceSize);
 		surfaceArray = new Surface.SurfaceArray(surfaces.length, surfaceSize);
 		for( i in 0 ... surfaces.length ) {
 		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);
+			if( surfaces[i].albedo != null ) h3d.pass.Copy.run(surfaces[i].albedo, surfaceArray.albedo, null, null, i);
+			if( surfaces[i].normal != null ) h3d.pass.Copy.run(surfaces[i].normal, surfaceArray.normal, null, null, i);
+			if( surfaces[i].pbr != null ) h3d.pass.Copy.run(surfaces[i].pbr, surfaceArray.pbr, null, null, i);
 		}
 		}
 
 
 		// OnContextLost support
 		// OnContextLost support
 		surfaceArray.albedo.realloc = function() {
 		surfaceArray.albedo.realloc = function() {
 			for( i in 0 ... surfaceArray.surfaceCount )
 			for( i in 0 ... surfaceArray.surfaceCount )
-				copyPass.apply(surfaces[i].albedo, surfaceArray.albedo, null, null, i);
+				h3d.pass.Copy.run(surfaces[i].albedo, surfaceArray.albedo, null, null, i);
 		}
 		}
 		surfaceArray.normal.realloc = function() {
 		surfaceArray.normal.realloc = function() {
 			for( i in 0 ... surfaceArray.surfaceCount )
 			for( i in 0 ... surfaceArray.surfaceCount )
-				copyPass.apply(surfaces[i].normal, surfaceArray.normal, null, null, i);
+				h3d.pass.Copy.run(surfaces[i].normal, surfaceArray.normal, null, null, i);
 		}
 		}
 		surfaceArray.pbr.realloc = function() {
 		surfaceArray.pbr.realloc = function() {
 			for( i in 0 ... surfaceArray.surfaceCount )
 			for( i in 0 ... surfaceArray.surfaceCount )
-				copyPass.apply(surfaces[i].pbr, surfaceArray.pbr, null, null, i);
+				h3d.pass.Copy.run(surfaces[i].pbr, surfaceArray.pbr, null, null, i);
 		}
 		}
 
 
 		updateSurfaceParams();
 		updateSurfaceParams();
@@ -119,16 +130,50 @@ class TerrainMesh extends h3d.scene.Object {
 		}
 		}
 	}
 	}
 
 
-	public function refreshAllGrids() {
-		for( tile in tiles ) {
-			tile.x = tile.tileX * tileSize.x;
-			tile.y = tile.tileY * tileSize.y;
-			tile.refreshGrid();
+	function createBigPrimitive() {
+
+		if( primitive != null )
+			primitive.dispose();
+
+		primitive = new h3d.prim.BigPrimitive(3, true);
+
+		inline function addVertice(x : Float, y : Float, i : Int) {
+			primitive.addPoint(x, y, 0);
 		}
 		}
-		for( tile in tiles )
-			tile.blendEdges();
-		for( tile in tiles )
-			tile.computeTangents();
+
+		primitive.begin(0,0);
+		for( y in 0 ... cellCount.y + 1 ) {
+			for( x in 0 ... cellCount.x + 1 ) {
+				addVertice(x * cellSize.x, y * cellSize.y, x + y * (cellCount.x + 1));
+			}
+		}
+
+		for( y in 0 ... cellCount.y ) {
+			for( x in 0 ... cellCount.x ) {
+				var i = x + y * (cellCount.x + 1);
+				if( i % 2 == 0 ) {
+					primitive.addIndex(i);
+					primitive.addIndex(i + 1);
+					primitive.addIndex(i + cellCount.x + 2);
+					primitive.addIndex(i);
+					primitive.addIndex(i + cellCount.x + 2);
+					primitive.addIndex(i + cellCount.x + 1);
+				}
+				else {
+					primitive.addIndex(i + cellCount.x + 1);
+					primitive.addIndex(i);
+					primitive.addIndex(i + 1);
+					primitive.addIndex(i + 1);
+					primitive.addIndex(i + cellCount.x + 2);
+					primitive.addIndex(i + cellCount.x + 1);
+				}
+			}
+		}
+		primitive.flush();
+	}
+
+	public function refreshAllGrids() {
+		createBigPrimitive();
 	}
 	}
 
 
 	public function refreshAllTex() {
 	public function refreshAllTex() {
@@ -145,9 +190,6 @@ class TerrainMesh extends h3d.scene.Object {
 		var tile = getTile(x,y);
 		var tile = getTile(x,y);
 		if(tile == null){
 		if(tile == null){
 			tile = new Tile(x, y, this);
 			tile = new Tile(x, y, this);
-			#if editor
-			tile.refreshGrid();
-			#end
 			if( createTexture ) tile.refreshTex();
 			if( createTexture ) tile.refreshTex();
 			tiles.push(tile);
 			tiles.push(tile);
 		}
 		}

+ 178 - 370
hrt/prefab/terrain/Tile.hx

@@ -1,123 +1,159 @@
 package hrt.prefab.terrain;
 package hrt.prefab.terrain;
 
 
-import h3d.prim.Grid;
+class NormalBake extends h3d.shader.ScreenShader {
+
+	static var SRC = {
+
+		@param var heightMap : Sampler2D;
+		@param var heightMapSize : Vec2;
+
+		function fragment() {
+			var pixelSize = 1.0 / heightMapSize;
+			var h = heightMap.get(calculatedUV).r;
+			var h1 = heightMap.get(calculatedUV + vec2(0, pixelSize.y)).r;
+			var h2 = heightMap.get(calculatedUV + vec2(pixelSize.x, 0)).r;
+			var h3 = heightMap.get(calculatedUV + vec2(0, -pixelSize.y)).r;
+			var h4 = heightMap.get(calculatedUV + vec2(-pixelSize.x, 0)).r;
+			var v1 = vec3( 0, 1, h1 - h);
+			var v2 = vec3( 1, 0, h2 - h);
+			var v3 = vec3( 0, -1, h3 - h);
+			var v4 = vec3( -1, 0, h4 - h);
+			var n = (cross(v1, v2) + cross(v2, v3) + cross(v3, v4) + cross(v4, v1)) / -4;
+			n = n.normalize();
+			pixelColor.rgb = n * 0.5 + 0.5;
+		}
+	};
+}
 
 
-enum Direction{
+enum Direction {
 	Up; Down; Left; Right; UpLeft; UpRight; DownLeft; DownRight;
 	Up; Down; Left; Right; UpLeft; UpRight; DownLeft; DownRight;
 }
 }
 
 
 @:access(hrt.prefab.terrain.TerrainMesh)
 @:access(hrt.prefab.terrain.TerrainMesh)
 class Tile extends h3d.scene.Mesh {
 class Tile extends h3d.scene.Mesh {
 
 
+	var shader : hrt.shader.Terrain;
+	var terrain : TerrainMesh;
+
+	// INDEXES
 	public var tileX (default, null) : Int;
 	public var tileX (default, null) : Int;
 	public var tileY (default, null) : Int;
 	public var tileY (default, null) : Int;
-	public var heightMap(default, set) : h3d.mat.Texture;
+
+	// TEXTURE & PIXEL
+	public var heightMap : h3d.mat.Texture;
+	public var heightMapPixels(get, default) : hxd.Pixels.PixelsFloat;
+
+	public var normalMap(default, null) : h3d.mat.Texture;
+	public var normalMapPixels(get, default) : hxd.Pixels.Pixels;
+	var needNormalBake = true;
+
 	public var surfaceIndexMap : h3d.mat.Texture;
 	public var surfaceIndexMap : h3d.mat.Texture;
 	public var surfaceWeights : Array<h3d.mat.Texture> = [];
 	public var surfaceWeights : Array<h3d.mat.Texture> = [];
 	public var surfaceWeightArray (default, null) : h3d.mat.TextureArray;
 	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 needNewPixelCapture = false;
+
+	// PRIMITIVE
+	var bigPrim : h3d.prim.BigPrimitive;
 	public var insideFrustrum(default, null) = false;
 	public var insideFrustrum(default, null) = false;
 
 
-	// set by prefab loader for CPU access ingame
+	// Set by prefab loader for CPU access ingame
 	public var packedWeightMapPixel : hxd.Pixels;
 	public var packedWeightMapPixel : hxd.Pixels;
 	public var indexMapPixels : hxd.Pixels;
 	public var indexMapPixels : hxd.Pixels;
 	public var normalTangentBytes : haxe.io.Bytes;
 	public var normalTangentBytes : haxe.io.Bytes;
 
 
-	var heightmapPixels : hxd.Pixels.PixelsFloat;
-	var shader : hrt.shader.Terrain;
-	var terrain : TerrainMesh;
-	var bigPrim : h3d.prim.BigPrimitive;
-
 	public function new( x : Int, y : Int, parent : TerrainMesh) {
 	public function new( x : Int, y : Int, parent : TerrainMesh) {
 		super(null, null, parent);
 		super(null, null, parent);
 		terrain = parent;
 		terrain = parent;
-		this.tileX = x;
-		this.tileY = y;
+		tileX = x;
+		tileY = y;
 		shader = new hrt.shader.Terrain();
 		shader = new hrt.shader.Terrain();
 		material.mainPass.addShader(shader);
 		material.mainPass.addShader(shader);
 		material.mainPass.culling = Back;
 		material.mainPass.culling = Back;
+		material.mainPass.setPassName("terrain");
+		material.mainPass.stencil = new h3d.mat.Stencil();
+		material.mainPass.stencil.setFunc(Always, 0x01, 0xFF, 0xFF);
+		material.mainPass.stencil.setOp(Keep, Keep, Replace);
 		this.x = x * terrain.tileSize.x;
 		this.x = x * terrain.tileSize.x;
 		this.y = y * terrain.tileSize.y;
 		this.y = y * terrain.tileSize.y;
 		name = "tile_" + x + "_" + y;
 		name = "tile_" + x + "_" + y;
-		material.mainPass.setPassName("terrain");
 	}
 	}
 
 
 	override function onRemove() {
 	override function onRemove() {
 		super.onRemove();
 		super.onRemove();
-		if( heightMap != null )
-			heightMap.dispose();
-		if( surfaceIndexMap != null )
-			surfaceIndexMap.dispose();
+
+		inline function disposeTex( t : h3d.mat.Texture ) { if( t != null ) { t.dispose();  t = null; } }
+		inline function disposePixels( p : hxd.Pixels ) { if( p != null ) { p.dispose(); p = null; } }
+
+		disposeTex(heightMap);
+		disposeTex(normalMap);
+		disposeTex(surfaceIndexMap);
+		disposeTex(surfaceWeightArray);
 		for( i in 0 ... surfaceWeights.length )
 		for( i in 0 ... surfaceWeights.length )
-			if( surfaceWeights[i] != null ) surfaceWeights[i].dispose();
-		if( surfaceWeightArray != null )
-			surfaceWeightArray.dispose();
-		if( bigPrim != null )
-			bigPrim.dispose();
-		if(packedWeightMapPixel != null) {
-			packedWeightMapPixel.dispose();
-			packedWeightMapPixel = null;
-		}
-		if(indexMapPixels != null) {
-			indexMapPixels.dispose();
-			indexMapPixels = null;
-		}
-		if(heightmapPixels != null) {
-			heightmapPixels.dispose();
-			heightmapPixels = null;
-		}
+			disposeTex(surfaceWeights[i]);
+
+		disposePixels(packedWeightMapPixel);
+		disposePixels(indexMapPixels);
+		disposePixels(heightMapPixels);
+
 		normalTangentBytes = null;
 		normalTangentBytes = null;
+		if( bigPrim != null ) bigPrim.dispose();
+	}
 
 
+	public inline function get_heightMapPixels() {
+		if( (needNewPixelCapture || heightMapPixels == null) && heightMap != null )
+			heightMapPixels = heightMap.capturePixels();
+		needNewPixelCapture = false;
+		return heightMapPixels;
 	}
 	}
 
 
-	function set_heightMap( v ) {
-		shader.heightMap = v;
-		return heightMap = v;
+	public inline function get_normalMapPixels() {
+		if( normalMapPixels == null && normalMap != null )
+			normalMapPixels = normalMap.capturePixels();
+		return normalMapPixels;
 	}
 	}
 
 
-	public function getHeightPixels() {
-		if( needNewPixelCapture || heightmapPixels == null && heightMap != null ) {
-			heightmapPixels = heightMap.capturePixels();
+	function bakeNormal() {
+		if( heightMap == null )
+			throw "Can't bake the normalMap of the tile if the heightMap is null.";
+		var s = new NormalBake();
+		s.heightMap = heightMap;
+		s.heightMapSize.set(heightMap.width, heightMap.height);
+		h3d.pass.ScreenFx.run(s, normalMap);
+		needNormalBake = false;
+		if( normalMapPixels != null ) {
+			normalMapPixels.dispose();
+			normalMapPixels = null;
 		}
 		}
-		needNewPixelCapture = false;
-		return heightmapPixels;
 	}
 	}
 
 
-	public function createBigPrim( bytes : haxe.io.Bytes ) {
+	public function createBigPrim() {
 
 
-		var stride = 3 * 4 + 3 * 4; // Normal + Tangent
-		var vertexCount = (terrain.cellCount.x + 1) * (terrain.cellCount.y + 1);
-		if( bytes.length != stride * vertexCount ) {
-			throw "Bytes length doesn't match with the size of tiles";
+		if( normalMapPixels == null || heightMapPixels == null )
 			return;
 			return;
-		}
 
 
 		if( bigPrim != null )
 		if( bigPrim != null )
 			bigPrim.dispose();
 			bigPrim.dispose();
 
 
-		normalTangentBytes = bytes;
-		bigPrim = new h3d.prim.BigPrimitive(9, true);
-		inline function addVertice(x : Float, y : Float, i : Int) {
+		bigPrim = new h3d.prim.BigPrimitive(6, true);
+
+		var cellCount = terrain.cellCount;
+		var cellSize = terrain.cellSize;
+		inline function addVertice(x : Int, y : Int) {
 			// Pos
 			// Pos
-			bigPrim.addPoint(x, y, getHeight(x / terrain.tileSize.x, y / terrain.tileSize.y, true)); // Use addPoint() instead of addVertexValue() for the bounds
+			bigPrim.addPoint(x * cellSize.x, y * cellSize.y, getHeight((x * cellSize.x) / terrain.tileSize.x, (y * cellSize.y) / terrain.tileSize.y, true)); // Use addPoint() instead of addVertexValue() for the bounds
 			// Normal
 			// Normal
-			bigPrim.addVertexValue(bytes.getFloat(i * stride));
-			bigPrim.addVertexValue(bytes.getFloat(i * stride + 4));
-			bigPrim.addVertexValue(bytes.getFloat(i * stride + 8));
-			// Tangents
-			bigPrim.addVertexValue(bytes.getFloat(i * stride + 12));
-			bigPrim.addVertexValue(bytes.getFloat(i * stride + 16));
-			bigPrim.addVertexValue(bytes.getFloat(i * stride + 20));
+			var n = h3d.Vector.fromColor(normalMapPixels.getPixel(x, y));
+			n = n.add(new h3d.Vector(-0.5, -0.5, -0.5));
+			n.scale3(2.0);
+			bigPrim.addVertexValue(n.x);
+			bigPrim.addVertexValue(n.y);
+			bigPrim.addVertexValue(n.z);
 		}
 		}
 
 
-		var cellCount = terrain.cellCount;
-		var cellSize = terrain.cellSize;
 		bigPrim.begin(0,0);
 		bigPrim.begin(0,0);
 		for( y in 0 ... cellCount.y + 1 ) {
 		for( y in 0 ... cellCount.y + 1 ) {
 			for( x in 0 ... cellCount.x + 1 ) {
 			for( x in 0 ... cellCount.x + 1 ) {
-				addVertice(x * cellSize.x, y * cellSize.y, x + y * (terrain.cellCount.x + 1));
+				addVertice(x, y);
 			}
 			}
 		}
 		}
 
 
@@ -133,70 +169,56 @@ class Tile extends h3d.scene.Mesh {
 			}
 			}
 		}
 		}
 		bigPrim.flush();
 		bigPrim.flush();
-		primitive = bigPrim;
 	}
 	}
 
 
-	public function refreshGrid() {
-		if( bigPrim != null )
-			return;
-		if( grid == null || grid.width != terrain.cellCount.x || grid.height != terrain.cellCount.y || grid.cellWidth != terrain.cellSize.x || grid.cellHeight != terrain.cellSize.y ) {
-			if( grid != null ) grid.dispose();
-		 	grid = new h3d.prim.Grid(terrain.cellCount.x, terrain.cellCount.y, terrain.cellSize.x, terrain.cellSize.y);
-			primitive = grid;
-		}
-		computeNormals();
-		computeTangents();
-	}
+	function refreshNormalMap() {
+		if( normalMap == null || normalMap.width != terrain.heightMapResolution.x || normalMap.height != terrain.heightMapResolution.y ) {
+			var oldNormalMap = normalMap;
+			normalMap = new h3d.mat.Texture(terrain.heightMapResolution.x, terrain.heightMapResolution.y, [Target], RGBA);
+			normalMap.setName("terrainNormalMap");
+			normalMap.wrap = Clamp;
+			normalMap.filter = Linear;
+			normalMap.preventAutoDispose();
+			normalMap.realloc = function() {
+				if( normalMapPixels != null )
+					normalMap.uploadPixels(normalMapPixels);
+				else
+					needNormalBake = true;
+			}
+			if( oldNormalMap != null )
+				oldNormalMap.dispose();
 
 
-	public function blendEdges() {
-		var adjTileX = terrain.getTile(tileX - 1, tileY);
-		if( adjTileX != null ) {
-			var flags = new haxe.EnumFlags<Direction>();
-        	flags.set(Left);
-			adjTileX.computeEdgesHeight(flags);
-		}
-		var adjTileY = terrain.getTile(tileX, tileY - 1);
-		if( adjTileY != null ) {
-			var flags = new haxe.EnumFlags<Direction>();
-        	flags.set(Up);
-			adjTileY.computeEdgesHeight(flags);
-		}
-		var adjTileXY = terrain.getTile(tileX - 1, tileY - 1);
-		if( adjTileXY != null ) {
-			var flags = new haxe.EnumFlags<Direction>();
-        	flags.set(UpLeft);
-			adjTileXY.computeEdgesHeight(flags);
+			if( normalMapPixels != null && (normalMapPixels.width != normalMap.width || normalMapPixels.height != normalMap.height) ) {
+				normalMapPixels.dispose();
+				normalMapPixels = null;
+			}
 		}
 		}
-		var flags = new haxe.EnumFlags<Direction>();
-        flags.set(Left);
-		flags.set(Up);
-		flags.set(UpLeft);
-
-		computeEdgesHeight(flags);
-		computeNormals();
-		computeEdgesNormals();
-		computeTangents();
 	}
 	}
 
 
 	function refreshHeightMap() {
 	function refreshHeightMap() {
-		if( heightMap == null || heightMap.width != terrain.heightMapResolution.x + 1 || heightMap.height != terrain.heightMapResolution.y + 1 ) {
+		if( heightMap == null || heightMap.width != terrain.heightMapResolution.x || heightMap.height != terrain.heightMapResolution.y ) {
 			var oldHeightMap = heightMap;
 			var oldHeightMap = heightMap;
-			heightMap = new h3d.mat.Texture(terrain.heightMapResolution.x + 1, terrain.heightMapResolution.y + 1, [Target], RGBA32F );
+			heightMap = new h3d.mat.Texture(terrain.heightMapResolution.x, terrain.heightMapResolution.y, [Target], R32F);
 			heightMap.setName("terrainHeightMap");
 			heightMap.setName("terrainHeightMap");
 			heightMap.wrap = Clamp;
 			heightMap.wrap = Clamp;
 			heightMap.filter = Linear;
 			heightMap.filter = Linear;
 			heightMap.preventAutoDispose();
 			heightMap.preventAutoDispose();
+			needNewPixelCapture = true;
 
 
 			heightMap.realloc = function() {
 			heightMap.realloc = function() {
-				if( heightmapPixels != null )
-					heightMap.uploadPixels(heightmapPixels);
+				if( heightMapPixels != null )
+					heightMap.uploadPixels(heightMapPixels);
 			}
 			}
 
 
 			if( oldHeightMap != null ) {
 			if( oldHeightMap != null ) {
-				terrain.copyPass.apply(oldHeightMap, heightMap);
+				h3d.pass.Copy.run(oldHeightMap, heightMap);
 				oldHeightMap.dispose();
 				oldHeightMap.dispose();
 			}
 			}
-			needNewPixelCapture = true;
+
+			if( heightMapPixels != null && (heightMapPixels.width != heightMap.width || heightMapPixels.height != heightMap.height) ) {
+				heightMapPixels.dispose();
+				heightMapPixels = null;
+			}
 		}
 		}
 	}
 	}
 
 
@@ -214,7 +236,7 @@ class Tile extends h3d.scene.Mesh {
 			}
 			}
 
 
 			if( oldSurfaceIndexMap != null ) {
 			if( oldSurfaceIndexMap != null ) {
-				terrain.copyPass.apply(oldSurfaceIndexMap, surfaceIndexMap);
+				h3d.pass.Copy.run(oldSurfaceIndexMap, surfaceIndexMap);
 				oldSurfaceIndexMap.dispose();
 				oldSurfaceIndexMap.dispose();
 			}
 			}
 		}
 		}
@@ -231,7 +253,7 @@ class Tile extends h3d.scene.Mesh {
 					surfaceWeights[i].wrap = Clamp;
 					surfaceWeights[i].wrap = Clamp;
 					surfaceWeights[i].preventAutoDispose();
 					surfaceWeights[i].preventAutoDispose();
 					if( i < oldArray.length && oldArray[i] != null )
 					if( i < oldArray.length && oldArray[i] != null )
-						terrain.copyPass.apply(oldArray[i], surfaceWeights[i]);
+						h3d.pass.Copy.run(oldArray[i], surfaceWeights[i]);
 				}
 				}
 				for( t in oldArray )
 				for( t in oldArray )
 					if( t != null)
 					if( t != null)
@@ -248,6 +270,7 @@ class Tile extends h3d.scene.Mesh {
 
 
 	public function refreshTex() {
 	public function refreshTex() {
 		refreshHeightMap();
 		refreshHeightMap();
+		refreshNormalMap();
 		refreshIndexMap();
 		refreshIndexMap();
 		refreshSurfaceWeightArray();
 		refreshSurfaceWeightArray();
 		generateWeightTextureArray();
 		generateWeightTextureArray();
@@ -278,249 +301,11 @@ class Tile extends h3d.scene.Mesh {
 			}
 			}
 		}
 		}
 		for( i in 0 ... surfaceWeights.length )
 		for( i in 0 ... surfaceWeights.length )
-			if( surfaceWeights[i] != null ) terrain.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 = terrain.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 = terrain.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 = terrain.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;
-
-		inline function isEdgeIndex( grid : Grid, i : Int, side : Int ) : Bool {
-			var v = grid.points[grid.idx[i]];
-			return switch( side ) {
-				case 0: v.x == grid.width; // Left
-				case 1: v.y == grid.height; // Up
-				case 2:	v.y == 0; // Down
-				case 3: v.x == 0; // Right
-				default: false;
-			}
-		}
-		// Need to recompute the normal before any blend
-		var t0 = new h3d.col.Point(); var t1 = new h3d.col.Point(); var t2 = new h3d.col.Point();
-		inline function computeNormal( tile : Tile, index : Int,  assignOnSide : Int = -1 ) : h3d.col.Point {
-			var grid = tile.grid;
-			t0.load(grid.points[grid.idx[index]]);
-			t1.load(grid.points[grid.idx[index+1]]);
-			t2.load(grid.points[grid.idx[index+2]]);
-			t0.z = tile.getHeight(t0.x / terrain.tileSize.x, t0.y / terrain.tileSize.y);
-			t1.z = tile.getHeight(t1.x / terrain.tileSize.x, t1.y / terrain.tileSize.y);
-			t2.z = tile.getHeight(t2.x / terrain.tileSize.x, t2.y / terrain.tileSize.y);
-			var n1 = t1.sub(t0).normalize();
-			var n2 = t2.sub(t0).normalize();
-			var n = n1.cross(n2).normalize();
-			if( isEdgeIndex(grid, index, assignOnSide) ) grid.normals[grid.idx[index]] = grid.normals[grid.idx[index]].add(n);
-			if( isEdgeIndex(grid, index+1, assignOnSide) ) grid.normals[grid.idx[index+1]] = grid.normals[grid.idx[index+1]].add(n);
-			if( isEdgeIndex(grid, index+2, assignOnSide) ) grid.normals[grid.idx[index+2]] = grid.normals[grid.idx[index+2]].add(n);
-			return n;
-		}
-
-		var widthVertexCount = grid.width + 1;
-		var heightVertexCount = grid.height + 1;
-		var widthTriangleCount = grid.width * 2;
-		var heightTriangleCount = grid.height * 2;
-
-		var adjUpTile = terrain.getTile(tileX, tileY + 1);
-		var adjUpGrid = adjUpTile != null ? adjUpTile.grid : null;
-		if( adjUpGrid != null && adjUpGrid.normals != null ) {
-			for( i in 0 ... widthVertexCount )
-				adjUpGrid.normals[i].set(0,0,0);
-			for( i in 0 ... widthTriangleCount )
-				computeNormal(adjUpTile, i * 3, 2);
-			for( i in 0 ... widthVertexCount ) {
-				adjUpGrid.normals[i].normalize();
-				var n = grid.normals[i + widthVertexCount * (heightVertexCount - 1)].add(adjUpGrid.normals[i]).normalize();
-				grid.normals[i + widthVertexCount * (heightVertexCount - 1)].load(n);
-				adjUpGrid.normals[i].load(n);
-			}
-			adjUpTile.needAlloc = true;
-		}
-
-		var adjDownTile = terrain.getTile(tileX, tileY - 1);
-		var adjDownGrid = adjDownTile != null ? adjDownTile.grid : null;
-		if( adjDownGrid != null && adjDownGrid.normals != null ) {
-			for( i in 0 ... widthVertexCount )
-				adjDownGrid.normals[i + widthVertexCount * (heightVertexCount - 1)].set(0,0,0);
-			for( i in 0 ... widthTriangleCount )
-				computeNormal(adjDownTile, i * 3 + widthTriangleCount * 3 * (grid.height - 1), 1);
-			for( i in 0 ... widthVertexCount ) {
-				adjDownGrid.normals[i + widthVertexCount * (heightVertexCount - 1)].normalize();
-				var n = grid.normals[i].add(adjDownGrid.normals[i + widthVertexCount * (heightVertexCount - 1)]).normalize();
-				grid.normals[i].load(n);
-				adjDownGrid.normals[i + widthVertexCount * (heightVertexCount - 1)].load(n);
-			}
-			adjDownTile.needAlloc = true;
-		}
-
-		var adjLeftTile = terrain.getTile(tileX + 1, tileY);
-		var adjLeftGrid = adjLeftTile != null ? adjLeftTile.grid : null;
-		if( adjLeftGrid != null && adjLeftGrid.normals != null ) {
-			for( i in 0 ... heightVertexCount )
-				adjLeftGrid.normals[i * widthVertexCount].set(0,0,0);
-			for( i in 0 ... grid.height ) {
-				computeNormal(adjLeftTile, i * widthTriangleCount * 3, 0);
-				computeNormal(adjLeftTile, i * widthTriangleCount * 3 + 3, 3);
-			}
-			for( i in 0 ... heightVertexCount ) {
-				adjLeftGrid.normals[i * widthVertexCount].normalize();
-				var n = grid.normals[(widthVertexCount - 1) + i * widthVertexCount].add(adjLeftGrid.normals[i * widthVertexCount]).normalize();
-				grid.normals[(widthVertexCount - 1) + i * widthVertexCount].load(n);
-				adjLeftGrid.normals[i * widthVertexCount].load(n);
-			}
-			adjLeftTile.needAlloc = true;
-		}
-
-		var adjRightTile = terrain.getTile(tileX - 1, tileY);
-		var adjRightGrid = adjRightTile != null ? adjRightTile.grid : null;
-		if( adjRightGrid != null && adjRightGrid.normals != null ) {
-			for( i in 0 ... heightVertexCount )
-				adjRightGrid.normals[(widthVertexCount - 1) + i * widthVertexCount].set(0,0,0);
-			for( i in 0 ... grid.height ) {
-				computeNormal(adjRightTile, (widthTriangleCount - 1) * 3 + i * widthTriangleCount * 3, 0);
-				computeNormal(adjRightTile, (widthTriangleCount - 2) * 3 + i * widthTriangleCount * 3, 0);
-			}
-			for( i in 0 ... heightVertexCount ) {
-				adjRightGrid.normals[(widthVertexCount - 1) + i * widthVertexCount].normalize();
-				var n = grid.normals[i * widthVertexCount].add(adjRightGrid.normals[(widthVertexCount - 1) + i * widthVertexCount]).normalize();
-				grid.normals[i * widthVertexCount].load(n);
-				adjRightGrid.normals[(widthVertexCount - 1) + i * widthVertexCount].load(n);
-			}
-			adjRightTile.needAlloc = true;
-		}
-
-		var adjUpRightTile = terrain.getTile(tileX - 1, tileY + 1);
-		var adjUpRightGrid = adjUpRightTile != null ? adjUpRightTile.grid : null;
-		var adjUpLeftTile = terrain.getTile(tileX + 1, tileY + 1);
-		var adjUpLeftGrid = adjUpLeftTile != null ? adjUpLeftTile.grid : null;
-		var adjDownLeftTile = terrain.getTile(tileX + 1, tileY - 1);
-		var adjDownLeftGrid = adjDownLeftTile != null ? adjDownLeftTile.grid : null;
-		var adjDownRightTile = terrain.getTile(tileX - 1, tileY - 1);
-		var adjDownRightGrid = adjDownRightTile != null ? adjDownRightTile.grid : null;
-
-		var upLeft = grid.points.length - 1;
-		var downRight = 0;
-		var downLeft = grid.width;
-		var upRight = (grid.width + 1) * (grid.height);
-		var n = new h3d.col.Point();
-
-		inline function computeUpLeftNormal( t : Tile ) : h3d.col.Point {
-			return t == null ? new h3d.col.Point() : computeNormal(t, ((widthTriangleCount - 1) + (grid.height - 1) * widthTriangleCount) * 3);
-		}
-		inline function computeDownLeftNormal( t : Tile ) : h3d.col.Point {
-			return t == null ? new h3d.col.Point() : computeNormal(t, (heightTriangleCount - 1) * 3).add(computeNormal(t, (heightTriangleCount - 2) * 3));
-		}
-		inline function computeUpRightNormal( t : Tile ) : h3d.col.Point {
-			return t == null ? new h3d.col.Point() : computeNormal(t, (grid.height - 1) * widthTriangleCount * 3).add(computeNormal(t, (grid.height - 1) * widthTriangleCount * 3 + 3));
-		}
-		inline function computeDownRightNormal( t : Tile ) : h3d.col.Point {
-			return t == null ? new h3d.col.Point() : computeNormal(t, 0);
-		}
-
-		// Up Right Corner
-		n.set(0,0,0);
-		n = n.add(computeDownLeftNormal(adjUpRightTile));
-		n = n.add(computeUpLeftNormal(adjRightTile));
-		n = n.add(computeDownRightNormal(adjUpTile));
-		n = n.add(computeUpRightNormal(this));
-		n.normalize();
-		if(	adjUpRightGrid != null ) adjUpRightGrid.normals[downLeft].load(n);
-		if( adjRightGrid != null ) adjRightGrid.normals[upLeft].load(n);
-		if( adjUpGrid != null ) adjUpGrid.normals[downRight].load(n);
-		grid.normals[upRight].load(n);
-		if( adjUpRightTile != null ) adjUpRightTile.needAlloc = true;
-
-		// Up Left Corner
-		n.set(0,0,0);
-		n = n.add(computeDownLeftNormal(adjUpTile));
-		n = n.add(computeUpLeftNormal(this));
-		n = n.add(computeDownRightNormal(adjUpLeftTile));
-		n = n.add(computeUpRightNormal(adjLeftTile));
-		n.normalize();
-		if( adjUpLeftGrid != null ) adjUpLeftGrid.normals[downRight].load(n);
-		if( adjLeftGrid != null ) adjLeftGrid.normals[upRight].load(n);
-		if( adjUpGrid != null ) adjUpGrid.normals[downLeft].load(n);
-		grid.normals[upLeft].load(n);
-		if( adjUpLeftTile != null ) adjUpLeftTile.needAlloc = true;
-
-		// Down Left Corner
-		n.set(0,0,0);
-		n = n.add(computeDownLeftNormal(this));
-		n = n.add(computeUpLeftNormal(adjDownTile));
-		n = n.add(computeDownRightNormal(adjLeftTile));
-		n = n.add(computeUpRightNormal(adjDownLeftTile));
-		n.normalize();
-		if( adjDownLeftGrid != null ) adjDownLeftGrid.normals[upRight].load(n);
-		if( adjLeftGrid != null ) adjLeftGrid.normals[downRight].load(n);
-		if( adjDownGrid != null ) adjDownGrid.normals[upLeft].load(n);
-		grid.normals[downLeft].load(n);
-		if( adjDownLeftTile != null ) adjDownLeftTile.needAlloc = true;
-
-		// Down Right Corner
-		n.set(0,0,0);
-		n = n.add(computeDownLeftNormal(adjRightTile));
-		n = n.add(computeUpLeftNormal(adjDownRightTile));
-		n = n.add(computeDownRightNormal(this));
-		n = n.add(computeUpRightNormal(adjDownTile));
-		n.normalize();
-		if( adjDownRightGrid != null ) adjDownRightGrid.normals[upLeft].load(n);
-		if( adjRightGrid != null ) adjRightGrid.normals[downLeft].load(n);
-		if( adjDownGrid != null ) adjDownGrid.normals[upRight].load(n);
-		grid.normals[downRight].load(n);
-		if( adjDownLeftTile != null ) adjDownLeftTile.needAlloc = true;
-
-		needAlloc = true;
-	}
-
-	public function computeNormals() {
-		if( grid != null )
-			grid.addNormals();
-	}
-
-	public function computeTangents() {
-		if( grid != null )
-			grid.addTangents();
+			if( surfaceWeights[i] != null ) h3d.pass.Copy.run(surfaceWeights[i], surfaceWeightArray, None, null, i);
 	}
 	}
 
 
 	public function getHeight( u : Float, v : Float, ?fast = false ) : Float {
 	public function getHeight( u : Float, v : Float, ?fast = false ) : Float {
-		var pixels = getHeightPixels();
+		var pixels = heightMapPixels;
 		if( pixels == null ) return 0.0;
 		if( pixels == null ) return 0.0;
 		if( !fast ) {
 		if( !fast ) {
 			inline function getPix(u, v) {
 			inline function getPix(u, v) {
@@ -550,6 +335,10 @@ class Tile extends h3d.scene.Mesh {
 
 
 	var cachedBounds : h3d.col.Bounds;
 	var cachedBounds : h3d.col.Bounds;
 	function computeBounds() {
 	function computeBounds() {
+
+		if( bigPrim == null && heightMapPixels == null )
+			return;
+
 		if( cachedBounds == null ) {
 		if( cachedBounds == null ) {
 			if( heightMap != null ) {
 			if( heightMap != null ) {
 				cachedBounds = new h3d.col.Bounds();
 				cachedBounds = new h3d.col.Bounds();
@@ -568,15 +357,23 @@ class Tile extends h3d.scene.Mesh {
 			else if( bigPrim != null ) {
 			else if( bigPrim != null ) {
 				cachedBounds = bigPrim.getBounds();
 				cachedBounds = bigPrim.getBounds();
 			}
 			}
-			cachedBounds.transform(getAbsPos());
 		}
 		}
+		if( cachedBounds != null )
+			cachedBounds.transform(getAbsPos());
+	}
+
+	inline public function getCachedBounds() : h3d.col.Bounds {
+		if( cachedBounds == null )
+			computeBounds();
+		return cachedBounds;
 	}
 	}
 
 
 	public dynamic function beforeEmit() : Bool { return true; };
 	public dynamic function beforeEmit() : Bool { return true; };
 	override function emit( ctx:h3d.scene.RenderContext ) {
 	override function emit( ctx:h3d.scene.RenderContext ) {
-		if( !isReadyForDraw() ) return;
-		computeBounds();
-		insideFrustrum = cachedBounds != null ? ctx.camera.frustum.hasBounds(cachedBounds) : true;
+		if( !isReadyForDraw() )
+			return;
+		insideFrustrum = getCachedBounds() != null ? ctx.camera.frustum.hasBounds(getCachedBounds()) : false;
+		insideFrustrum = true;
 		var b = beforeEmit();
 		var b = beforeEmit();
 		if( b && insideFrustrum )
 		if( b && insideFrustrum )
 			super.emit(ctx);
 			super.emit(ctx);
@@ -584,36 +381,47 @@ class Tile extends h3d.scene.Mesh {
 
 
 	override function sync(ctx:h3d.scene.RenderContext) {
 	override function sync(ctx:h3d.scene.RenderContext) {
 
 
-		if( terrain.surfaceArray == null )
-			return;
+		primitive = bigPrim == null ? terrain.primitive : bigPrim;
 
 
+		// DEBUG
 		shader.SHOW_GRID = #if editor terrain.showGrid #else false #end;
 		shader.SHOW_GRID = #if editor terrain.showGrid #else false #end;
 		shader.CHECKER = #if editor terrain.showChecker #else false #end;
 		shader.CHECKER = #if editor terrain.showChecker #else false #end;
 		shader.COMPLEXITY = #if editor terrain.showComplexity #else false #end;
 		shader.COMPLEXITY = #if editor terrain.showComplexity #else false #end;
-		shader.VERTEX_DISPLACEMENT = bigPrim == null;
-		shader.SURFACE_COUNT = terrain.surfaceArray.surfaceCount;
-		shader.PARALLAX = terrain.enableParallax && terrain.parallaxAmount != 0;
 
 
+		// TILE INFO
+		shader.VERTEX_DISPLACEMENT = bigPrim == null;
 		shader.primSize.set(terrain.tileSize.x, terrain.tileSize.y);
 		shader.primSize.set(terrain.tileSize.x, terrain.tileSize.y);
 		shader.cellSize.set(terrain.cellSize.x, terrain.cellSize.y);
 		shader.cellSize.set(terrain.cellSize.x, terrain.cellSize.y);
-		if( heightMap != null ) shader.heightMapSize.set(heightMap.width, heightMap.height);
+		shader.tileIndex.set(tileX, tileY);
 
 
-		shader.albedoTextures = terrain.surfaceArray.albedo;
-		shader.normalTextures = terrain.surfaceArray.normal;
-		shader.pbrTextures = terrain.surfaceArray.pbr;
-		shader.weightTextures = surfaceWeightArray;
-		shader.heightMap = heightMap;
-		shader.surfaceIndexMap = surfaceIndexMap;
+		// SURFACE
+		if( terrain.surfaceArray != null ) {
+			shader.SURFACE_COUNT = terrain.surfaceArray.surfaceCount;
+			shader.albedoTextures = terrain.surfaceArray.albedo;
+			shader.normalTextures = terrain.surfaceArray.normal;
+			shader.pbrTextures = terrain.surfaceArray.pbr;
+			shader.surfaceParams = terrain.surfaceArray.params;
+			shader.secondSurfaceParams = terrain.surfaceArray.secondParams;
+		}
 
 
-		shader.surfaceParams = terrain.surfaceArray.params;
-		shader.secondSurfaceParams = terrain.surfaceArray.secondParams;
-		shader.tileIndex.set(tileX, tileY);
+		// BLEND PARAM
+		shader.PARALLAX = terrain.enableParallax && terrain.parallaxAmount != 0;
 		shader.parallaxAmount = terrain.parallaxAmount;
 		shader.parallaxAmount = terrain.parallaxAmount;
 		shader.minStep = terrain.parallaxMinStep;
 		shader.minStep = terrain.parallaxMinStep;
 		shader.maxStep = terrain.parallaxMaxStep;
 		shader.maxStep = terrain.parallaxMaxStep;
 		shader.heightBlendStrength = terrain.heightBlendStrength;
 		shader.heightBlendStrength = terrain.heightBlendStrength;
 		shader.blendSharpness = terrain.blendSharpness;
 		shader.blendSharpness = terrain.blendSharpness;
 
 
+		// TILE TEXTURE
+		shader.weightTextures = surfaceWeightArray;
+		shader.heightMap = heightMap;
+		shader.normalMap = normalMap;
+		shader.surfaceIndexMap = surfaceIndexMap;
+
+
+		if( bigPrim == null && needNormalBake && isReadyForDraw() )
+			bakeNormal();
+
 		// OnContextLost support : re-create the bigPrim
 		// OnContextLost support : re-create the bigPrim
 		var needRealloc = false;
 		var needRealloc = false;
 		if( bigPrim != null ) {
 		if( bigPrim != null ) {
@@ -624,7 +432,7 @@ class Tile extends h3d.scene.Mesh {
 				}
 				}
 			}
 			}
 			if( needRealloc ) {
 			if( needRealloc ) {
-				createBigPrim(normalTangentBytes);
+				createBigPrim();
 				cachedBounds = null;
 				cachedBounds = null;
 			}
 			}
 		}
 		}

+ 46 - 34
hrt/shader/Terrain.hx

@@ -4,8 +4,6 @@ class Terrain extends hxsl.Shader {
 
 
 	static var SRC = {
 	static var SRC = {
 
 
-		@input var tangent : Vec3;
-		@:import h3d.shader.BaseMesh;
 		@const var SHOW_GRID : Bool;
 		@const var SHOW_GRID : Bool;
 		@const var SURFACE_COUNT : Int;
 		@const var SURFACE_COUNT : Int;
 		@const var CHECKER : Bool;
 		@const var CHECKER : Bool;
@@ -28,6 +26,7 @@ class Terrain extends hxsl.Shader {
 		@param var weightTextures : Sampler2DArray;
 		@param var weightTextures : Sampler2DArray;
 		@param var surfaceIndexMap : Sampler2D;
 		@param var surfaceIndexMap : Sampler2D;
 		@param var heightMap : Sampler2D;
 		@param var heightMap : Sampler2D;
+		@param var normalMap : Sampler2D;
 		@param var surfaceParams : Array<Vec4, SURFACE_COUNT>;
 		@param var surfaceParams : Array<Vec4, SURFACE_COUNT>;
 		@param var secondSurfaceParams : Array<Vec4, SURFACE_COUNT>;
 		@param var secondSurfaceParams : Array<Vec4, SURFACE_COUNT>;
 
 
@@ -50,25 +49,43 @@ class Terrain extends hxsl.Shader {
 
 
 		var tangentViewPos : Vec3;
 		var tangentViewPos : Vec3;
 		var tangentFragPos : Vec3;
 		var tangentFragPos : Vec3;
-		var transformedTangent : Vec4;
+		var transformedPosition : Vec3;
+		var transformedNormal : Vec3;
+		var terrainNormal : Vec3;
+		var pixelColor : Vec4;
+
+		@input var input : {
+			var position : Vec3;
+			var normal : Vec3;
+		};
+
+		@global var global : {
+			@perObject var modelView : Mat4;
+		};
+
+		@global var camera : {
+			var position : Vec3;
+		};
 
 
 		function vertex() {
 		function vertex() {
+
 			calculatedUV = input.position.xy / primSize;
 			calculatedUV = input.position.xy / primSize;
 			worldUV = transformedPosition.xy;
 			worldUV = transformedPosition.xy;
-			if( VERTEX_DISPLACEMENT ) {
-				// The last pixel is for edge blend
-				var terrainUV = (calculatedUV * (heightMapSize - 1)) / heightMapSize;
-				// Blend with the heightpixel of the adjacent chunk
-				if( input.position.x == primSize.x ) terrainUV.x += 0.5 / heightMapSize.x;
-				if( input.position.y == primSize.y ) terrainUV.y += 0.5 / heightMapSize.y;
-				transformedPosition += vec3(0,0,textureLod(heightMap, terrainUV, 0).r) * global.modelView.mat3();
+			
+			if( VERTEX_DISPLACEMENT ) { // Use heightMap and normalMap
+				transformedPosition += vec3(0, 0, textureLod(heightMap, calculatedUV, 0).r);
+				terrainNormal = unpackNormal(textureLod(normalMap, calculatedUV, 0).rgba);
 			}
 			}
-			transformedTangent = vec4(tangent * global.modelView.mat3(),tangent.dot(tangent) > 0.5 ? 1. : -1.);
-			var tanX = transformedTangent.xyz.normalize() * -transformedTangent.w;
-			var tanY = transformedNormal.cross(tanX).normalize();
-			TBN = mat3(tanX, tanY, transformedNormal);
-			tangentViewPos = (camera.position * TBN);
-			tangentFragPos = (transformedPosition * TBN);
+			else { // The normal and height are in the vertex
+				terrainNormal = input.normal * global.modelView.mat3();
+			}
+
+			// Make the TBN matrix for normal mapping and parallax
+			var bitangent = cross(vec3(1, 0, 0), terrainNormal);
+			var tangent = cross(terrainNormal, bitangent);
+			TBN = mat3(	vec3(tangent.x, bitangent.x, terrainNormal.x),
+						vec3(tangent.y, bitangent.y, terrainNormal.y),
+						vec3(tangent.z, bitangent.z, terrainNormal.z));
 		}
 		}
 
 
 		function getWeight( i : IVec3,  uv : Vec2 ) : Vec3 {
 		function getWeight( i : IVec3,  uv : Vec2 ) : Vec3 {
@@ -90,10 +107,11 @@ class Terrain extends hxsl.Shader {
 		var w : Vec3;
 		var w : Vec3;
 		var i : IVec3;
 		var i : IVec3;
 		function getPOMUV( i : IVec3, uv : Vec2 ) : Vec2 {
 		function getPOMUV( i : IVec3, uv : Vec2 ) : Vec2 {
-			var viewNS = normalize(tangentViewPos - tangentFragPos);
+			var viewNS = normalize(camera.position - transformedPosition) * TBN;
 			viewNS.xy /= viewNS.z;
 			viewNS.xy /= viewNS.z;
-			var numLayers = mix(float(maxStep), float(minStep), viewNS.dot(transformedNormal));
-			var layerDepth = 1 / numLayers;
+			viewNS.x *= -1;
+			var numLayers = mix(float(maxStep), float(minStep), viewNS.dot(terrainNormal));
+			var layerDepth = 1.0 / numLayers;
 			var curLayerDepth = 0.;
 			var curLayerDepth = 0.;
 			var delta = (viewNS.xy * parallaxAmount / primSize) / numLayers;
 			var delta = (viewNS.xy * parallaxAmount / primSize) / numLayers;
 			var curUV = uv;
 			var curUV = uv;
@@ -114,7 +132,6 @@ class Terrain extends hxsl.Shader {
 			var before = prevDepth - curLayerDepth + layerDepth;
 			var before = prevDepth - curLayerDepth + layerDepth;
 			var pomUV = mix(curUV, prevUV,  after / (after - before));
 			var pomUV = mix(curUV, prevUV,  after / (after - before));
 			return pomUV;
 			return pomUV;
-
 		}
 		}
 
 
 		function getsurfaceUV( id : Int, uv : Vec2 ) : Vec3 {
 		function getsurfaceUV( id : Int, uv : Vec2 ) : Vec3 {
@@ -133,11 +150,6 @@ class Terrain extends hxsl.Shader {
 			if( CHECKER ) {
 			if( CHECKER ) {
 				var tile = abs(abs(floor(input.position.x)) % 2 - abs(floor(input.position.y)) % 2);
 				var tile = abs(abs(floor(input.position.x)) % 2 - abs(floor(input.position.y)) % 2);
 				pixelColor = vec4(mix(vec3(0.4), vec3(0.1), tile), 1.0);
 				pixelColor = vec4(mix(vec3(0.4), vec3(0.1), tile), 1.0);
-				var n = transformedNormal;
-				var nf = vec3(0,0,1);
-				var tanX = transformedTangent.xyz;
-				var tanY = n.cross(tanX) * -transformedTangent.w;
-				transformedNormal = (nf.x * tanX + nf.y * tanY + nf.z * n).normalize();
 				roughnessValue = mix(0.1, 0.9, tile);
 				roughnessValue = mix(0.1, 0.9, tile);
 				metalnessValue = mix(1.0, 0, tile);
 				metalnessValue = mix(1.0, 0, tile);
 				occlusionValue = 1;
 				occlusionValue = 1;
@@ -148,11 +160,6 @@ class Terrain extends hxsl.Shader {
 				for(i in 0 ... SURFACE_COUNT)
 				for(i in 0 ... SURFACE_COUNT)
 					blendCount += ceil(weightTextures.get(vec3(calculatedUV, i)).r);
 					blendCount += ceil(weightTextures.get(vec3(calculatedUV, i)).r);
 				pixelColor = vec4(mix(vec3(0,1,0), vec3(1,0,0), blendCount / 3.0) , 1);
 				pixelColor = vec4(mix(vec3(0,1,0), vec3(1,0,0), blendCount / 3.0) , 1);
-				var n = transformedNormal;
-				var nf = vec3(0,0,1);
-				var tanX = transformedTangent.xyz;
-				var tanY = n.cross(tanX) * -transformedTangent.w;
-				transformedNormal = (nf.x * tanX + nf.y * tanY + nf.z * n).normalize();
 				emissiveValue = 1;
 				emissiveValue = 1;
 				roughnessValue = 1;
 				roughnessValue = 1;
 				metalnessValue = 0;
 				metalnessValue = 0;
@@ -179,11 +186,17 @@ class Terrain extends hxsl.Shader {
 				var h = vec3( 	secondSurfaceParams[i.x].x + pbr1.a * (secondSurfaceParams[i.x].y - secondSurfaceParams[i.x].x),
 				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.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));
 								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);
+
+				var h = mix(vec3(1,1,1), h, heightBlendStrength);
+				w *= h;
 
 
 				// Sharpness
 				// Sharpness
+				var ws = mix(w, w, blendSharpness);
 				var m = max(w.x, max(w.y, w.z));
 				var m = max(w.x, max(w.y, w.z));
-				var mw = ceil(w - m + 0.01);
+				var mw = vec3(0,0,0);
+				if( m == w.x ) mw = vec3(1,0,0);
+				if( m == w.y ) mw = vec3(0,1,0);
+				if( m == w.z ) mw = vec3(0,0,1);
 				w = mix(w, mw, blendSharpness);
 				w = mix(w, mw, blendSharpness);
 
 
 				// Blend
 				// Blend
@@ -211,8 +224,7 @@ class Terrain extends hxsl.Shader {
 				normal /= wSum;
 				normal /= wSum;
 
 
 				// Output
 				// Output
-				var n = unpackNormal(normal);
-				//transformedNormal = vec3(n.x * -1, n.y, n.z) * TBN;
+				transformedNormal = unpackNormal(normal) * TBN;
 				pixelColor = vec4(albedo, 1.0);
 				pixelColor = vec4(albedo, 1.0);
 				roughnessValue = 1 - pbr.g * pbr.g;
 				roughnessValue = 1 - pbr.g * pbr.g;
 				metalnessValue = pbr.r;
 				metalnessValue = pbr.r;