Ver código fonte

review notion of ground, added Object3D.localRayInterection for Polygon and HeightMap

Nicolas Cannasse 5 anos atrás
pai
commit
7853c15ae5

+ 42 - 9
hide/comp/SceneEditor.hx

@@ -73,6 +73,14 @@ class SceneEditorContext extends hide.prefab.EditContext {
 		}
 	}
 
+	override function screenToGround(x:Float, y:Float, ?forPrefab:hrt.prefab.Prefab) {
+		return editor.screenToGround(x, y, forPrefab);
+	}
+
+	override function positionToGroundZ(x:Float, y:Float, ?forPrefab:hrt.prefab.Prefab):Float {
+		return editor.getZ(x, y, forPrefab);
+	}
+
 	override function getCurrentProps( p : hrt.prefab.Prefab ) {
 		var cur = editor.curEdit;
 		return cur != null && cur.elements[0] == p ? editor.properties.element : new Element();
@@ -1483,7 +1491,7 @@ class SceneEditor {
 	}
 
 	public function getPickTransform(parent: PrefabElement) {
-		var proj = screenToWorld(scene.s2d.mouseX, scene.s2d.mouseY);
+		var proj = screenToGround(scene.s2d.mouseX, scene.s2d.mouseY);
 		if(proj == null) return null;
 
 		var localMat = new h3d.Matrix();
@@ -2228,30 +2236,55 @@ class SceneEditor {
 		};
 	}
 
-	public function getZ(x: Float, y: Float) {
-		var offset = 1000;
+	public function getZ(x: Float, y: Float, ?paintOn : hrt.prefab.Prefab) {
+		var offset = 1000000;
 		var ray = h3d.col.Ray.fromValues(x, y, offset, 0, 0, -1);
-		var dist = projectToGround(ray);
+		var dist = projectToGround(ray, paintOn);
 		if(dist >= 0) {
 			return offset - dist;
 		}
 		return 0.;
 	}
 
-	public function projectToGround(ray: h3d.col.Ray) {
+	function getGroundPrefabs() : Array<PrefabElement> {
+		return sceneData.findAll((p) -> p);
+	}
+
+	public function projectToGround(ray: h3d.col.Ray, ?paintOn : hrt.prefab.Prefab ) {
 		var minDist = -1.;
+
+		for( elt in (paintOn == null ? getGroundPrefabs() : [paintOn]) ) {
+			var obj = Std.downcast(elt, Object3D);
+			if( obj == null ) continue;
+			var ctx = getContext(elt);
+			if( ctx == null ) continue;
+
+			var lray = ray.clone();
+			lray.transform(ctx.local3d.getInvPos());
+			var dist = obj.localRayIntersection(ctx, lray);
+			if( dist > 0 ) {
+				var pt = lray.getPoint(dist);
+				pt.transform(ctx.local3d.getAbsPos());
+				var dist = pt.sub(ray.getPos()).length();
+				if( minDist < 0 || dist < minDist )
+					minDist = dist;
+			}
+		}
+		if( minDist >= 0 )
+			return minDist;
+
 		var zPlane = h3d.col.Plane.Z(0);
 		var pt = ray.intersect(zPlane);
-		if(pt != null) {
+		if( pt != null )
 			minDist = pt.sub(ray.getPos()).length();
-		}
+
 		return minDist;
 	}
 
-	public function screenToWorld(sx: Float, sy: Float) {
+	public function screenToGround(sx: Float, sy: Float, ?paintOn : hrt.prefab.Prefab ) {
 		var camera = scene.s3d.camera;
 		var ray = camera.rayFromScreen(sx, sy);
-		var dist = projectToGround(ray);
+		var dist = projectToGround(ray, paintOn);
 		if(dist >= 0) {
 			return ray.getPoint(dist);
 		}

+ 17 - 0
hide/prefab/EditContext.hx

@@ -63,6 +63,23 @@ class EditContext {
 		return rootContext.shared.contexts.get(p);
 	}
 
+	/**
+		Converts screen mouse coordinates into projection into ground.
+		If "forPrefab" is used, only this prefab is taken into account for ground consideration (self painting)
+	**/
+	public function screenToGround( x : Float, y : Float, ?forPrefab : Prefab ) : h3d.col.Point {
+		throw "Not implemented";
+		return null;
+	}
+
+	/**
+		Similar to screenToGround but based on 3D coordinates instead of screen ones
+	**/
+	public function positionToGroundZ( x : Float, y : Float, ?forPrefab : Prefab ) : Float {
+		throw "Not implemented";
+		return null;
+	}
+
 	/**
 		Rebuild the edit window
 	**/

+ 12 - 25
hide/view/l3d/Level3D.hx

@@ -61,8 +61,8 @@ class CamController extends h3d.scene.CameraController {
 					}
 					else {
 						var se = level3d.sceneEditor;
-						var fromPt = se.screenToWorld(pushX, pushY);
-						var toPt = se.screenToWorld(e.relX, e.relY);
+						var fromPt = se.screenToGround(pushX, pushY);
+						var toPt = se.screenToGround(e.relX, e.relY);
 						if(fromPt == null || toPt == null)
 							return;
 						var delta = toPt.sub(fromPt).toVector();
@@ -131,30 +131,15 @@ private class Level3DSceneEditor extends hide.comp.SceneEditor {
 		parent.onPrefabChange(p, pname);
 	}
 
-	override function projectToGround(ray: h3d.col.Ray) {
-		var polygons = parent.getGroundPolys();
-		var minDist = -1.;
-		for(polygon in polygons) {
-			var ctx = getContext(polygon);
-			var mesh = Std.downcast(ctx.local3d, h3d.scene.Mesh);
-			if(mesh == null)
-				continue;
-			var collider = mesh.getGlobalCollider();
-			var d = collider.rayIntersection(ray, true);
-			if(d > 0 && (d < minDist || minDist < 0)) {
-				minDist = d;
-			}
-		}
-		if(minDist >= 0)
-			return minDist;
-		return super.projectToGround(ray);
+	override function getGroundPrefabs():Array<PrefabElement> {
+		return parent.getGroundPrefabs();
 	}
 
 	override function getNewContextMenu(current: PrefabElement, ?onMake: PrefabElement->Void=null, ?groupByType = true ) {
 		var newItems = super.getNewContextMenu(current, onMake, groupByType);
 
 		function setup(p : PrefabElement) {
-			var proj = screenToWorld(scene.s2d.width/2, scene.s2d.height/2);
+			var proj = screenToGround(scene.s2d.width/2, scene.s2d.height/2);
 			var obj3d = p.to(hrt.prefab.Object3D);
 			var autoCenter = proj != null && obj3d != null && (Type.getClass(p) != Object3D || p.parent != sceneData);
 			if(autoCenter) {
@@ -548,7 +533,7 @@ class Level3D extends FileView {
 	function onUpdate(dt:Float) {
 		if(hxd.Key.isDown(hxd.Key.ALT)) {
 			posToolTip.visible = true;
-			var proj = sceneEditor.screenToWorld(scene.s2d.mouseX, scene.s2d.mouseY);
+			var proj = sceneEditor.screenToGround(scene.s2d.mouseX, scene.s2d.mouseY);
 			posToolTip.text = proj != null ? '${Math.fmt(proj.x)}, ${Math.fmt(proj.y)}, ${Math.fmt(proj.z)}' : '???';
 			posToolTip.setPosition(scene.s2d.mouseX, scene.s2d.mouseY - 12);
 		}
@@ -693,14 +678,16 @@ class Level3D extends FileView {
 		return null;
 	}
 
-	function getGroundPolys() {
+	function getGroundPrefabs() {
 		var groundGroups = data.findAll(p -> if(p.name == "ground") p else null);
-		var ret = [];
+		if( groundGroups.length == 0 )
+			return null;
+		var ret : Array<hrt.prefab.Prefab> = [];
 		for(group in groundGroups)
-			group.findAll(function(p) {
+			group.findAll(function(p) : hrt.prefab.Prefab {
 				if(p.name == "nocollide")
 					return null;
-				return p.to(hrt.prefab.l3d.Polygon);
+				return p;
 			},ret);
 		return ret;
 	}

+ 4 - 0
hrt/prefab/Object3D.hx

@@ -36,6 +36,10 @@ class Object3D extends Prefab {
 		return { x : x, y : y, z : z, scaleX : scaleX, scaleY : scaleY, scaleZ : scaleZ, rotationX : rotationX, rotationY : rotationY, rotationZ : rotationZ };
 	}
 
+	public function localRayIntersection( ctx : Context, ray : h3d.col.Ray ) : Float {
+		return -1;
+	}
+
 	override function load( obj : Dynamic ) {
 		x = obj.x == null ? 0. : obj.x;
 		y = obj.y == null ? 0. : obj.y;

+ 103 - 13
hrt/prefab/l3d/HeightMap.hx

@@ -79,6 +79,7 @@ class HeightMap extends Object3D {
 	var normalScale = 1.;
 	var tileX = 1;
 	var tileY = 1;
+	var heightTexturesCache : Array<hxd.Pixels>;
 
 	override function save():{} {
 		var o : Dynamic = super.save();
@@ -108,22 +109,105 @@ class HeightMap extends Object3D {
 		}
 	}
 
+	override function localRayIntersection(ctx:Context, ray:h3d.col.Ray):Float {
+		if( ray.lz > 0 )
+			return -1; // only from top
+		var maxZ = getHScale() * 100;
+		var b = h3d.col.Bounds.fromValues(0,0,0,size * tileX, size * tileY, maxZ);
+		var dist = b.rayIntersection(ray, false);
+		if( dist < 0 )
+			return -1;
+		var prim = cast(ctx.local3d.toMesh().primitive, HeightGrid);
+		var pt = ray.getPoint(dist);
+		var m = hxd.Math.min(prim.cellWidth, prim.cellHeight) * 0.5;
+		var isTiled = tileX != 1 || tileY != 1;
+		var curX = -1, curY = -1, curMap = null, offX = 0., offY = 0., cw = 0., ch = 0.;
+		if( !isTiled ) {
+			curX = 0;
+			curY = 0;
+			curMap = getHeightMap(0,0);
+			cw = curMap.width / size;
+			ch = curMap.height / size;
+		}
+		var prevH = pt.z;
+		var hscale = getHScale();
+		while( true ) {
+			pt.x += ray.lx * m;
+			pt.y += ray.ly * m;
+			pt.z += ray.lz * m;
+			if( !b.contains(pt) )
+				break;
+			if( isTiled ) {
+				var px = Std.int(pt.x / size);
+				var py = Std.int(pt.y / size);
+				if( px != curX || py != curY ) {
+					curX = px;
+					curY = py;
+					offX = -px * size;
+					offY = -py * size;
+					curMap = getHeightMap(px, py);
+					cw = curMap.width / size;
+					ch = curMap.height / size;
+				}
+			}
+			var ix = Std.int((pt.x + offX)*cw);
+			var iy = Std.int((pt.y + offY)*ch);
+			var h = curMap.bytes.getFloat( (ix + iy * curMap.width) << 2 );
+			h *= hscale;
+			if( pt.z < h ) {
+				// todo : fix interpolation using getZ dichotomy
+				var k = 1 - (prevH - (pt.z - ray.lz * m)) / (ray.lz * m - (h - prevH));
+				pt.x -= k * ray.lx * m;
+				pt.y -= k * ray.ly * m;
+				pt.z -= k * ray.lz * m;
+				return pt.sub(ray.getPos()).length();
+			}
+			prevH = h;
+		}
+		return -1;
+	}
+
+	function getHeightMap( x : Int, y : Int ) {
+		var id = x + y * tileX;
+		if( heightTexturesCache == null )
+			heightTexturesCache = [];
+		else {
+			var b = heightTexturesCache[id];
+			if( b != null ) return b;
+		}
+		var pix : hxd.Pixels = null;
+		for( t in textures )
+			if( t.kind == Height && t.enable && t.path != null ) {
+				var path = resolveTexturePath(t.path, x, y);
+				pix = try hxd.res.Loader.currentInstance.load(path).toImage().getPixels() catch( e : hxd.res.NotFound ) null;
+				break;
+			}
+		if( pix == null ) pix = hxd.Pixels.alloc(1, 1, R32F);
+		pix.convert(R32F);
+		heightTexturesCache[id] = pix;
+		return pix;
+	}
+
+	function resolveTexturePath( path : String, x : Int, y : Int ) {
+		if( x != 0 && y != 0 ) {
+			var parts = path.split("0");
+			switch( parts.length ) {
+			case 2:
+				path = x + parts[0] + y + parts[1];
+			case 3:
+				path = parts[0] + x + parts[1] + y + parts[2];
+			default:
+				// pattern not recognized - should contain 2 zeroes
+			}
+		}
+		return path;
+	}
+
 	function getTextures( ctx : Context, k : HeightMapTextureKind, x : Int, y : Int ) {
 		var tl = [];
 		for( t in textures )
 			if( t.kind == k && t.path != null && t.enable ) {
-				var path = t.path;
-				if( x != 0 && y != 0 ) {
-					var parts = path.split("0");
-					switch( parts.length ) {
-					case 2:
-						path = x + parts[0] + y + parts[1];
-					case 3:
-						path = parts[0] + x + parts[1] + y + parts[2];
-					default:
-						// pattern not recognized - should contain 2 zeroes
-					}
-				}
+				var path = resolveTexturePath(t.path,x,y);
 				tl.push(ctx.loadTexture(path));
 			}
 		return tl;
@@ -142,6 +226,8 @@ class HeightMap extends Object3D {
 	override function updateInstance( ctx : Context, ?propName : String ) {
 		super.updateInstance(ctx, propName);
 
+		heightTexturesCache = null;
+
 		var mesh = cast(ctx.local3d, h3d.scene.Mesh);
 		var grid = cast(mesh.primitive, HeightGrid);
 
@@ -178,6 +264,10 @@ class HeightMap extends Object3D {
 		for( p in prev ) p.remove();
 	}
 
+	function getHScale() {
+		return heightScale * size * 0.1;
+	}
+
 	function updateMesh( ctx : Context, mesh : h3d.scene.Mesh, x : Int, y : Int ) {
 		inline function getTextures(k) return this.getTextures(ctx,k,x,y);
 
@@ -193,7 +283,7 @@ class HeightMap extends Object3D {
 		shader.heightMap = hmap;
 		shader.hasNormal = normal != null;
 		shader.normalMap = normal;
-		shader.heightScale = heightScale * size * 0.1;
+		shader.heightScale = getHScale();
 		shader.normalScale = 1 / normalScale;
 		shader.cellSize.set(prim.cellWidth,prim.cellHeight);
 		if( hmap != null ) shader.heightOffset.set(1 / hmap.width,1 / hmap.height);

+ 7 - 90
hrt/prefab/l3d/MeshSpray.hx

@@ -281,8 +281,8 @@ class MeshSpray extends Object3D {
 							sceneEditor.selectObjects([this]);
 							previewModels = [];
 						}
-						var worldPos = getMousePicker(s2d.mouseX, s2d.mouseY);
-						previewMeshesAround(ctx, worldPos);
+						var worldPos = ectx.screenToGround(s2d.mouseX, s2d.mouseY);
+						previewMeshesAround(ectx, ctx, worldPos);
 					}
 					lastSpray = Date.now().getTime();
 				}
@@ -292,7 +292,7 @@ class MeshSpray extends Object3D {
 		interactive.onPush = function(e) {
 			e.propagate = false;
 			sprayEnable = true;
-			var worldPos = getMousePicker(s2d.mouseX, s2d.mouseY);
+			var worldPos = ectx.screenToGround(s2d.mouseX, s2d.mouseY);
 			if( K.isDown( K.SHIFT) )
 				removeMeshesAround(ctx, worldPos);
 			else {
@@ -312,7 +312,7 @@ class MeshSpray extends Object3D {
 		};
 
 		interactive.onMove = function(e) {
-			var worldPos = getMousePicker(s2d.mouseX, s2d.mouseY);
+			var worldPos = ectx.screenToGround(s2d.mouseX, s2d.mouseY);
 
 			var shiftPressed = K.isDown( K.SHIFT);
 
@@ -324,7 +324,7 @@ class MeshSpray extends Object3D {
 					previewModels = [];
 				}
 				if( !shiftPressed ) {
-					previewMeshesAround(ctx, worldPos);
+					previewMeshesAround(ectx, ctx, worldPos);
 				}
 
 				if( K.isDown( K.MOUSE_LEFT) ) {
@@ -638,7 +638,7 @@ class MeshSpray extends Object3D {
 	var localMat = new h3d.Matrix();
 	var lastPos : h3d.col.Point;
 	var lastMeshId = -1;
-	function previewMeshesAround(ctx : Context, point : h3d.col.Point) {
+	function previewMeshesAround(ectx : hide.prefab.EditContext, ctx : Context, point : h3d.col.Point) {
 		if (currentMeshes.length == 0) {
 			return;
 		}
@@ -742,7 +742,7 @@ class MeshSpray extends Object3D {
 
 				localMat.scale(currentScale, currentScale, currentScale);
 
-				position.z = getZ(position.x, position.y) + CONFIG.zOffset;
+				position.z = ectx.positionToGroundZ(position.x, position.y) + CONFIG.zOffset;
 				localMat.setPosition(new Vector(position.x, position.y, position.z));
 				localMat.multiply(localMat, invParent);
 
@@ -864,89 +864,6 @@ class MeshSpray extends Object3D {
 		return primitive;
 	}
 
-	var terrainPrefab : hrt.prefab.terrain.Terrain = null;
-
-	// GET Z with TERRAIN
-	public function getZ( x : Float, y : Float ) {
-		var z = this.z;
-
-		if (terrainPrefab == null)
-			@:privateAccess terrainPrefab = sceneEditor.sceneData.find(p -> Std.downcast(p, hrt.prefab.terrain.Terrain));
-
-		if(terrainPrefab != null){
-			var pos = new h3d.Vector(x, y, 0);
-			pos.transform3x4(this.getTransform());
-			z = terrainPrefab.terrain.getHeight(pos.x, pos.y);
-		}
-
-		return z;
-	}
-
-	public function  getMousePicker( ?x, ?y ) {
-		var camera = sceneEditor.scene.s3d.camera;
-		var ray = camera.rayFromScreen(x, y);
-		var planePt = ray.intersect(h3d.col.Plane.Z());
-		var offset = ray.getDir();
-
-		// Find rough intersection point in the camera forward direction to get first collision point
-		final maxZBounds = 25;
-		offset.scale(maxZBounds);
-		var pt = planePt.clone();
-		pt.load(pt.sub(offset));
-
-		var step = ray.getDir();
-		step.scale(0.25);
-
-		while(pt.z > -maxZBounds) {
-			var z = getZ(pt.x, pt.y);
-			if(pt.z < z)
-				break;
-			pt.load(pt.add(step));
-		}
-
-		// Bissect search for exact intersection point
-		for(_ in 0...50) {
-			var z = getZ(pt.x, pt.y);
-			var delta = z - pt.z;
-			if(hxd.Math.abs(delta) < 0.05)
-				return pt;
-
-			if(delta < 0)
-				pt.load(pt.add(step));
-			else
-				pt.load(pt.sub(step));
-
-			step.scale(0.5);
-		}
-
-		return planePt;
-	}
-
-
-	public function screenToWorld(sx: Float, sy: Float) {
-		var camera = sceneEditor.scene.s3d.camera;
-		var ray = camera.rayFromScreen(sx, sy);
-		var dist = projectToGround(ray);
-		if(dist >= 0) {
-			return ray.getPoint(dist);
-		}
-		return null;
-	}
-
-	function projectToGround( ray: h3d.col.Ray ) {
-		var dist = 0.0;
-		if (terrainPrefab == null)
-			@:privateAccess terrainPrefab = sceneEditor.sceneData.find(p -> Std.downcast(p, hrt.prefab.terrain.Terrain));
-
-		if (terrainPrefab != null) {
-			var normal = terrainPrefab.terrain.getAbsPos().up();
-			var plane = h3d.col.Plane.fromNormalPoint(normal.toPoint(), new h3d.col.Point(terrainPrefab.terrain.getAbsPos().tx, terrainPrefab.terrain.getAbsPos().ty, terrainPrefab.terrain.getAbsPos().tz));
-			var pt = ray.intersect(plane);
-			if(pt != null) { dist = pt.sub(ray.getPos()).length();}
-		}
-		return dist;
-	}
-
 	#end
 
 	static var _ = Library.register("meshSpray", MeshSpray);

+ 6 - 0
hrt/prefab/l3d/Polygon.hx

@@ -100,6 +100,12 @@ class Polygon extends Object3D {
 		return primitive;
 	}
 
+	override function localRayIntersection(ctx : hrt.prefab.Context, ray:h3d.col.Ray):Float {
+		var prim = makePrimitive();
+		var col = prim.getCollider();
+		return col.rayIntersection(ray, true);
+	}
+
 	public static function createPrimitive( shape : Shape ) {
 		var uvs : Array<Point> = null;
 		var points : Array<Point> = null;