Ver código fonte

Update Terrain

ShiroSmith 7 anos atrás
pai
commit
69db0d93fe
3 arquivos alterados com 520 adições e 146 exclusões
  1. 4 2
      hide/comp/Scene.hx
  2. 482 144
      hide/prefab/Terrain.hx
  3. 34 0
      hide/prefab/terrain/TilePreview.hx

+ 4 - 2
hide/comp/Scene.hx

@@ -198,7 +198,7 @@ class Scene extends Component implements h3d.IDrawable {
 		}
 		}
 		var path = ide.getPath(img.entry.path);
 		var path = ide.getPath(img.entry.path);
 		var img = new Element('<img src="file://$path"/>');
 		var img = new Element('<img src="file://$path"/>');
-		img.on("load", function() {
+		function onLoaded() {
 			setCurrent();
 			setCurrent();
 			var bmp : js.html.ImageElement = cast img[0];
 			var bmp : js.html.ImageElement = cast img[0];
 			var t;
 			var t;
@@ -210,12 +210,14 @@ class Scene extends Component implements h3d.IDrawable {
 			}
 			}
 			untyped bmp.ctx = { getImageData : function(_) return bmp, canvas : { width : 0, height : 0 } };
 			untyped bmp.ctx = { getImageData : function(_) return bmp, canvas : { width : 0, height : 0 } };
 			engine.driver.uploadTextureBitmap(t, cast bmp, 0, 0);
 			engine.driver.uploadTextureBitmap(t, cast bmp, 0, 0);
+			t.realloc = onLoaded;
 			t.flags.unset(Loading);
 			t.flags.unset(Loading);
 			if( onReady != null ) {
 			if( onReady != null ) {
 				onReady(t);
 				onReady(t);
 				onReady = null;
 				onReady = null;
 			}
 			}
-		});
+		}
+		img.on("load", onLoaded);
 		var w = js.node.Fs.watch(path, function(_, _) {
 		var w = js.node.Fs.watch(path, function(_, _) {
 			img.attr("src", 'file://$path?t='+Date.now().getTime());
 			img.attr("src", 'file://$path?t='+Date.now().getTime());
 		});
 		});

+ 482 - 144
hide/prefab/Terrain.hx

@@ -5,6 +5,9 @@ import hxd.Key as K;
 class BrushMode {
 class BrushMode {
 	public var accumulate = false;
 	public var accumulate = false;
 	public var substract = false;
 	public var substract = false;
+	public var sculptMode = false;
+
+	public function new(){}
 }
 }
 
 
 class StrokeBuffer {
 class StrokeBuffer {
@@ -18,6 +21,8 @@ class StrokeBuffer {
 	public function new(size, x, y){
 	public function new(size, x, y){
 		tex = new h3d.mat.Texture(size,size, [Target], R16F);
 		tex = new h3d.mat.Texture(size,size, [Target], R16F);
 		tempTex = new h3d.mat.Texture(size,size, [Target], R16F);
 		tempTex = new h3d.mat.Texture(size,size, [Target], R16F);
+		tex.filter = Nearest;
+		tempTex.filter = Nearest;
 		this.x = x;
 		this.x = x;
 		this.y = y;
 		this.y = y;
 		used = false;
 		used = false;
@@ -30,6 +35,70 @@ class StrokeBuffer {
 	}
 	}
 }
 }
 
 
+class TilePreview extends h3d.scene.Mesh {
+	public var used = false;
+	public var heightMap : h3d.mat.Texture;
+	public var shader : hide.prefab.terrain.TilePreview;
+
+	public function new(prim, parent){
+		super(prim, null, parent);
+		material.setDefaultProps("ui");
+		material.shadows = false;
+		material.blendMode = Alpha;
+		material.color = new h3d.Vector(1,0,0,0.5);
+		shader = new hide.prefab.terrain.TilePreview();
+		material.mainPass.addShader(shader);
+	}
+
+	override function sync(ctx : h3d.impl.RenderContext) {
+		shader.heightMap = heightMap;
+		shader.heightMapSize = heightMap.width;
+		shader.primSize = Std.instance(parent, h3d.scene.pbr.Terrain).tileSize;
+	}
+}
+
+class BrushPreview {
+
+	var terrain : h3d.scene.pbr.Terrain;
+	var tiles : Array<TilePreview> = [];
+
+	public function new(terrain){
+		this.terrain = terrain;
+	}
+
+	public function addPreviewMeshAt(x : Int, y : Int, brush : Brush, brushPos : h3d.Vector) : TilePreview {
+		var tilePreview = null;
+		for(tile in tiles){
+			if(tile.used) continue;
+			tilePreview = tile;
+		}
+		if(tilePreview == null){
+			tilePreview = new TilePreview(terrain.grid, terrain);
+			tiles.push(tilePreview);
+		}
+		tilePreview.used = true;
+		tilePreview.heightMap = terrain.getTile(x,y).heightMap;
+		var pos = new h3d.Vector(x * terrain.tileSize, y * terrain.tileSize);
+		tilePreview.setPosition(pos.x, pos.y, pos.z + 0.1 * terrain.scaleZ);
+		tilePreview.visible = true;
+		tilePreview.shader.brushTex = brush.tex;
+		tilePreview.shader.brushSize =  brush.size;
+		tilePreview.shader.brushPos = brushPos;
+		return tilePreview;
+	}
+	public function reset(){
+		for(tile in tiles){
+			tile.used = false;
+			tile.visible = false;
+		}
+	}
+
+	public function refreshMesh(){
+		for(tile in tiles)
+			tile.primitive = terrain.grid;
+	}
+}
+
 class Brush {
 class Brush {
 	public var name : String;
 	public var name : String;
 	public var size : Float;
 	public var size : Float;
@@ -38,6 +107,7 @@ class Brush {
 	public var tex : h3d.mat.Texture;
 	public var tex : h3d.mat.Texture;
 	public var bitmap : h2d.Bitmap;
 	public var bitmap : h2d.Bitmap;
 	public var texPath : String;
 	public var texPath : String;
+	public var index : Int = -1;
 
 
 	public function new(){
 	public function new(){
 
 
@@ -52,8 +122,8 @@ class Brush {
 		bitmap.setScale(scale);
 		bitmap.setScale(scale);
 	}
 	}
 
 
-	public function drawTo( target : h3d.mat.Texture, pos : h3d.Vector, tileSize : Float){
-		var texSize = target.width - 2;
+	public function drawTo( target : h3d.mat.Texture, pos : h3d.Vector, tileSize : Float, ?offset = 0){
+		var texSize = target.width + offset;
 		scaleForTex(tileSize, texSize);
 		scaleForTex(tileSize, texSize);
 		bitmap.setPosition(
 		bitmap.setPosition(
 						(pos.x * texSize - ( size / (tileSize / texSize) * 0.5 )),
 						(pos.x * texSize - ( size / (tileSize / texSize) * 0.5 )),
@@ -62,65 +132,218 @@ class Brush {
 	}
 	}
 }
 }
 
 
+typedef SurfaceProps = {
+	albedo : String,
+	normal : String,
+	pbr : String,
+	tilling : Float,
+	angle : Float,
+	offsetX : Float,
+	offsetY : Float
+};
+
 class Terrain extends Object3D {
 class Terrain extends Object3D {
 
 
 	public var tileSize = 1;
 	public var tileSize = 1;
 	public var cellSize = 1;
 	public var cellSize = 1;
-	public var heightMapResolution = 1;
-	public var weightMapResolution = 1;
+	public var heightMapResolution : Int = 1;
+	public var weightMapResolution : Int = 1;
 
 
+	var tmpSurfacesProps : Array<SurfaceProps> = [];
 	var terrain : h3d.scene.pbr.Terrain;
 	var terrain : h3d.scene.pbr.Terrain;
 
 
 	#if editor
 	#if editor
+	var brushPreview : BrushPreview;
 	var interactive : h2d.Interactive;
 	var interactive : h2d.Interactive;
 	var currentBrush : Brush;
 	var currentBrush : Brush;
-	var substractMode = false;
+	var currentSurface : h3d.scene.pbr.Terrain.Surface;
+	var brushMode : BrushMode;
 	var remainingDist = 0.0;
 	var remainingDist = 0.0;
 	var previewResolution = 256;
 	var previewResolution = 256;
-	var accumulate : Bool;
 	var lastPos : h3d.Vector;
 	var lastPos : h3d.Vector;
-
 	var grid : h3d.scene.Graphics;
 	var grid : h3d.scene.Graphics;
 	var showGrid : Bool;
 	var showGrid : Bool;
-	var currentSurfaceName: String;
 	var copyPass : h3d.pass.Copy;
 	var copyPass : h3d.pass.Copy;
 	var strokeBuffers : Array<StrokeBuffer> = [];
 	var strokeBuffers : Array<StrokeBuffer> = [];
+	var tmpTexPath : String;
+	var textureType = ["_Albedo", "_Normal", "_MetallicGlossAO"];
 	#end
 	#end
 
 
 	override function load( obj : Dynamic ) {
 	override function load( obj : Dynamic ) {
 		super.load(obj);
 		super.load(obj);
 		tileSize = obj.tileSize == null ? 1 : obj.tileSize;
 		tileSize = obj.tileSize == null ? 1 : obj.tileSize;
 		cellSize = obj.cellSize == null ? 1 : obj.cellSize;
 		cellSize = obj.cellSize == null ? 1 : obj.cellSize;
-		heightMapResolution = obj.heightMapResolution == null ? 1 : obj.heightMapResolution;
-		weightMapResolution = obj.weightMapResolution == null ? 1 : obj.weightMapResolution;
+		heightMapResolution = obj.heightMapResolution == null ? 1 : hxd.Math.ceil(obj.heightMapResolution);
+		weightMapResolution = obj.weightMapResolution == null ? 1 : hxd.Math.ceil(obj.weightMapResolution);
+		if( obj.surfaces!= null ) tmpSurfacesProps = obj.surfaces;
 	}
 	}
 
 
 	override function save() {
 	override function save() {
-		var o : Dynamic = super.save();
-		if( tileSize > 0 ) o.tileSize = tileSize;
-		if( cellSize > 0 ) o.cellSize = cellSize;
-		if( heightMapResolution > 0 ) o.heightMapResolution = heightMapResolution;
-		if( weightMapResolution > 0 ) o.weightMapResolution = weightMapResolution;
-		return o;
+		var obj : Dynamic = super.save();
+		if( tileSize > 0 ) obj.tileSize = tileSize;
+		if( cellSize > 0 ) obj.cellSize = cellSize;
+		if( heightMapResolution > 0 ) obj.heightMapResolution = hxd.Math.ceil(heightMapResolution);
+		if( weightMapResolution > 0 ) obj.weightMapResolution = hxd.Math.ceil(weightMapResolution);
+
+		var surfacesProps : Array<SurfaceProps> = [];
+		for(surface in terrain.surfaces){
+			var surfaceProps : SurfaceProps =
+			{
+				albedo : surface.albedo.name,
+				normal : surface.normal.name,
+				pbr : surface.pbr.name,
+				tilling : surface.tilling,
+				angle : surface.angle,
+				offsetX : surface.offset.x,
+				offsetY : surface.offset.y
+			};
+			surfacesProps.push(surfaceProps);
+		}
+		obj.surfaces = surfacesProps;
+
+		return obj;
+	}
+
+	function saveHeightTextures(ctx : Context){
+		if(ctx.shared.currentPath == null) return;
+		var dir = ctx.shared.currentPath.split(".l3d")[0] + "_terrain";
+		for(tile in terrain.tiles){
+			var pixels = tile.heightMap.capturePixels();
+			var name = tile.tileX + "_" + tile.tileY + "_" + "h";
+			ctx.shared.saveTexture(name, pixels.bytes, dir, "heightMap");
+		}
+	}
+
+	function saveWeightTextures(ctx : Context){
+		if(ctx.shared.currentPath == null) return;
+		var dir = ctx.shared.currentPath.split(".l3d")[0] + "_terrain";
+		for(tile in terrain.tiles){
+			var surfaceIndex = 0;
+			for(surfaceWeight in tile.surfaceWeights){
+				var pixels = surfaceWeight.capturePixels();
+				var bytes = pixels.toPNG();
+				var name = tile.tileX + "_" + tile.tileY + "_" + surfaceIndex + "_" + "w";
+				ctx.shared.saveTexture(name, bytes, dir, "png");
+				surfaceIndex++;
+			}
+		}
+	}
+
+	function loadHeightTextures(ctx : Context){
+		var dir = ctx.shared.currentPath.split(".l3d")[0] + "_terrain";
+		var files = sys.FileSystem.readDirectory(hide.Ide.inst.getPath(dir));
+		for(file in files){
+			var texName = file.split(".heightMap")[0];
+			var coords = texName.split("_");
+			if(coords[2] != "h") continue;
+			var x = Std.parseInt(coords[0]);
+			var y = Std.parseInt(coords[1]);
+			var bytes = ctx.shared.loadBytes(dir + "/" + file);
+			if(bytes == null) continue;
+			var pixels : hxd.Pixels.PixelsFloat = new hxd.Pixels(heightMapResolution + 2, heightMapResolution + 2, bytes, RGBA32F);
+			var tile = terrain.createTile(x, y);
+			tile.heightMap.uploadPixels(pixels);
+		}
+	}
+
+	function loadWeightTextures(ctx : Context){
+		var dir = ctx.shared.currentPath.split(".l3d")[0] + "_terrain";
+		var files = sys.FileSystem.readDirectory(hide.Ide.inst.getPath(dir)); // TODO : FIXME
+		for(file in files){
+			var texName = file.split(".png")[0];
+			var coords = texName.split("_");
+			if(coords[3] != "w") continue;
+			var x = Std.parseInt(coords[0]);
+			var y = Std.parseInt(coords[1]);
+			var i = Std.parseInt(coords[2]);
+			var tile = terrain.createTile(x, y);
+			var tex = ctx.loadTexture(dir + "/" + file);
+			function wait() {
+				if( tex.flags.has(Loading) )
+					haxe.Timer.delay(wait, 1);
+				else {
+					tile.uploadWeightMap(tex, i);
+					tex.dispose();
+				}
+			}
+			wait();
+		}
+	}
+
+	function initTexture(ctx : Context, path : String, ?wrap : h3d.mat.Data.Wrap) : h3d.mat.Texture {
+		if(path != null){
+			var tex = ctx.shared.loadTexture(hide.Ide.inst.getPath(path));
+			function wait() {
+				if( tex.flags.has(Loading) )
+					haxe.Timer.delay(wait, 1);
+			}
+			wait();
+			if(tex != null ) tex.wrap = wrap == null ? Repeat : wrap;
+			return tex;
+		}
+		return null;
 	}
 	}
 
 
 	override function makeInstance(ctx:Context):Context {
 	override function makeInstance(ctx:Context):Context {
 		ctx = ctx.clone(this);
 		ctx = ctx.clone(this);
 
 
 		terrain = new h3d.scene.pbr.Terrain(ctx.local3d);
 		terrain = new h3d.scene.pbr.Terrain(ctx.local3d);
+		terrain.cellCount = getCellCount();
 		terrain.cellSize = getCellSize();
 		terrain.cellSize = getCellSize();
-		terrain.tileSize = tileSize;
+		terrain.tileSize = terrain.cellCount * terrain.cellSize;
+		terrain.refreshMesh();
 		terrain.heightMapResolution = heightMapResolution;
 		terrain.heightMapResolution = heightMapResolution;
 		terrain.weightMapResolution = weightMapResolution;
 		terrain.weightMapResolution = weightMapResolution;
-		terrain.createTile(0,0);
-		terrain.refresh();
-
 
 
 		ctx.local3d = terrain;
 		ctx.local3d = terrain;
 		ctx.local3d.name = name;
 		ctx.local3d.name = name;
 
 
+		for(surfaceProps in tmpSurfacesProps){
+			var surface = terrain.addEmptySurface();
+			var albedo = ctx.shared.loadTexture(hide.Ide.inst.getPath(surfaceProps.albedo));
+			var normal = ctx.shared.loadTexture(hide.Ide.inst.getPath(surfaceProps.normal));
+			var pbr = ctx.shared.loadTexture(hide.Ide.inst.getPath(surfaceProps.pbr));
+			function wait() {
+				if( albedo.flags.has(Loading) || normal.flags.has(Loading)|| pbr.flags.has(Loading))
+					haxe.Timer.delay(wait, 1);
+				else{
+					surface.albedo = albedo;
+					surface.normal = normal;
+					surface.pbr = pbr;
+					surface.offset.x = surfaceProps.offsetX;
+					surface.offset.y = surfaceProps.offsetY;
+					surface.angle = surfaceProps.angle;
+					surface.tilling = surfaceProps.tilling;
+					albedo.dispose();
+					normal.dispose();
+					pbr.dispose();
+				}
+			}
+			wait();
+		}
+
+		function waitAll() {
+			var ready = true;
+			for(surface in terrain.surfaces)
+				if(surface == null || surface.albedo == null || surface.normal == null || surface.pbr == null ){
+					ready = false;
+					break;
+				}
+			if(ready){
+				terrain.generateSurfaceArray();
+				loadWeightTextures(ctx);
+				loadHeightTextures(ctx);
+			}
+			else
+				haxe.Timer.delay(waitAll, 1);
+		}
+		waitAll();
+
 		#if editor
 		#if editor
+		brushPreview = new BrushPreview(terrain);
+		brushPreview.refreshMesh();
 		currentBrush = new Brush();
 		currentBrush = new Brush();
+		brushMode = new BrushMode();
 		copyPass = new h3d.pass.Copy();
 		copyPass = new h3d.pass.Copy();
 		#end
 		#end
 
 
@@ -129,7 +352,48 @@ class Terrain extends Object3D {
 		return ctx;
 		return ctx;
 	}
 	}
 
 
+	function getCellCount(){
+		var resolution = Math.max(0.1, cellSize);
+		var cellCount = Math.ceil(Math.min(1000, tileSize / resolution));
+		return cellCount;
+	}
+
+	function getCellSize(){
+		var cellCount = getCellCount();
+		var finalCellSize = tileSize / cellCount;
+		return finalCellSize;
+	}
+
+	override function updateInstance( ctx: Context, ?propName : String ) {
+		super.updateInstance(ctx, null);
+
+		#if editor
+		if(propName == "currentSurface.tilling"
+		|| propName == "currentSurface.offset.x"
+		|| propName == "currentSurface.offset.y"
+		|| propName == "currentSurface.angle"
+		|| propName == "currentSurface.parallaxAmount")
+			terrain.updateSurfaceParams();
+
+		if(propName == "tileSize" || propName == "cellSize"){
+			terrain.cellCount = getCellCount();
+			terrain.cellSize = getCellSize();
+			terrain.tileSize = terrain.cellCount * terrain.cellSize;
+			terrain.refreshMesh();
+			brushPreview.refreshMesh();
+		}
+
+		if(propName == "heightMapResolution" || propName == "weightMapResolution"){
+			terrain.heightMapResolution = heightMapResolution;
+			terrain.weightMapResolution = weightMapResolution;
+			terrain.refreshTex();
+			updateStrokeBuffers(heightMapResolution + 2);
+		}
+		#end
+	}
+
 	#if editor
 	#if editor
+
 	function getStrokeBuffer(x, y){
 	function getStrokeBuffer(x, y){
 		for(strokebuffer in strokeBuffers)
 		for(strokebuffer in strokeBuffers)
 			if((strokebuffer.x == x && strokebuffer.y == y) || strokebuffer.used == false){
 			if((strokebuffer.x == x && strokebuffer.y == y) || strokebuffer.used == false){
@@ -148,6 +412,8 @@ class Terrain extends Object3D {
 			if(strokeBuffer.tempTex != null) strokeBuffer.tempTex.dispose();
 			if(strokeBuffer.tempTex != null) strokeBuffer.tempTex.dispose();
 			strokeBuffer.tex = new h3d.mat.Texture(size,size, [Target], R16F);
 			strokeBuffer.tex = new h3d.mat.Texture(size,size, [Target], R16F);
 			strokeBuffer.tempTex = new h3d.mat.Texture(size,size, [Target], R16F);
 			strokeBuffer.tempTex = new h3d.mat.Texture(size,size, [Target], R16F);
+			strokeBuffer.tex.filter = Nearest;
+			strokeBuffer.tempTex.filter = Nearest;
 		}
 		}
 	}
 	}
 
 
@@ -163,7 +429,7 @@ class Terrain extends Object3D {
 		for(strokeBuffer in strokeBuffers){
 		for(strokeBuffer in strokeBuffers){
 			if(strokeBuffer.used == true){
 			if(strokeBuffer.used == true){
 				var tile = terrain.getTile(strokeBuffer.x, strokeBuffer.y);
 				var tile = terrain.getTile(strokeBuffer.x, strokeBuffer.y);
-				copyPass.apply(strokeBuffer.tex, strokeBuffer.prevTex, substractMode ? Sub : Add);
+				copyPass.apply(strokeBuffer.tex, strokeBuffer.prevTex, brushMode.substract ? Sub : Add);
 				strokeBuffer.tempTex = tile.heightMap;
 				strokeBuffer.tempTex = tile.heightMap;
 				tile.heightMap = strokeBuffer.prevTex;
 				tile.heightMap = strokeBuffer.prevTex;
 			}
 			}
@@ -175,88 +441,43 @@ class Terrain extends Object3D {
 			if(strokeBuffer.used == true){
 			if(strokeBuffer.used == true){
 				var tile = terrain.getTile(strokeBuffer.x, strokeBuffer.y);
 				var tile = terrain.getTile(strokeBuffer.x, strokeBuffer.y);
 				copyPass.apply(strokeBuffer.prevTex, tile.heightMap);
 				copyPass.apply(strokeBuffer.prevTex, tile.heightMap);
-				copyPass.apply(strokeBuffer.tex, tile.heightMap, substractMode ? Sub : Add);
+				copyPass.apply(strokeBuffer.tex, tile.heightMap, brushMode.substract ? Sub : Add);
 			}
 			}
 		}
 		}
 	}
 	}
-	#end
-
-	function getCellSize(){
-		var resolution = Math.max(0.1, cellSize);
-		var cellCount = Math.ceil(Math.min(100, tileSize / resolution));
-		var finalCellSize = 1.0 / cellCount * tileSize;
-		return finalCellSize;
-	}
-
-	function checkTexture(tex : h3d.mat.Texture, size, format) : h3d.mat.Texture {
-		if(tex == null || tex.width != size || tex.height != size || tex.format != format){
-			if(tex != null) tex.dispose();
-			return new h3d.mat.Texture(size, size, [Target], format);
-		}
-		return tex;
-	}
-
-	override function updateInstance( ctx: Context, ?propName : String ) {
-		super.updateInstance(ctx, null);
-
-		if(propName == "tileSize" || propName == "cellSize"){
-			terrain.cellSize = getCellSize();
-			terrain.tileSize = tileSize;
-			terrain.refreshMesh();
-		}
-
-		#if editor
-
-		if(propName == "heightMapResolution" || propName == "weightMapResolution"){
-			terrain.heightMapResolution = heightMapResolution;
-			terrain.weightMapResolution = weightMapResolution;
-			terrain.refreshTex();
-			updateStrokeBuffers(heightMapResolution+2);
-		}
-
-		if(currentBrush.isValid())
-			currentBrush.bitmap.color = new h3d.Vector(currentBrush.strength);
-
-		#end
-	}
-
-	#if editor
 
 
-	public function projectToGround(ray: h3d.col.Ray, z) {
+	public function projectToGround(ray: h3d.col.Ray) {
 		var minDist = -1.;
 		var minDist = -1.;
-		var zPlane = h3d.col.Plane.Z(z);
-		var pt = ray.intersect(zPlane);
+		var normal = terrain.getAbsPos().up();
+		var plane = h3d.col.Plane.fromNormalPoint(normal.toPoint(), new h3d.col.Point(terrain.getAbsPos().tx, terrain.getAbsPos().ty, terrain.getAbsPos().tz));
+		var pt = ray.intersect(plane);
 		if(pt != null) { minDist = pt.sub(ray.getPos()).length();}
 		if(pt != null) { minDist = pt.sub(ray.getPos()).length();}
 		return minDist;
 		return minDist;
 	}
 	}
 
 
-	function screenToWorld( u : Float, v : Float, z, ctx : Context) {
+	function screenToWorld( u : Float, v : Float, ctx : Context) {
 		var camera = @:privateAccess ctx.local3d.getScene().camera;
 		var camera = @:privateAccess ctx.local3d.getScene().camera;
 		var ray = camera.rayFromScreen(u, v);
 		var ray = camera.rayFromScreen(u, v);
-		var dist = projectToGround(ray, z);
+		var dist = projectToGround(ray);
 		if(dist >= 0) { return ray.getPoint(dist); }
 		if(dist >= 0) { return ray.getPoint(dist); }
 		return null;
 		return null;
 	}
 	}
 
 
-	function drawBrushPreview( coords : h3d.Vector, ctx : Context){
-		/*currentBrush.bitmap.blendMode = Add;
-		currentBrush.scaleForTex(terrain.tileSize, previewResolution);
-		currentBrush.bitmap.setPosition(coords.x * previewResolution - (currentBrush.size / (tileSize/previewResolution))* 0.5, coords.y * previewResolution - (currentBrush.size / (tileSize/previewResolution))* 0.5);
-		currentBrush.bitmap.drawTo(brushPreview);*/
+	function drawBrushPreview( worldPos : h3d.Vector, ctx : Context){
+		brushPreview.reset();
+		var tiles = terrain.getTiles(worldPos, currentBrush.size / 2.0 , false);
+		for(tile in tiles){
+			var brushPos = tile.globalToLocal(worldPos.clone());
+			brushPos.scale3(1.0/terrain.tileSize);
+			brushPreview.addPreviewMeshAt(tile.tileX, tile.tileY, currentBrush, brushPos);
+		}
 	}
 	}
 
 
 	function drawBrush( from : h3d.Vector, to : h3d.Vector, ctx : Context){
 	function drawBrush( from : h3d.Vector, to : h3d.Vector, ctx : Context){
-
 		var dist = (to.sub(from)).length();
 		var dist = (to.sub(from)).length();
 		if(dist == 0){
 		if(dist == 0){
-			var tiles = terrain.getTiles(from, currentBrush.size / 2.0 +1, true);
-			for(tile in tiles){
-					var strokeBuffer = getStrokeBuffer(tile.tileX, tile.tileY);
-					if(strokeBuffer.used == false) strokeBuffer.linkTo(tile);
-					var localPos = tile.globalToLocal(from.clone());
-					localPos.scale3(1/tileSize);
-					currentBrush.drawTo(strokeBuffer.tex, localPos, tileSize);
-				}
+			if(brushMode.sculptMode) drawHeight(currentBrush, from);
+			else drawSurface(currentBrush, from);
 			return;
 			return;
 		}
 		}
 		else if(dist + remainingDist >= currentBrush.step){
 		else if(dist + remainingDist >= currentBrush.step){
@@ -274,15 +495,9 @@ class Terrain extends Object3D {
 				}else
 				}else
 					pos = pos.add(step);
 					pos = pos.add(step);
 
 
-				var tiles = terrain.getTiles(pos, currentBrush.size / 2.0 + 1.0 / tileSize, true);
+				if(brushMode.sculptMode) drawHeight(currentBrush, pos);
+				else drawSurface(currentBrush, pos);
 
 
-				for(tile in tiles){
-					var strokeBuffer = getStrokeBuffer(tile.tileX, tile.tileY);
-					if(strokeBuffer.used == false) strokeBuffer.linkTo(tile);
-					var localPos = tile.globalToLocal(pos.clone());
-					localPos.scale3(1/tileSize);
-					currentBrush.drawTo(strokeBuffer.tex, localPos, tileSize);
-				}
 				dist -= currentBrush.step - remainingDist;
 				dist -= currentBrush.step - remainingDist;
 				remainingDist = 0;
 				remainingDist = 0;
 			}
 			}
@@ -292,19 +507,66 @@ class Terrain extends Object3D {
 		}
 		}
 	}
 	}
 
 
+	public function drawSurface(brush : Brush, pos : h3d.Vector){
+		if(currentBrush.index == -1) return;
+		var tiles = terrain.getTiles(pos, currentBrush.size / 2.0 , true);
+		for(tile in tiles){
+			var localPos = tile.globalToLocal(pos.clone());
+			localPos.scale3(1.0/tileSize);
+			currentBrush.bitmap.color = new h3d.Vector(1);
+			var shader : h3d.shader.pbr.Brush = currentBrush.bitmap.getShader(h3d.shader.pbr.Brush);
+			if( shader == null ) shader = currentBrush.bitmap.addShader(new h3d.shader.pbr.Brush());
+			shader.normalize = false;
+
+			currentBrush.bitmap.blendMode = brushMode.substract ? Sub : Add ;
+			shader.strength = currentBrush.strength;
+			currentBrush.drawTo(tile.surfaceWeights[currentBrush.index], localPos, tileSize);
+			tile.generateWeightArray();
+
+			shader.normalize = true;
+			currentBrush.bitmap.blendMode = None;
+			shader.refIndex = currentBrush.index;
+			shader.weightTextures = tile.surfaceWeightArray;
+			shader.weightCount = tile.surfaceCount;
+			shader.size = currentBrush.size / tileSize;
+			shader.pos = new h3d.Vector(localPos.x - (currentBrush.size  / tileSize * 0.5), localPos.y - (currentBrush.size  / tileSize * 0.5));
+			for(i in 0 ... tile.surfaceWeights.length){
+				if(i == currentBrush.index) continue;
+				shader.targetIndex = i;
+				currentBrush.drawTo(tile.surfaceWeights[i], localPos, tileSize);
+			}
+			tile.generateWeightArray();
+		}
+	}
+
+	public function drawHeight(brush : Brush, pos : h3d.Vector){
+		var tiles = terrain.getTiles(pos, currentBrush.size / 2.0 + 5, true);
+		for(tile in tiles){
+			var localPos = tile.globalToLocal(pos.clone());
+			localPos.scale3(1.0/tileSize);
+			var strokeBuffer = getStrokeBuffer(tile.tileX, tile.tileY);
+			if(strokeBuffer.used == false) strokeBuffer.linkTo(tile);
+			currentBrush.bitmap.blendMode = brushMode.accumulate ? Add : Max;
+			currentBrush.bitmap.color = new h3d.Vector(currentBrush.strength);
+			if(currentBrush.bitmap.getShader(h3d.shader.pbr.Brush) != null ) currentBrush.bitmap.removeShader(currentBrush.bitmap.getShader(h3d.shader.pbr.Brush));
+			currentBrush.drawTo(strokeBuffer.tex, localPos, tileSize, -2);
+		}
+	}
+
 	override function setSelected( ctx : Context, b : Bool ) {
 	override function setSelected( ctx : Context, b : Bool ) {
 		if(b){
 		if(b){
 			var s2d = @:privateAccess ctx.local2d.getScene();
 			var s2d = @:privateAccess ctx.local2d.getScene();
 			interactive = new h2d.Interactive(10000, 10000, s2d);
 			interactive = new h2d.Interactive(10000, 10000, s2d);
-			interactive.propagateEvents = false;
+			interactive.propagateEvents = true;
+			interactive.cancelEvents = false;
 
 
 			interactive.onPush = function(e) {
 			interactive.onPush = function(e) {
 				if(K.isDown( K.MOUSE_LEFT)){
 				if(K.isDown( K.MOUSE_LEFT)){
+					e.propagate = false;
 					if(currentBrush.isValid()){
 					if(currentBrush.isValid()){
-						var worldPos = screenToWorld(s2d.mouseX, s2d.mouseY, terrain.getAbsPos().tz, ctx).toVector();
+						var worldPos = screenToWorld(s2d.mouseX, s2d.mouseY, ctx).toVector();
 						lastPos = worldPos.clone();
 						lastPos = worldPos.clone();
-						substractMode = K.isDown(K.CTRL);
-						currentBrush.bitmap.blendMode = accumulate ? Add : Max;
+						brushMode.substract = K.isDown(K.CTRL);
 						drawBrush( lastPos, worldPos, ctx);
 						drawBrush( lastPos, worldPos, ctx);
 						previewStrokeBuffers();
 						previewStrokeBuffers();
 					}
 					}
@@ -319,20 +581,22 @@ class Terrain extends Object3D {
 			};
 			};
 
 
 			interactive.onMove = function(e) {
 			interactive.onMove = function(e) {
+				var worldPos = screenToWorld(s2d.mouseX, s2d.mouseY, ctx).toVector();
 				if(K.isDown( K.MOUSE_LEFT)){
 				if(K.isDown( K.MOUSE_LEFT)){
+					e.propagate = false;
 					if(currentBrush.isValid()){
 					if(currentBrush.isValid()){
-						var worldPos = screenToWorld(s2d.mouseX, s2d.mouseY, terrain.getAbsPos().tz, ctx).toVector();
 						if( lastPos == null) lastPos = worldPos.clone();
 						if( lastPos == null) lastPos = worldPos.clone();
 						drawBrush( lastPos, worldPos, ctx);
 						drawBrush( lastPos, worldPos, ctx);
 						lastPos = worldPos;
 						lastPos = worldPos;
 					}
 					}
 				}
 				}
 				previewStrokeBuffers();
 				previewStrokeBuffers();
+				drawBrushPreview(worldPos, ctx);
 			};
 			};
-
 		}
 		}
 		else{
 		else{
 			if(interactive != null) interactive.remove();
 			if(interactive != null) interactive.remove();
+			brushPreview.reset();
 		}
 		}
 	}
 	}
 
 
@@ -341,90 +605,164 @@ class Terrain extends Object3D {
 	}
 	}
 
 
 	override function edit( ctx : EditContext ) {
 	override function edit( ctx : EditContext ) {
+
+		//super.edit(ctx);
+
 		function loadTexture( ctx : hide.prefab.EditContext, propsName : String, ?wrap : h3d.mat.Data.Wrap){
 		function loadTexture( ctx : hide.prefab.EditContext, propsName : String, ?wrap : h3d.mat.Data.Wrap){
-			var texture = ctx.rootContext.loadTexture(propsName);
+			var texture = ctx.rootContext.shared.loadTexture(propsName);
 			texture.wrap = wrap == null ? Repeat : wrap;
 			texture.wrap = wrap == null ? Repeat : wrap;
 			return texture;
 			return texture;
 		}
 		}
+
 		var props = new hide.Element('
 		var props = new hide.Element('
-			<div class="group" name="Position">
-				<dt>X</dt><dd><input type="range" min="-10" max="10" value="0" field="x"/></dd>
-				<dt>Y</dt><dd><input type="range" min="-10" max="10" value="0" field="y"/></dd>
-				<dt>Z</dt><dd><input type="range" min="-10" max="10" value="0" field="z"/></dd>
-			</div>
 			<div class="group" name="<Terrain>">
 			<div class="group" name="<Terrain>">
 				<dl>
 				<dl>
 					<dt>Tile Size</dt><dd><input type="range" min="1" max="100" value="0" field="tileSize"/></dd>
 					<dt>Tile Size</dt><dd><input type="range" min="1" max="100" value="0" field="tileSize"/></dd>
 					<dt>Cell Size</dt><dd><input type="range" min="0.01" max="10" value="0" field="cellSize"/></dd>
 					<dt>Cell Size</dt><dd><input type="range" min="0.01" max="10" value="0" field="cellSize"/></dd>
-					<dt>WeightMap Resolution</dt><dd><input type="range" min="1" max="4096" value="0" field="weightMapResolution"/></dd>
-					<dt>HeightMap Resolution</dt><dd><input type="range" min="1" max="4096" value="0" field="heightMapResolution"/></dd>
+					<dt>WeightMap Resolution</dt><dd><input type="range" min="1" max="1000" value="0" step="1" field="weightMapResolution"/></dd>
+					<dt>HeightMap Resolution</dt><dd><input type="range" min="1" max="1000" value="0" step="1" field="heightMapResolution"/></dd>
+					<dt>HeightBlend</dt><dd><input type="checkbox" field="terrain.useHeightBlend"/></dd>
+					<dt>Parallax Amount</dt><dd><input type="range" min="0" max="1" field="terrain.parallaxAmount"/></dd>
 					<dt>Show Grid</dt><dd><input type="checkbox" field="terrain.showGrid"/></dd>
 					<dt>Show Grid</dt><dd><input type="checkbox" field="terrain.showGrid"/></dd>
 					<dt>Visible</dt><dd><input type="checkbox" field="visible"/></dd>
 					<dt>Visible</dt><dd><input type="checkbox" field="visible"/></dd>
 				</dl>
 				</dl>
 			</div>
 			</div>
+			<div class="group" name="Mode">
+				<dt>Accumulate</dt><dd><input type="checkbox" field="brushMode.accumulate"/></dd>
+				<dt>Scrulpt</dt><dd><input type="checkbox" field="brushMode.sculptMode"/></dd>
+			</div>
 			<div class="group" name="Brush">
 			<div class="group" name="Brush">
 				<dl>
 				<dl>
-					<dt>Accumulate</dt><dd><input type="checkbox" field="accumulate"/></dd>
 					<div class="terrain-brushes"></div>
 					<div class="terrain-brushes"></div>
-					<dt>Size</dt><dd><input type="range" min="0" max="100" field="currentBrush.size"/></dd>
+					<dt>Size</dt><dd><input type="range" min="0.01" max="10" field="currentBrush.size"/></dd>
 					<dt>Strength</dt><dd><input type="range" min="0" max="1" field="currentBrush.strength"/></dd>
 					<dt>Strength</dt><dd><input type="range" min="0" max="1" field="currentBrush.strength"/></dd>
 					<dt>Step</dt><dd><input type="range" min="0.01" max="10" field="currentBrush.step"/></dd>
 					<dt>Step</dt><dd><input type="range" min="0.01" max="10" field="currentBrush.step"/></dd>
-					<div class="terrain-surfaces"></div>
 				</dl>
 				</dl>
 			</div>
 			</div>
+			<div class="group" name="Surface">
+				<dt>Add</dt><dd><input type="texturepath" field="tmpTexPath"/></dd>
+				<div class="terrain-surfaces"></div>
+				<dt>Tilling</dt><dd><input type="range" min="0" max="10" field="currentSurface.tilling"/></dd>
+				<dt>Offset X</dt><dd><input type="range" min="0" max="1" field="currentSurface.offset.x"/></dd>
+				<dt>Offset Y</dt><dd><input type="range" min="0" max="1" field="currentSurface.offset.y"/></dd>
+				<dt>Rotate</dt><dd><input type="range" min="0" max="360" field="currentSurface.angle"/></dd>
+			</div>
+			<div><dl><input type="button" value="Save" class="save"/><dl></div>
 		');
 		');
+
+		props.find(".save").click(function(_) {
+			saveWeightTextures(ctx.rootContext);
+			saveHeightTextures(ctx.rootContext);
+		});
+
+		inline function setRange(name, value){
+			var field = Lambda.find(ctx.properties.fields, f->f.fname==name);
+			if(field != null) @:privateAccess field.range.value = value;
+		};
+
 		var brushes : Array<Dynamic> = ctx.scene.config.get("terrain.brushes");
 		var brushes : Array<Dynamic> = ctx.scene.config.get("terrain.brushes");
 		var brushesContainer = props.find(".terrain-brushes");
 		var brushesContainer = props.find(".terrain-brushes");
-		for( brush in brushes){
-			var label = brush.name + "<br/>Step : " + brush.step + "<br/>Strength : " + brush.strength + "<br/>Size : " + brush.size ;
-			var img : Element;
-			if( brush.name == currentBrush.name) img = new Element('<div class="brush-preview-selected"></div>');
-			else img = new Element('<div class="brush-preview"></div>');
-			img.css("background-image", 'url("file://${hide.Ide.inst.getPath(brush.texture)}")');
-			var brushElem = new Element('<div class="brush"><span class="tooltiptext">$label</span></div>').prepend(img);
-			brushElem.click(function(e){
-
-				currentBrush.size = brush.size;
-				currentBrush.strength = brush.strength;
-				currentBrush.step = brush.step;
-				currentBrush.texPath = hide.Ide.inst.getPath(brush.texture);
-				currentBrush.tex = loadTexture(ctx, currentBrush.texPath);
-				currentBrush.name = brush.name;
-				currentBrush.bitmap = new h2d.Bitmap(h2d.Tile.fromTexture(currentBrush.tex));
-				currentBrush.bitmap.smooth = true;
-				currentBrush.bitmap.color = new h3d.Vector(currentBrush.strength);
-
-				props.remove();
-				edit(ctx);
-			});
-			brushesContainer.append(brushElem);
+		function refreshBrushes(){
+			brushesContainer.empty();
+			for( brush in brushes){
+				var label = brush.name + "<br/>Step : " + brush.step + "<br/>Strength : " + brush.strength + "<br/>Size : " + brush.size ;
+				var img : Element;
+				if( brush.name == currentBrush.name) img = new Element('<div class="brush-preview-selected"></div>');
+				else img = new Element('<div class="brush-preview"></div>');
+				img.css("background-image", 'url("file://${hide.Ide.inst.getPath(brush.texture)}")');
+				var brushElem = new Element('<div class="brush"><span class="tooltiptext">$label</span></div>').prepend(img);
+				brushElem.click(function(e){
+					currentBrush.size = brush.size;
+					currentBrush.strength = brush.strength;
+					currentBrush.step = brush.step;
+					currentBrush.texPath = hide.Ide.inst.getPath(brush.texture);
+					currentBrush.tex = loadTexture(ctx, currentBrush.texPath);
+					currentBrush.name = brush.name;
+					if(currentBrush.bitmap != null){
+						currentBrush.bitmap.tile.dispose();
+						currentBrush.bitmap.tile = h2d.Tile.fromTexture(currentBrush.tex);
+					}
+					else
+						currentBrush.bitmap = new h2d.Bitmap(h2d.Tile.fromTexture(currentBrush.tex));
+					currentBrush.bitmap.smooth = true;
+					currentBrush.bitmap.color = new h3d.Vector(currentBrush.strength);
+					refreshBrushes();
+				});
+				brushesContainer.append(brushElem);
+			}
+			if(currentBrush != null){
+				setRange("currentBrush.size", currentBrush.size);
+				setRange("currentBrush.strength", currentBrush.strength);
+				setRange("currentBrush.step", currentBrush.step);
+			}
 		}
 		}
+		refreshBrushes();
 
 
 		var surfacesContainer = props.find(".terrain-surfaces");
 		var surfacesContainer = props.find(".terrain-surfaces");
-		var surfacesPath : Dynamic = ctx.scene.config.get("terrain.surfacesPath");
-		var dir = hide.Ide.inst.getPath(surfacesPath);
-		for( f in try sys.FileSystem.readDirectory(dir) catch( e : Dynamic ) [] ){
-			if( StringTools.endsWith(f,"_Albedo.png") ){
-				var label = f.substr(0,f.length -  "_Albedo.png".length);
+		function refreshSurfaces(){
+			surfacesContainer.empty();
+			for( i in 0 ... terrain.surfaces.length ){
+				var surface = terrain.surfaces[i];
+				if(surface == null || surface.albedo == null) continue;
+				var label = surface.albedo.name;
 				var img : Element;
 				var img : Element;
-				if( f == currentSurfaceName) img = new Element('<div class="surface-preview-selected"></div>');
+				if( i == currentBrush.index) img = new Element('<div class="surface-preview-selected"></div>');
 				else img = new Element('<div class="surface-preview"></div>');
 				else img = new Element('<div class="surface-preview"></div>');
-				var imgPath = dir + f;
+				var imgPath = hide.Ide.inst.getPath(surface.albedo.name);
 				img.css("background-image", 'url("file://$imgPath")');
 				img.css("background-image", 'url("file://$imgPath")');
 				var surfaceElem = new Element('<div class=" surface"><span class="tooltiptext">$label</span></div>').prepend(img);
 				var surfaceElem = new Element('<div class=" surface"><span class="tooltiptext">$label</span></div>').prepend(img);
 				surfaceElem.click(function(e){
 				surfaceElem.click(function(e){
-					currentSurfaceName = f;
-					props.remove();
-					edit(ctx);
+					currentBrush.index = i;
+					currentSurface = terrain.getSurface(i);
+					refreshSurfaces();
 				});
 				});
 				surfacesContainer.append(surfaceElem);
 				surfacesContainer.append(surfaceElem);
 			}
 			}
-		}
+			if(currentSurface != null){
+				setRange("currentSurface.tilling", currentSurface.tilling);
+				setRange("currentSurface.offset.x", currentSurface.offset.x);
+				setRange("currentSurface.offset.y", currentSurface.offset.y);
+				setRange("currentSurface.angle", currentSurface.angle);
+			}
+		};
+		refreshSurfaces();
 
 
 		ctx.properties.add(props, this, function(pname) {
 		ctx.properties.add(props, this, function(pname) {
-			ctx.onChange(this, pname);
-		});
+			if(pname == "tmpTexPath"){
+				var split : Array<String> = [];
+				var curTypeIndex = 0;
+				while( split.length <= 1 && curTypeIndex < textureType.length){
+					split = tmpTexPath.split(textureType[curTypeIndex]);
+					curTypeIndex++;
+				}
+				if(split.length > 1) {
+					var t : h3d.mat.Texture;
+					var name = split[0];
+					var albedo = ctx.rootContext.shared.loadTexture(name + textureType[0] + ".png");
+					var normal = ctx.rootContext.shared.loadTexture(name + textureType[1] + ".png");
+					var pbr = ctx.rootContext.shared.loadTexture(name + textureType[2] + ".png");
+					function wait() {
+						if( albedo.flags.has(Loading) || normal.flags.has(Loading)|| pbr.flags.has(Loading))
+							haxe.Timer.delay(wait, 1);
+						else{
+							if(terrain.getSurfaceFromTex(name + textureType[0] + ".png", name + textureType[1] + ".png", name + textureType[2] + ".png") == null){
+								terrain.addSurface(albedo, normal, pbr);
+								albedo.dispose();
+								normal.dispose();
+								pbr.dispose();
+								terrain.generateSurfaceArray();
+								props.remove();
+								edit(ctx);
+							}
+						}
+					}
+					wait();
+				}
+			}
+			tmpTexPath = null;
 
 
+		ctx.onChange(this, pname);
+		});
 	}
 	}
 	#end
 	#end
 
 

+ 34 - 0
hide/prefab/terrain/TilePreview.hx

@@ -0,0 +1,34 @@
+package hide.prefab.terrain;
+
+class TilePreview extends hxsl.Shader {
+
+	static var SRC = {
+
+		@:import h3d.shader.BaseMesh;
+		@param var heightMap : Sampler2D;
+		@param var heightMapSize : Float;
+		@param var primSize : Float;
+
+		@param var brushTex : Sampler2D;
+		@param var brushSize : Float;
+		@param var brushPos : Vec2;
+
+		function vertex() {
+			var calculatedUV = input.position.xy / primSize;
+			var terrainUV = (calculatedUV * (heightMapSize - 2)) / heightMapSize;
+			terrainUV += 0.5 / heightMapSize;
+			transformedPosition += (vec3(0,0, heightMap.get(terrainUV).r) * global.modelView.mat3());
+		}
+
+		function fragment() {
+			var tilePos = (input.position.xy / primSize);
+			var brushUV = tilePos - (brushPos - (brushSize / (2.0 * primSize)));
+			brushUV /= (brushSize / primSize);
+			pixelColor = vec4(brushTex.get(brushUV).r * vec3(0,1,1), min(0.7,brushTex.get(brushUV).r));
+
+			if(brushUV.x < 0 || brushUV.x > 1 || brushUV.y < 0 || brushUV.y > 1 )
+				pixelColor = vec4(0);
+
+		}
+	}
+}