Bläddra i källkod

Update Terrain

ShiroSmith 7 år sedan
förälder
incheckning
51d560eb90

+ 1 - 0
h2d/BlendMode.hx

@@ -11,4 +11,5 @@ enum BlendMode {
 	Screen;
 	Sub;
 	Max;
+	Min;
 }

+ 1 - 1
h3d/mat/Material.hx

@@ -138,7 +138,7 @@ class Material extends BaseMaterial {
 			case Alpha:
 				mainPass.depthWrite = true;
 				mainPass.setPassName("alpha");
-			case Add, AlphaAdd, SoftAdd, Multiply, Erase, Screen, Sub, Max:
+			case Add, AlphaAdd, SoftAdd, Multiply, Erase, Screen, Sub, Max, Min:
 				mainPass.depthWrite = false;
 				mainPass.setPassName("additive");
 			}

+ 39 - 14
h3d/mat/Pass.hx

@@ -86,38 +86,63 @@ class Pass implements hxd.impl.Serializable {
 
 	public function setBlendMode( b : BlendMode ) {
 		switch( b ) {
-		case None:
+		case None: // Out = 1 * Src + 0 * Dst
 			blend(One, Zero);
 			blendOp = Add;
-		case Alpha:
+			blendAlphaOp = Add;
+		case Alpha: // Out = SrcA * Src + (1 - SrcA) * Dst
 			blend(SrcAlpha, OneMinusSrcAlpha);
 			blendOp = Add;
-		case Add:
+			blendAlphaOp = Add;
+		case Add: // Out = SrcA * Src + 1 * Dst
 			blend(SrcAlpha, One);
 			blendOp = Add;
-		case AlphaAdd:
+			blendAlphaOp = Add;
+		case AlphaAdd: // Out = Src + (1 - SrcA) * Dst
 			blend(One, OneMinusSrcAlpha);
 			blendOp = Add;
-		case SoftAdd:
+			blendAlphaOp = Add;
+		case SoftAdd: // Out = (1 - Dst) * Src + 1 * Dst
 			blend(OneMinusDstColor, One);
 			blendOp = Add;
-		case Multiply:
+			blendAlphaOp = Add;
+		case Multiply: // Out = Dst * Src + 0 * Dst
 			blend(DstColor, Zero);
-		case Erase:
+			blendOp = Add;
+			blendAlphaOp = Add;
+		case Erase: // Out = 0 * Src + (1 - Srb) * Dst
 			blend(Zero, OneMinusSrcColor);
 			blendOp = Add;
-		case Screen:
+			blendAlphaOp = Add;
+		case Screen: // Out = 1 * Src + (1 - Srb) * Dst
 			blend(One, OneMinusSrcColor);
 			blendOp = Add;
-		case Sub:
+			blendAlphaOp = Add;
+		case Sub: // Out = 1 * Dst - SrcA * Src
 			blend(SrcAlpha, One);
 			blendOp = ReverseSub;
-		case Max:
-			this.blendSrc = SrcColor;
-			this.blendAlphaSrc = SrcAlpha;
-			this.blendDst = DstColor;
-			this.blendAlphaDst = DstAlpha;
+			blendAlphaOp = ReverseSub;
+
+		// The output color min/max of the source and dest colors.
+		// The blend parameters Src and Dst are ignored for this equation.
+		case Max: // Out = MAX( Src, Dst )
+			blendSrc = Zero;
+			blendAlphaSrc = Zero;
+			blendDst = Zero;
+			blendAlphaDst = Zero;
+			blendAlphaSrc = Zero;
+			blendAlphaDst = Zero;
+			blendAlphaOp = Max;
 			blendOp = Max;
+		case Min: // Out = MIN( Src, Dst )
+			blendSrc = Zero;
+			blendAlphaSrc = Zero;
+			blendDst = Zero;
+			blendAlphaDst = Zero;
+			blendAlphaSrc = Zero;
+			blendAlphaDst = Zero;
+			blendAlphaOp = Min;
+			blendOp = Min;
 		}
 	}
 

+ 10 - 13
h3d/prim/Grid.hx

@@ -2,12 +2,16 @@ package h3d.prim;
 
 class Grid extends Polygon {
 
-	var width : Int;
-	var height : Int;
+	public var width (default, null) : Int;
+	public var height (default, null)  : Int;
+	public var cellWidth (default, null) : Float;
+	public var cellHeight (default, null)  : Float;
 
 	public function new( width : Int, height : Int, cellWidth = 1., cellHeight = 1. ) {
 		this.width = width;
 		this.height = height;
+		this.cellWidth = width;
+		this.cellHeight = height;
 
 		var idx = new hxd.IndexBuffer();
 		for( y in 0...height )
@@ -27,18 +31,11 @@ class Grid extends Polygon {
 	}
 
 	override function addUVs() {
-		unindex();
 		uvs = [];
-		for( y in 0...height ) {
-			for( x in 0...width ) {
-				uvs.push(new UV(x/width,     y/height));
-				uvs.push(new UV((x+1)/width, y/height));
-				uvs.push(new UV(x/width,     (y+1)/height));
-
-				uvs.push(new UV((x+1)/width, y/height));
-				uvs.push(new UV((x+1)/width, (y+1)/height));
-				uvs.push(new UV(x/width,     (y+1)/height));				
-			}
+		for(i in 0 ... points.length){
+			var y = hxd.Math.floor(i / width);
+			var x = i - y * width;
+			uvs.push(new UV(x/width,y/height));
 		}
 	}
 

+ 0 - 353
h3d/scene/pbr/Terrain.hx

@@ -1,353 +0,0 @@
-package h3d.scene.pbr;
-#if false
-
-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 surfaceCount = 0;
-	public var surfaceWeights : Array<h3d.mat.Texture> = [];
-	var shader : h3d.shader.pbr.Terrain;
-	public var surfaceWeightArray (default, null) : h3d.mat.TextureArray;
-
-	public function new(x : Int, y : Int , ?parent){
-		super(Std.instance(parent, Terrain).grid, null, parent);
-		this.tileX = x;
-		this.tileY = y;
-		shader = new h3d.shader.pbr.Terrain();
-		material.mainPass.addShader(shader);
-		material.mainPass.culling = None;
-		this.x = x * getTerrain().tileSize;
-		this.y = y * getTerrain().tileSize;
-		this.surfaceCount = getTerrain().surfaces.length;
-	}
-
-	function set_heightMap(v){
-		shader.heightMap = v;
-		return heightMap = v;
-	}
-
-	public function uploadWeightMap(tex : h3d.mat.Texture, i : Int){
-		if(i < surfaceWeights.length && surfaceWeights[i] != null){
-			getTerrain().copyPass.apply(tex, surfaceWeights[i]);
-			generateWeightArray();
-		}
-	}
-
-	public function refresh(){
-
-		if(heightMap == null || heightMap.width != getTerrain().heightMapResolution + 2){
-			var oldHeightMap = heightMap;
-			heightMap = new h3d.mat.Texture(getTerrain().heightMapResolution + 2, getTerrain().heightMapResolution + 2, [Target], RGBA32F );
-			heightMap.wrap = Clamp;
-			heightMap.filter = Nearest;
-			if(oldHeightMap != null){
-				getTerrain().copyPass.apply(oldHeightMap, heightMap);
-				oldHeightMap.dispose();
-			}
-		}
-
-		if(surfaceIndexMap == null || surfaceIndexMap.width != getTerrain().weightMapResolution){
-			var oldSurfaceIndexMap = surfaceIndexMap;
-			surfaceIndexMap = new h3d.mat.Texture(getTerrain().weightMapResolution, getTerrain().weightMapResolution, [Target], RGBA);
-			surfaceIndexMap.filter = Nearest;
-			if(oldSurfaceIndexMap != null){
-				getTerrain().copyPass.apply(oldSurfaceIndexMap, surfaceIndexMap);
-				oldSurfaceIndexMap.dispose();
-			}
-		}
-
-		if(surfaceWeights.length != surfaceCount || surfaceWeights[0].width != getTerrain().weightMapResolution){
-			var oldArray = surfaceWeights;
-			surfaceWeights = new Array<h3d.mat.Texture>();
-			surfaceWeights.resize(surfaceCount);
-			for(i in 0 ... surfaceWeights.length){
-				surfaceWeights[i] = new h3d.mat.Texture(getTerrain().weightMapResolution, getTerrain().weightMapResolution, [Target], RGBA);
-				surfaceWeights[i].wrap = Clamp;
-				if(i < oldArray.length && oldArray[i] != null)
-					getTerrain().copyPass.apply(oldArray[i], surfaceWeights[i]);
-			}
-			for(i in 0 ... oldArray.length)
-				if( oldArray[i] != null) oldArray[i].dispose();
-
-			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]);
-			surfaceWeightArray.wrap = Clamp;
-		}
-		for(i in 0 ... surfaceWeights.length)
-			if(surfaceWeights[i] != null) getTerrain().copyPass.apply(surfaceWeights[i], surfaceWeightArray, None, null, i);
-	}
-
-	public override function dispose() {
-		if(heightMap != null) heightMap.dispose();
-		if(surfaceIndexMap != null) surfaceIndexMap.dispose();
-		for(i in 0 ... surfaceWeights.length)
-			if( surfaceWeights[i] != null) surfaceWeights[i].dispose();
-	}
-
-	override function emit(ctx:RenderContext){
-		if( getTerrain().surfaceArray == null) return;
-		var bounds = getBounds();
-		if(bounds != null){
-			if(ctx.camera.getFrustum().hasBounds(bounds))
-				super.emit(ctx);
-		}else
-			super.emit(ctx);
-	}
-
-	override function sync(ctx:RenderContext) {
-		if( getTerrain().surfaceArray == null) return;
-		shader.heightMap = heightMap;
-		shader.heightMapSize = getTerrain().heightMapResolution;
-		shader.primSize = getTerrain().tileSize;
-		shader.cellSize = getTerrain().cellSize;
-		shader.showGrid = getTerrain().showGrid;
-		shader.surfaceIndexMap = surfaceIndexMap;
-		shader.albedoTextures = getTerrain().surfaceArray.albedo;
-		shader.normalTextures = getTerrain().surfaceArray.normal;
-		shader.pbrTextures = getTerrain().surfaceArray.pbr;
-		shader.weightTextures = surfaceWeightArray;
-		shader.weightCount = surfaceCount;
-		shader.surfaceParams = getTerrain().surfaceArray.params;
-		shader.secondSurfaceParams = getTerrain().surfaceArray.secondParams;
-		shader.tileIndex = new h3d.Vector(tileX, tileY);
-		shader.useHeightBlend = getTerrain().useHeightBlend;
-		shader.parallaxAmount = getTerrain().parallaxAmount / 10.0;
-	}
-
-	inline function getTerrain(){
-		return Std.instance(parent, Terrain);
-	}
-}
-
-class Surface {
-	public var albedo (default, set) : h3d.mat.Texture;
-	public var normal (default, set) : h3d.mat.Texture;
-	public var pbr (default, set) : h3d.mat.Texture;
-	public var tilling = 1.0;
-	public var offset : h3d.Vector;
-	public var angle = 0.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 dispose() {
-		if(albedo != null) albedo.dispose();
-		if(normal != null) normal.dispose();
-		if(pbr != null) pbr.dispose();
-	}
-
-	function set_albedo(t : h3d.mat.Texture){
-		return albedo = swap(albedo, t);
-	}
-
-	function set_normal(t : h3d.mat.Texture){
-		return normal = swap(normal, t);
-	}
-
-	function set_pbr(t : h3d.mat.Texture){
-		return pbr = swap(pbr, t);
-	}
-
-	function swap(a : h3d.mat.Texture, b : h3d.mat.Texture) : h3d.mat.Texture {
-		if(a != null) a.dispose();
-		if(b != null){
-			var r = b.clone();
-			r.wrap = Repeat;
-			return r;
-		}
-		return null;
-	}
-}
-
-class SurfaceArray {
-	public var albedo : h3d.mat.TextureArray;
-	public var normal : h3d.mat.TextureArray;
-	public var pbr : h3d.mat.TextureArray;
-	public var params : Array<h3d.Vector> = [];
-	public var secondParams : Array<h3d.Vector> = [];
-
-	public function new(count, res){
-		albedo = new h3d.mat.TextureArray(res, res, count, [Target]);
-		normal = new h3d.mat.TextureArray(res, res, count, [Target]);
-		pbr = new h3d.mat.TextureArray(res, res, count, [Target]);
-		albedo.wrap = Repeat;
-		normal.wrap = Repeat;
-		pbr.wrap = Repeat;
-	}
-
-	public function dispose() {
-		if(albedo != null) albedo.dispose();
-		if(normal != null) normal.dispose();
-		if(pbr != null) pbr.dispose();
-	}
-}
-
-class Terrain extends Object {
-
-	public var tileSize = 1.0;
-	public var cellSize = 1.0;
-	public var cellCount = 1;
-	public var heightMapResolution = 1;
-	public var weightMapResolution = 1;
-	public var showGrid : Bool;
-	public var useHeightBlend : Bool;
-	public var parallaxAmount : Float = 0;
-	public var grid (default, null) : h3d.prim.Grid;
-	public var copyPass (default, null): h3d.pass.Copy;
-	public var tiles (default, null) : Array<Tile> = [];
-	public var surfaces (default, null) : Array<Surface> = [];
-	public var surfaceArray (default, null) : SurfaceArray;
-
-	public function new(?parent){
-		super(parent);
-		grid = new h3d.prim.Grid( cellCount, cellCount, cellSize, cellSize);
-		grid.addUVs();
-		grid.addNormals();
-		copyPass = new h3d.pass.Copy();
-	}
-
-	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(){
-		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 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(0, 0, 0, 0);
-		}
-	}
-
-	public function refreshMesh(){
-		if(grid != null) grid.dispose();
-		grid = new h3d.prim.Grid( cellCount, cellCount, cellSize, cellSize);
-		grid.addUVs();
-		grid.addNormals();
-
-		for(tile in tiles){
-			tile.primitive = grid;
-			tile.x = tile.tileX * tileSize;
-			tile.y = tile.tileY * tileSize;
-		}
-	}
-
-	public function refreshTex(){
-		for(tile in tiles){
-			tile.surfaceCount = surfaces.length;
-			tile.refresh();
-		}
-	}
-
-	public function refresh(){
-		refreshMesh();
-		refreshTex();
-	}
-
-	public function createTile(x : Int, y : Int) : Tile {
-		var tile = getTile(x,y);
-		if(tile == null){
-			tile = new Tile(x, y, this);
-			tile.refresh();
-			tiles.push(tile);
-		}
-		return tile;
-	}
-
-	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(pos : h3d.Vector) : Tile {
-		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(pos : h3d.Vector) : Tile {
-		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(pos : h3d.Vector, range : Float, ?create = false) : Array<Tile> {
-		if(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;
-	}
-}
-
-
-#end

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

@@ -0,0 +1,68 @@
+package h3d.scene.pbr.terrain;
+
+class Surface {
+	public var albedo (default, set) : h3d.mat.Texture;
+	public var normal (default, set) : h3d.mat.Texture;
+	public var pbr (default, set) : h3d.mat.Texture;
+	public var tilling = 1.0;
+	public var offset : h3d.Vector;
+	public var angle = 0.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 dispose() {
+		if(albedo != null) albedo.dispose();
+		if(normal != null) normal.dispose();
+		if(pbr != null) pbr.dispose();
+	}
+
+	function set_albedo(t : h3d.mat.Texture){
+		return albedo = swap(albedo, t);
+	}
+
+	function set_normal(t : h3d.mat.Texture){
+		return normal = swap(normal, t);
+	}
+
+	function set_pbr(t : h3d.mat.Texture){
+		return pbr = swap(pbr, t);
+	}
+
+	function swap(a : h3d.mat.Texture, b : h3d.mat.Texture) : h3d.mat.Texture {
+		if(a != null) a.dispose();
+		if(b != null){
+			var r = b.clone();
+			r.wrap = Repeat;
+			return r;
+		}
+		return null;
+	}
+}
+
+class SurfaceArray {
+	public var albedo : h3d.mat.TextureArray;
+	public var normal : h3d.mat.TextureArray;
+	public var pbr : h3d.mat.TextureArray;
+	public var params : Array<h3d.Vector> = [];
+	public var secondParams : Array<h3d.Vector> = [];
+
+	public function new(count, res){
+		albedo = new h3d.mat.TextureArray(res, res, count, [Target]);
+		normal = new h3d.mat.TextureArray(res, res, count, [Target]);
+		pbr = new h3d.mat.TextureArray(res, res, count, [Target]);
+		albedo.wrap = Repeat;
+		normal.wrap = Repeat;
+		pbr.wrap = Repeat;
+	}
+
+	public function dispose() {
+		if(albedo != null) albedo.dispose();
+		if(normal != null) normal.dispose();
+		if(pbr != null) pbr.dispose();
+	}
+}

+ 187 - 0
h3d/scene/pbr/terrain/Terrain.hx

@@ -0,0 +1,187 @@
+package h3d.scene.pbr.terrain;
+
+class Terrain extends Object {
+
+	public var tileSize = 1.0;
+	public var cellSize = 1.0;
+	public var cellCount = 1;
+	public var heightMapResolution = 1;
+	public var weightMapResolution = 1;
+	public var showGrid : Bool;
+	public var parallaxAmount : Float = 0.0;
+	public var parallaxMinStep : Int = 1;
+	public var parallaxMaxStep : Int = 1;
+	public var heightBlendStrength : Float = 0.0;
+	public var heightBlendSharpness : Float = 0.0;
+	public var grid (default, null) : h3d.prim.Grid;
+	public var copyPass (default, null): h3d.pass.Copy;
+	public var tiles (default, null) : Array<Tile> = [];
+	public var surfaces (default, null) : Array<Surface> = [];
+	public var surfaceArray (default, null) : h3d.scene.pbr.terrain.Surface.SurfaceArray;
+
+	public var correctUV = false;
+
+	public function new(?parent){
+		super(parent);
+		grid = new h3d.prim.Grid( cellCount, cellCount, cellSize, cellSize);
+		grid.addUVs();
+		grid.addNormals();
+		copyPass = new h3d.pass.Copy();
+	}
+
+	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(){
+		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 h3d.scene.pbr.terrain.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(0, 0, 0, 0);
+		}
+	}
+
+	public function refreshTiles(){
+		for(tile in tiles)
+			if(tile.needAlloc){
+				tile.grid.alloc(h3d.Engine.getCurrent());
+				tile.needAlloc = false;
+			}
+	}
+
+	public function refreshMesh(){
+		if(grid != null) grid.dispose();
+		grid = new h3d.prim.Grid( cellCount, cellCount, cellSize, cellSize);
+		grid.addUVs();
+		grid.addNormals();
+
+		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.surfaceCount = surfaces.length;
+			tile.refresh();
+		}
+	}
+
+	public function refresh(){
+		refreshMesh();
+		refreshTex();
+	}
+
+	public function createTile(x : Int, y : Int) : Tile {
+		var tile = getTile(x,y);
+		if(tile == null){
+			tile = new Tile(x, y, this);
+			tile.refresh();
+			tiles.push(tile);
+		}
+		return 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 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(pos : h3d.Vector) : Tile {
+		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(pos : h3d.Vector) : Tile {
+		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(pos : h3d.Vector, range : Float, ?create = false) : Array<Tile> {
+		if(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;
+	}
+}
+
+

+ 477 - 0
h3d/scene/pbr/terrain/Tile.hx

@@ -0,0 +1,477 @@
+package h3d.scene.pbr.terrain;
+
+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 surfaceCount = 0;
+	public var surfaceWeights : Array<h3d.mat.Texture> = [];
+	var shader : h3d.shader.pbr.Terrain;
+	public var surfaceWeightArray (default, null) : h3d.mat.TextureArray;
+	public var grid (default, null) : h3d.prim.Grid;
+	public var needAlloc = false;
+	var heightmapPixels : hxd.Pixels.PixelsFloat;
+	public var needNewPixelCapture = false;
+
+	public function new(x : Int, y : Int , ?parent){
+		super(null, null, parent);
+		this.tileX = x;
+		this.tileY = y;
+		shader = new h3d.shader.pbr.Terrain();
+		material.mainPass.addShader(shader);
+		material.mainPass.culling = None;
+		this.x = x * getTerrain().tileSize;
+		this.y = y * getTerrain().tileSize;
+		this.surfaceCount = getTerrain().surfaces.length;
+		refreshMesh();
+	}
+
+	function set_heightMap(v){
+		shader.heightMap = v;
+		return heightMap = v;
+	}
+
+	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();
+		}
+		computeNormals();
+	}
+
+	public function blendEdges(?propagate = false){
+		computeEdgesHeight();
+		computeEdgesNormals();
+	}
+
+	public function refresh(){
+		if(heightMap == null || heightMap.width != getTerrain().heightMapResolution + 1){
+			var oldHeightMap = heightMap;
+			heightMap = new h3d.mat.Texture(getTerrain().heightMapResolution + 1, getTerrain().heightMapResolution + 1, [Target], RGBA32F );
+			heightMap.wrap = Clamp;
+			heightMap.filter = Linear;
+			if(oldHeightMap != null){
+				getTerrain().copyPass.apply(oldHeightMap, heightMap);
+				oldHeightMap.dispose();
+			}
+		}
+
+		if(surfaceIndexMap == null || surfaceIndexMap.width != getTerrain().weightMapResolution){
+			var oldSurfaceIndexMap = surfaceIndexMap;
+			surfaceIndexMap = new h3d.mat.Texture(getTerrain().weightMapResolution, getTerrain().weightMapResolution, [Target], RGBA);
+			surfaceIndexMap.filter = Nearest;
+			if(oldSurfaceIndexMap != null){
+				getTerrain().copyPass.apply(oldSurfaceIndexMap, surfaceIndexMap);
+				oldSurfaceIndexMap.dispose();
+			}
+		}
+
+		if(surfaceWeights.length != surfaceCount || surfaceWeights[0].width != getTerrain().weightMapResolution){
+			var oldArray = surfaceWeights;
+			surfaceWeights = new Array<h3d.mat.Texture>();
+			surfaceWeights.resize(surfaceCount);
+			for(i in 0 ... surfaceWeights.length){
+				surfaceWeights[i] = new h3d.mat.Texture(getTerrain().weightMapResolution, getTerrain().weightMapResolution, [Target], R8);
+				surfaceWeights[i].wrap = Clamp;
+				if(i < oldArray.length && oldArray[i] != null)
+					getTerrain().copyPass.apply(oldArray[i], surfaceWeights[i]);
+			}
+			for(i in 0 ... oldArray.length)
+				if( oldArray[i] != null) oldArray[i].dispose();
+			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.wrap = Clamp;
+		}
+		for(i in 0 ... surfaceWeights.length)
+			if(surfaceWeights[i] != null) getTerrain().copyPass.apply(surfaceWeights[i], surfaceWeightArray, None, null, i);
+	}
+
+	public function computeEdgesHeight(){
+		if(heightMap == null) return;
+		var pixels : hxd.Pixels.PixelsFloat = getHeightPixels();
+		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) );
+			}
+		}
+		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) );
+			}
+		}
+		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);
+	}
+
+	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){
+			var pixels : hxd.Pixels.PixelsFloat = tile.getHeightPixels();
+			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, pixels);
+			t1.z += tile.getHeight(t1.x / getTerrain().tileSize, t1.y / getTerrain().tileSize, pixels);
+			t2.z += tile.getHeight(t2.x / getTerrain().tileSize, t2.y / getTerrain().tileSize, pixels);
+		}
+
+		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;
+		needAlloc = true;
+	}
+
+	public function computeNormals(){
+		var pixels : hxd.Pixels.PixelsFloat = null;
+		if(heightMap != null)
+			pixels = getHeightPixels();
+		if(grid.normals == null) grid.normals = new Array();
+		grid.normals.resize(grid.points.length);
+		for( i in 0 ... grid.points.length){
+			if(grid.normals[i] == null) grid.normals[i] = new h3d.col.Point();
+			grid.normals[i].x = 0; grid.normals[i].y = 0; grid.normals[i].z = 0;
+		}
+		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(pixels != null){
+				t0.z += getHeight(t0.x / getTerrain().tileSize, t0.y / getTerrain().tileSize, pixels);
+				t1.z += getHeight(t1.x / getTerrain().tileSize, t1.y / getTerrain().tileSize, pixels);
+				t2.z += getHeight(t2.x / getTerrain().tileSize, t2.y / getTerrain().tileSize, pixels);
+			}
+			var n1 = t1.sub(t0);
+			n1.normalize();
+			var n2 = t2.sub(t0);
+			n2.normalize();
+			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;
+	}
+
+	function getHeight(u : Float, v : Float, pixels : hxd.Pixels.PixelsFloat ){
+		if(heightMap.filter == Linear){
+			var coordsX = hxd.Math.max(0, u * (heightMap.width - 1) - 0.5);
+			var coordsY =  hxd.Math.max(0, v * (heightMap.width - 1) - 0.5);
+			var x = hxd.Math.floor(coordsX);
+			var y = hxd.Math.floor(coordsY);
+			var ratioX = coordsX - x;
+			var ratioY = coordsY - y;
+			var oppositeU = 1 - ratioX;
+			var oppositeV = 1 - ratioY;
+			var result = (pixels.getPixelF(x, y).r * oppositeU + pixels.getPixelF( Std.int(hxd.Math.min(x + 1, pixels.width)), y).r * ratioX) * oppositeV +
+                   (pixels.getPixelF(x,  Std.int(hxd.Math.min(y + 1, pixels.height))).r * oppositeU +
+				   pixels.getPixelF(Std.int(hxd.Math.min(x + 1, pixels.width)), Std.int(hxd.Math.min(y + 1, pixels.height))).r * ratioX) * ratioY;
+			return result;
+		}
+		else{
+			var x = hxd.Math.floor(u * (heightMap.width - 2));
+			var y = hxd.Math.floor(v * (heightMap.height - 2));
+			return pixels.getPixelF(x, y).r;
+		}
+	}
+
+	public override function dispose() {
+		if(heightMap != null) heightMap.dispose();
+		if(surfaceIndexMap != null) surfaceIndexMap.dispose();
+		for(i in 0 ... surfaceWeights.length)
+			if( surfaceWeights[i] != null) surfaceWeights[i].dispose();
+	}
+
+	override function emit(ctx:RenderContext){
+		if( getTerrain().surfaceArray == null) return;
+		var bounds = getBounds();
+		if(bounds != null){
+			if(ctx.camera.getFrustum().hasBounds(bounds))
+				super.emit(ctx);
+		}else
+			super.emit(ctx);
+	}
+
+	override function sync(ctx:RenderContext) {
+		if( getTerrain().surfaceArray == null) return;
+		shader.heightMap = heightMap;
+		shader.heightMapSize = heightMap.width;
+		shader.primSize = getTerrain().tileSize;
+		shader.cellSize = getTerrain().cellSize;
+		shader.showGrid = getTerrain().showGrid;
+		shader.surfaceIndexMap = surfaceIndexMap;
+		shader.albedoTextures = getTerrain().surfaceArray.albedo;
+		shader.normalTextures = getTerrain().surfaceArray.normal;
+		shader.pbrTextures = getTerrain().surfaceArray.pbr;
+		shader.weightTextures = surfaceWeightArray;
+		shader.weightCount = surfaceCount;
+		shader.surfaceParams = getTerrain().surfaceArray.params;
+		shader.secondSurfaceParams = getTerrain().surfaceArray.secondParams;
+		shader.tileIndex = new h3d.Vector(tileX, tileY);
+		shader.parallaxAmount = getTerrain().parallaxAmount;
+		shader.minStep = getTerrain().parallaxMinStep;
+		shader.maxStep = getTerrain().parallaxMaxStep;
+		shader.heightBlendStrength = getTerrain().heightBlendStrength;
+		shader.heightBlendSharpness = getTerrain().heightBlendSharpness;
+	}
+
+	inline function getTerrain(){
+		return Std.instance(parent, Terrain);
+	}
+}

+ 43 - 3
h3d/shader/pbr/Brush.hx

@@ -6,6 +6,8 @@ class Brush extends hxsl.Shader {
 
 		@:import h3d.shader.Base2d;
 		@const var normalize : Bool;
+		@const var clamp : Bool;
+		@const var generateIndex : Bool;
 
 		@param var strength : Float;
 		@param var weightTextures : Sampler2DArray;
@@ -14,21 +16,59 @@ class Brush extends hxsl.Shader {
 		@param var targetIndex : Int;
 		@param var size : Float;
 		@param var pos : Vec3;
+		@param var mask : Array<Vec4, 4>;
 
 		function fragment() {
-			if(normalize){
+			if(clamp){
+				var tileUV = pos.xy + calculatedUV * size;
+				var count = 0;
+				var smallestWeightIndex = -1;
+				var smallestWeight = 1.0;
+				for(i in 0 ... weightCount){
+					var w = weightTextures.get(vec3(tileUV, i)).r;
+					if(w > 0.0){
+						count++;
+						if(i != refIndex && smallestWeight > w){
+							smallestWeight = w;
+							smallestWeightIndex = i;
+						}
+					}
+				}
+				pixelColor = weightTextures.get(vec3(tileUV, targetIndex)).rgba;
+				if(count > 3 && targetIndex == smallestWeightIndex)
+					pixelColor = vec4(0);
+			}
+			else if(normalize){
 				var tileUV = pos.xy + calculatedUV * size ;
 				var refValue = weightTextures.get(vec3(tileUV, refIndex)).r;
 				var sum = 0.0;
 				for(i in 0 ... weightCount)
 					if(i != refIndex) sum += weightTextures.get(vec3(tileUV, i)).r;
-				var targetSum = min(1 - refValue, sum);
-
+				var targetSum = 1 - refValue;
 				pixelColor = vec4(vec3((weightTextures.get(vec3(tileUV, targetIndex)).rgb / sum) * targetSum), 1.0);
+
+				if(targetIndex == refIndex){
+					pixelColor = mix(vec4(1), vec4(refValue), ceil(min(1,sum)));
+				}
+			}
+			else if(generateIndex){
+				var tileUV = pos.xy + calculatedUV * size;
+				var indexes = vec4(0);
+				var curMask = 0;
+				for(i in 0 ... weightCount){
+					var w = weightTextures.get(vec3(tileUV, i)).r;
+					if( w > 0 && curMask < 3){
+						indexes += mask[curMask] * i / 255.0;
+						curMask++;
+					}
+				}
+				pixelColor = indexes;
 			}
 			else{
 				pixelColor = vec4(textureColor * strength);
 			}
 		}
 	}
+
+
 }

+ 110 - 102
h3d/shader/pbr/Terrain.hx

@@ -17,43 +17,37 @@ class Terrain extends hxsl.Shader {
 		@param var normalTextures : Sampler2DArray;
 		@param var pbrTextures : Sampler2DArray;
 		@param var weightTextures : Sampler2DArray;
-		@param var parallaxAmount : Float;
 		@param var weightCount : Int;
+		@param var heightBlendStrength : Float;
+		@param var heightBlendSharpness : Float;
+
+		@param var parallaxAmount : Float;
+		@param var minStep : Int;
+		@param var maxStep : Int;
+
 		@param var surfaceParams : Array<Vec4, 8>;
 		@param var secondSurfaceParams : Array<Vec4, 8>;
 		@param var tileIndex : Vec2;
 
 		@const var showGrid : Bool;
-		@const var useHeightBlend : Bool;
 
 		var calculatedUV : Vec2;
 		var terrainUV : Vec2;
 		var TBN : Mat3;
 		var worldNormal : Vec3;
-
-		function computeNormal(uv: Vec2) : Vec3 {
-			var offset = vec2(1.0/heightMapSize, 0) * heightMapSize / (primSize / cellSize);
-			var base = heightMap.get(uv).r;
-			var ddx = heightMap.get(uv + offset.xy).r;
-			var ddy = heightMap.get(uv + offset.yx).r;
-			var normal = normalize(vec3(base - ddx, base - ddy, 0.1));
-			normal = (normal * global.modelView.mat3()).normalize();
-			worldNormal = normal;
-			return normal;
-		}
+		var emissiveValue : Float;
+		var metalnessValue : Float;
+		var roughnessValue : Float;
+		var occlusionValue : Float;
 
 		function vertex() {
+			terrainUV = input.position.xy / primSize * (heightMapSize - 1) / heightMapSize + 0.5 / heightMapSize;
 			calculatedUV = input.position.xy / primSize;
-			terrainUV = (calculatedUV * (heightMapSize - 2)) / heightMapSize;
-			terrainUV += 0.5 / heightMapSize;
 			transformedPosition += (vec3(0,0, heightMap.get(terrainUV).r) * global.modelView.mat3());
-			transformedNormal = computeNormal(terrainUV);
-			var tangent = normalize(cross(transformedNormal, vec3(0,1,0)));
-			var bitangent = normalize(cross(transformedNormal,vec3(1,0,0)));
-			TBN = mat3(tangent, bitangent, transformedNormal);
+			TBN = mat3(normalize(cross(transformedNormal, vec3(0,1,0))), normalize(cross(transformedNormal,vec3(-1,0,0))), transformedNormal);
 		}
 
-		function getPOMUV( uv : Vec2, surfaceIndex : Int, minLayers : Int, maxLayers : Int, amount: Float) : Vec2 {
+		function getPOMUV( uv : Vec2, surfaceIndex : Int) : Vec2 {
 			var viewWS = (camera.position - transformedPosition).normalize();
 			var viewNS : Vec3;
 			{
@@ -64,20 +58,20 @@ class Terrain extends hxsl.Shader {
 				viewNS = vec3(viewWS.dot(tanX), viewWS.dot(tanY), viewWS.dot(n)).normalize();
 			}
 
-			var numLayers = mix(float(maxLayers), float(minLayers), abs(viewNS.z));
+			var numLayers = mix(float(maxStep), float(minStep), abs(viewNS.z));
 			var layerDepth = 1 / numLayers;
 			var curLayerDepth = 0.;
-			var delta = (viewNS.xy / viewNS.z) * amount / numLayers;
+			var delta = (viewNS.xy / viewNS.z) * parallaxAmount / numLayers * 1.0/surfaceParams[surfaceIndex].x;
 			var curUV = uv;
-			var curDepth = 1 - pbrTextures.get(vec3(curUV, surfaceIndex)).a;
+			var curDepth = 1 - pbrTextures.get(getsurfaceUV(surfaceIndex, curUV)).a;
 			while( curLayerDepth < curDepth ) {
 				curUV += delta;
-				curDepth =  1 - pbrTextures.get(vec3(curUV, surfaceIndex)).a;
+				curDepth =  1 - pbrTextures.get(getsurfaceUV(surfaceIndex, curUV)).a;
 				curLayerDepth += layerDepth;
 			}
 			var prevUV = curUV - delta;
 			var after = curDepth - curLayerDepth;
-			var before =  (1 - pbrTextures.get(vec3(prevUV, surfaceIndex)).a) - curLayerDepth + layerDepth;
+			var before = (1 - pbrTextures.get(vec3(prevUV, surfaceIndex)).a) - curLayerDepth + layerDepth;
 			return mix(curUV, prevUV, after / (after - before));
 		}
 
@@ -91,87 +85,101 @@ class Terrain extends hxsl.Shader {
 			return surfaceUV;
 		}
 
-		var emissiveValue : Float;
-		var metalnessValue : Float;
-		var roughnessValue : Float;
-		var occlusionValue : Float;
 		function fragment() {
-
-			if(useHeightBlend){
-				var curTexIndex = 0;
-				var curWeight = 0.0;
-				var curUV = vec2(0);
-				for(i in 0...weightCount){
-					var uv = getPOMUV(calculatedUV, i, 64, 128, parallaxAmount);
-					var h = pbrTextures.get(getsurfaceUV(i, uv)).a;
-					var a = weightTextures.get(vec3(uv, i)).r;
-					if(h * a > curWeight){
-						curTexIndex = i;
-						curWeight = h * a;
-						curUV = getsurfaceUV(i, uv).xy;
-					}
-				}
-				var albedo = albedoTextures.get(vec3(curUV, curTexIndex)).rgb;
-				var normal = unpackNormal(normalTextures.get(vec3(curUV, curTexIndex)).rgba);
-				var pbr = pbrTextures.get(vec3(curUV, curTexIndex)).rgba;
-
-				/*var albedo = albedoTextures.get(getsurfaceUV(curTexIndex)).rgb;
-				var normal = unpackNormal(normalTextures.get(getsurfaceUV(curTexIndex)).rgba);
-				var pbr = pbrTextures.get(getsurfaceUV(curTexIndex)).rgba;*/
-
-				var index : Int = int(surfaceIndexMap.get(calculatedUV).r * 255);
-				pixelColor = vec4(albedo, 1.0);
-				transformedNormal = normalize(normal) * TBN;
-				roughnessValue = 1 - pbr.g * pbr.g;
-				metalnessValue = pbr.r;
-				occlusionValue = pbr.b;
-				emissiveValue = 0;
-
-				if(curWeight <= 0) {
-					pixelColor = vec4(0,0,0, 1.0);
-					transformedNormal = vec3(0,0,1) * TBN;
-					roughnessValue = 1;
-					metalnessValue = 0;
-					occlusionValue = 1;
-					emissiveValue = 0;
-				}
-			}
-			else{
-				var albedo = vec3(0);
-				var normal = vec4(0);
-				var pbr = vec4(0);
-				for(i in 0...weightCount){
-					var a = weightTextures.get(vec3(calculatedUV, i)).r;
-					albedo += albedoTextures.get(getsurfaceUV(i, calculatedUV)).rgb * a;
-					pbr += pbrTextures.get(getsurfaceUV(i, calculatedUV)).rgba * a;
-					normal += normalTextures.get(getsurfaceUV(i, calculatedUV)).rgba * a;
-				}
-				normal = vec4(unpackNormal(normal), 0.0);
-				pixelColor = vec4(albedo, 0.0);
-				transformedNormal = normalize(normal.xyz) * TBN;
-				roughnessValue = 1 - pbr.g * pbr.g;
-				metalnessValue = pbr.r;
-				occlusionValue = pbr.b;
-				emissiveValue = 0;
-			}
-
-			// Tri-planar Mapping
-			/*var coords = vec3(input.position.xy / primSize, heightMap.get(terrainUV).r / primSize);
-			var blending = abs( worldNormal );
-			blending = normalize(max(blending, 0.00001)); // Force weights to sum to 1.0
-			var b = (blending.x + blending.y + blending.z);
-			blending /= vec3(b, b, b);
-			var xaxis = texture( albedoTextures, vec3(coords.yz, index));
-			var yaxis = texture( albedoTextures, vec3(coords.xz, index));
-			var zaxis = texture( albedoTextures, vec3(coords.xy, index));
-			// blend the results of the 3 planar projections.
-			var tex = xaxis * blending.x + yaxis * blending.y + zaxis * blending.z;
-			pixelColor = tex;*/
+			// Extract participating surfaces from the pixel
+			var texIndex = surfaceIndexMap.get(calculatedUV).rgb;
+			var i1 : Int = int(texIndex.r * 255);
+			var i2 : Int = int(texIndex.g * 255);
+			var i3 : Int = int(texIndex.b * 255);
+			var uv1 = getPOMUV(calculatedUV, i1);
+			var uv2 = getPOMUV(calculatedUV, i2);
+			var uv3 = getPOMUV(calculatedUV, i3);
+			var surfaceUV1 = getsurfaceUV(i1, uv1);
+			var surfaceUV2 = getsurfaceUV(i2, uv2);
+			var surfaceUV3 = getsurfaceUV(i3, uv3);
+			var pbr1 = pbrTextures.get(surfaceUV1).rgba;
+			var pbr2 = pbrTextures.get(surfaceUV2).rgba;
+			var pbr3 = pbrTextures.get(surfaceUV3).rgba;
+			var albedo1 = albedoTextures.get(surfaceUV1).rgb;
+			var albedo2 = albedoTextures.get(surfaceUV2).rgb;
+			var albedo3 = albedoTextures.get(surfaceUV3).rgb;
+			var normal1 = normalTextures.get(surfaceUV1).rgba;
+			var normal2 = normalTextures.get(surfaceUV2).rgba;
+			var normal3 = normalTextures.get(surfaceUV3).rgba;
+			var h1 = pbr1.a;
+			var h2 = pbr2.a;
+			var h3 = pbr3.a;
+			var aw1 = weightTextures.get(vec3(uv1, i1)).r;
+			var aw2 = weightTextures.get(vec3(uv2, i2)).r;
+			var aw3 = weightTextures.get(vec3(uv3, i3)).r;
+
+			// Sum of each surface
+			var albedo = vec3(0);
+			var normal = vec4(0,0,0,0);
+			var pbr = vec4(0);
+			var weightSum = 0 + heightBlendSharpness * 0;
+
+			// Keep the surface with the heightest weight for sharpness
+			var maxAlbedo = vec3(0);
+			var maxPbr = vec4(0);
+			var maxNormal = vec4(0);
+			var curMaxWeight = -1.0;
+
+			// Alpha / Height Blend
+			var b1 = mix(aw1, aw1 * h1, heightBlendStrength);
+			var b2 = mix(aw2, aw2 * h2, heightBlendStrength);
+			var b3 = mix(aw3, aw3 * h3, heightBlendStrength);
+			albedo += albedo1 * b1;
+			albedo += albedo2 * b2;
+			albedo += albedo3 * b3;
+			pbr += pbr1 * b1;
+			pbr += pbr2 * b2;
+			pbr += pbr3 * b3;
+			normal += normal1 * b1;
+			normal += normal2 * b2;
+			normal += normal3 * b3;
+
+			// Normalisation
+			weightSum = b1 + b2 + b3;
+			albedo /= vec3(weightSum);
+			pbr /= vec4(weightSum);
+			normal /= vec4(weightSum);
+
+			// Find the max
+			var maxW = clamp(ceil(b1 - curMaxWeight), 0 ,1);
+			curMaxWeight = mix(curMaxWeight, b1, maxW);
+			maxAlbedo = mix(maxAlbedo, albedo1, maxW);
+			maxPbr = mix(maxPbr, pbr1, maxW);
+			maxNormal = mix(maxNormal, normal1, maxW);
+			maxW = clamp(ceil(b2 - curMaxWeight), 0 ,1);
+			curMaxWeight = mix(curMaxWeight, b2, maxW);
+			maxAlbedo = mix(maxAlbedo, albedo2, maxW);
+			maxPbr = mix(maxPbr, pbr2, maxW);
+			maxNormal = mix(maxNormal, normal2, maxW);
+			maxW = clamp(ceil(b3 - curMaxWeight), 0 ,1);
+			curMaxWeight = mix(curMaxWeight, b3, maxW);
+			maxAlbedo = mix(maxAlbedo, albedo3, maxW);
+			maxPbr = mix(maxPbr, pbr3, maxW);
+			maxNormal = mix(maxNormal, normal3, maxW);
+
+			// Sharpness
+			albedo = mix(albedo, maxAlbedo, heightBlendSharpness);
+			pbr = mix(normal, maxPbr, heightBlendSharpness);
+			normal = mix(normal, maxNormal, heightBlendSharpness);
+
+			// Output
+			normal = vec4(unpackNormal(normal), 0.0);
+			pixelColor = vec4(albedo, 0.0);
+			//transformedNormal = normalize(normal.xyz) * TBN;
+			roughnessValue = 1 - pbr.g * pbr.g;
+			metalnessValue = pbr.r;
+			occlusionValue = pbr.b;
+			emissiveValue = 0;
 
 			if(showGrid){
 				var gridColor = vec4(1,0,0,1);
 				var tileEdgeColor = vec4(1,1,0,1);
-				var grid = ((input.position.xy.mod(cellSize) / cellSize ) - 0.5) * 2.0;
+				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));
@@ -180,7 +188,7 @@ class Terrain extends hxsl.Shader {
 				metalnessValue =  mix(metalnessValue, 0, emissiveValue);
 				roughnessValue = mix(roughnessValue, 1, emissiveValue);
 				occlusionValue = mix(occlusionValue, 1, emissiveValue);
-				transformedNormal = mix(transformedNormal, vec3(0,0,1), emissiveValue);
+				transformedNormal = mix(transformedNormal, vec3(0,1,0), emissiveValue);
 			}
 		}
 	};