瀏覽代碼

added HeightMap prefab

Nicolas Cannasse 5 年之前
父節點
當前提交
bd9c0c90e1
共有 5 個文件被更改,包括 297 次插入19 次删除
  1. 1 1
      bin/style.css
  2. 9 10
      bin/style.less
  3. 10 7
      hide/comp/PropsEditor.hx
  4. 2 1
      hrt/prefab/Object3D.hx
  5. 275 0
      hrt/prefab/l3d/HeightMap.hx

+ 1 - 1
bin/style.css

@@ -563,7 +563,7 @@ input[type=checkbox]:checked:after {
   width: 200px;
   margin-left: 10px;
 }
-.hide-properties dd .texture-preview {
+.hide-properties .texture-preview {
   pointer-events: none;
   display: inline-block;
   width: 15px;

+ 9 - 10
bin/style.less

@@ -612,17 +612,16 @@ input[type=checkbox] {
 		position:relative;
 		width:200px;
 		margin-left:10px;
+	}
 
-		.texture-preview {
-			pointer-events: none;
-			display:inline-block;
-			width : 15px;
-			height: 15px;
-			position : absolute;
-			margin-left : -17px;
-			margin-top : 2px;
-		}
-
+	.texture-preview {
+		pointer-events: none;
+		display:inline-block;
+		width : 15px;
+		height: 15px;
+		position : absolute;
+		margin-left : -17px;
+		margin-top : 2px;
 	}
 
 	dl dl {

+ 10 - 7
hide/comp/PropsEditor.hx

@@ -81,26 +81,26 @@ class PropsEditor extends Component {
 			if ( p.def != null ) e.attr("value", "" + p.def);
 		}
 	}
-	
+
 	public static function makeGroupEl(name: String, content: Element) {
 		var el = new Element('<div class="group" name="${name}"></div>');
 		content.appendTo(el);
 		return el;
 	}
-	
+
 	public static function makeSectionEl(name: String, content: Element, ?headerContent: Element) {
 		var el = new Element('<div class="section"><h1><span>${name}</span></h1><div class="content"></div></div>');
 		if (headerContent != null) headerContent.appendTo(el.find("h1"));
 		content.appendTo(el.find(".content"));
 		return el;
 	}
-	
+
 	public static function makeLabelEl(name: String, content: Element) {
 		var el = new Element('<dt>${name}</dt><dd></dd>');
 		content.appendTo(el.find("dd"));
 		return el;
 	}
-	
+
 	public static function makeListEl(content:Array<Element>) {
 		var el = new Element("<dl>");
 		for ( e in content ) e.appendTo(el);
@@ -127,8 +127,11 @@ class PropsEditor extends Component {
 	}
 
 	public function add( e : Element, ?context : Dynamic, ?onChange : String -> Void ) {
-
 		e.appendTo(element);
+		return build(e,context,onChange);
+	}
+
+	public function build( e : Element, ?context : Dynamic, ?onChange : String -> Void ) {
 		e = e.wrap("<div></div>").parent(); // necessary to have find working on top level element
 
 		e.find("input[type=checkbox]").wrap("<div class='checkbox-wrapper'></div>");
@@ -154,7 +157,7 @@ class PropsEditor extends Component {
 			section.children(".content").slideToggle(100);
 			saveDisplayState("section:" + StringTools.trim(e.getThis().text()), section.hasClass("open"));
 		}).find("input").mousedown(function(e) e.stopPropagation());
-		
+
 		e.find("input[type=section_name]").change(function(e) {
 			e.getThis().closest(".section").find(">h1 span").text(e.getThis().val());
 		});
@@ -190,7 +193,7 @@ class PropsEditor extends Component {
 			saveDisplayState("group:" + key, group.hasClass("open"));
 
 		}).find("input").mousedown(function(e) e.stopPropagation());
-		
+
 		e.find("input[type=group_name]").change(function(e) {
 			e.getThis().closest(".group").find(">.title").val(e.getThis().val());
 		});

+ 2 - 1
hrt/prefab/Object3D.hx

@@ -210,9 +210,10 @@ class Object3D extends Prefab {
 
 	override function getHideProps() : HideProps {
 		// Check children
+		var cname = Type.getClassName(Type.getClass(this)).split(".").pop();
 		return {
 			icon : children == null || children.length > 0 ? "folder-open" : "genderless",
-			name : "Group"
+			name : cname == "Object3D" ? "Group" : cname,
 		};
 	}
 	#end

+ 275 - 0
hrt/prefab/l3d/HeightMap.hx

@@ -0,0 +1,275 @@
+package hrt.prefab.l3d;
+
+enum abstract HeightMapTextureKind(String) {
+	var Albedo = "albedo";
+	var Height = "height";
+	var Normal = "normal";
+	var SplatMap = "splatmap";
+}
+
+class HeightMapShader extends hxsl.Shader {
+	static var SRC = {
+		@:import h3d.shader.BaseMesh;
+
+		@const var hasHeight : Bool;
+		@const var hasNormal : Bool;
+
+		@param var heightMap : Sampler2D;
+		@param var normalMap : Sampler2D;
+		@param var heightScale : Float;
+		@param var heightOffset : Vec2;
+		@param var cellSize : Vec2;
+		@input var input2 : { uv : Vec2 };
+
+		var calculatedUV : Vec2;
+
+		function getPoint( dx : Float, dy : Float ) : Vec3 {
+			var v = vec2(dx,dy);
+			return vec3( cellSize * v , heightMap.get(calculatedUV + heightOffset * v).r * heightScale - relativePosition.z);
+		}
+
+		function vertex() {
+			calculatedUV = input2.uv;
+			if( hasHeight ) {
+				var z = heightMap.get(calculatedUV).x * heightScale;
+				relativePosition.z = z;
+
+				// calc normal
+				if( !hasNormal ) {
+					var px0 = getPoint(-1,0);
+					var py0 = getPoint(0, -1);
+					var px1 = getPoint(1, 0);
+					var py1 = getPoint(0, 1);
+					var n = px1.cross(py1) + py1.cross(px0) + px0.cross(py0) + py0.cross(px1);
+					transformedNormal = (n.normalize() * global.modelView.mat3()).normalize();
+				}
+			}
+		}
+
+		function __init__fragment() {
+			if( hasNormal )
+				transformedNormal = (unpackNormal(normalMap.get(calculatedUV)) * global.modelView.mat3()).normalize();
+		}
+
+	};
+}
+
+class HeightMap extends Object3D {
+
+	var textures : Array<{ path : String, kind : HeightMapTextureKind, enable : Bool }> = [];
+	var size = 128.;
+	var heightScale = 0.2;
+
+	override function save():{} {
+		var o : Dynamic = super.save();
+		o.textures = [for( t in textures ) { path : t.path, kind : t.kind }];
+		o.size = size;
+		o.heightScale = heightScale;
+		return o;
+	}
+
+	override function load(obj:Dynamic) {
+		super.load(obj);
+		textures = [for( o in (obj.textures:Array<Dynamic>) ) { path : o.path, kind : o.kind, enable : true }];
+		size = obj.size;
+		heightScale = obj.heightScale;
+	}
+
+	function getTextures( ctx : Context, k : HeightMapTextureKind ) {
+		return [for( t in textures ) if( t.kind == k && t.path != null && t.enable ) ctx.loadTexture(t.path)];
+	}
+
+	override function makeInstance(ctx:Context):Context {
+		ctx = ctx.clone(this);
+		var mesh = new h3d.scene.Mesh(null, ctx.local3d);
+		mesh.material.mainPass.addShader(new HeightMapShader());
+		ctx.local3d = mesh;
+		ctx.local3d.name = name;
+		updateInstance(ctx);
+		return ctx;
+	}
+
+	override function updateInstance( ctx : Context, ?propName : String ) {
+		super.updateInstance(ctx, propName);
+
+		var mesh = cast(ctx.local3d, h3d.scene.Mesh);
+		var grid = cast(mesh.primitive, HeightGrid);
+
+		var hmap = getTextures(ctx,Height)[0];
+		var width = hmap == null ? Std.int(size) : hmap.width;
+		var height = hmap == null ? Std.int(size) : hmap.height;
+		var cw = size/width, ch = size/height;
+		if( grid == null || grid.width != width || grid.height != height || grid.cellWidth != cw || grid.cellHeight != ch ) {
+			grid = new HeightGrid(width,height,cw,ch);
+			grid.addUVs();
+			grid.addNormals();
+			mesh.primitive = grid;
+		}
+
+		var albedo = getTextures(ctx, Albedo);
+		mesh.material.texture = albedo.length == 1 ? albedo[0] : null;
+
+		var normal = getTextures(ctx,Normal)[0];
+
+		var shader = mesh.material.mainPass.getShader(HeightMapShader);
+		shader.hasHeight = hmap != null;
+		shader.heightMap = hmap;
+		shader.hasNormal = normal != null;
+		shader.normalMap = normal;
+		shader.heightScale = heightScale * size;
+		shader.cellSize.set(cw,ch);
+		if( hmap != null ) shader.heightOffset.set(1 / hmap.width,1 / hmap.height);
+	}
+
+	#if editor
+	override function edit(ectx:EditContext) {
+		super.edit(ectx);
+		var ctx = ectx.getContext(this);
+		var props = new hide.Element('
+			<div class="group" name="View">
+			<dl>
+				<dt>Size</dt><dd><input type="range" min="0" max="1000" value="128" field="size"/></dd>
+				<dt>Height Scale</dt><dd><input type="range" min="0" max="1" field="heightScale"/></dd>
+			</dl>
+			<div class="group" name="Textures">
+			<ul></ul>
+			</div>
+		');
+		var list = props.find("ul");
+		ectx.properties.add(props,this, (_) -> updateInstance(ctx));
+		for( tex in textures ) {
+			var e = new hide.Element('<li>
+				<input type="texturepath" field="path"/>
+				<select field="kind" style="width:80px">
+					<option value="albedo">Albedo
+					<option value="height">Height
+					<option value="normal">Normal
+					<option value="splatmap">SplatMap
+					<option value="delete">-- Delete --
+				</select>
+				<input type="checkbox" field="enable"/>
+			</li>
+			');
+			e.appendTo(list);
+			ectx.properties.build(e, tex, (_) -> {
+				if( ""+tex.kind == "delete" ) {
+					textures.remove(tex);
+					ectx.rebuildProperties();
+				}
+				updateInstance(ctx);
+			});
+		}
+		var add = new hide.Element('<li><a href="#">[+]</a></li>');
+		add.appendTo(list);
+		add.find("a").click(function(_) {
+			textures.push({ path : null, kind : Albedo, enable: true });
+			ectx.rebuildProperties();
+		});
+	}
+	#end
+
+	static var _ = Library.register("heightmap", HeightMap);
+
+}
+
+
+class HeightGrid extends h3d.prim.MeshPrimitive {
+
+	/**
+		The number of cells in width
+	**/
+	public var width (default, null) : Int;
+
+	/**
+		The number of cells in height
+	**/
+	public var height (default, null)  : Int;
+
+	/**
+		The width of a cell
+	**/
+	public var cellWidth (default, null) : Float;
+
+	/**
+		The height of a cell
+	**/
+	public var cellHeight (default, null)  : Float;
+
+	var hasNormals : Bool;
+	var hasUVs : Bool;
+
+	public function new( width : Int, height : Int, cellWidth = 1., cellHeight = 1. ) {
+		this.width = width;
+		this.height = height;
+		this.cellWidth = cellWidth;
+		this.cellHeight = cellHeight;
+	}
+
+	public function addNormals() {
+		hasNormals = true;
+	}
+
+	public function addUVs() {
+		hasUVs = true;
+	}
+
+	override function getBounds():h3d.col.Bounds {
+		return h3d.col.Bounds.fromValues(0,0,0,width*cellWidth,height*cellHeight,0);
+	}
+
+	override function alloc(engine:h3d.Engine) {
+		dispose();
+		var size = 3;
+		var names = ["position"];
+		var positions = [0];
+		if( hasNormals ) {
+			names.push("normal");
+			positions.push(size);
+			size += 3;
+		}
+		if( hasUVs ) {
+			names.push("uv");
+			positions.push(size);
+			size += 2;
+		}
+
+		var buf = new hxd.FloatBuffer((width + 1) * (height +  1) * size);
+		var p = 0;
+		for( y in 0...height + 1 )
+			for( x in 0...width + 1 ) {
+				buf[p++] = x * cellWidth;
+				buf[p++] = y * cellHeight;
+				buf[p++] = 0;
+				if( hasNormals ) {
+					buf[p++] = 0;
+					buf[p++] = 0;
+					buf[p++] = 1;
+				}
+				if( hasUVs ) {
+					buf[p++] = x / width;
+					buf[p++] = y / height;
+				}
+			}
+		var flags : Array<h3d.Buffer.BufferFlag> = [LargeBuffer];
+		buffer = h3d.Buffer.ofFloats(buf, size, flags);
+
+		for( i in 0...names.length )
+			addBuffer(names[i], buffer, positions[i]);
+
+		indexes = new h3d.Indexes(width * height * 6, true);
+		var b = haxe.io.Bytes.alloc(indexes.count * 4);
+		var p = 0;
+		for( y in 0...height )
+			for( x in 0...width ) {
+				var s = x + y * (width + 1);
+				b.setInt32(p++ << 2, s);
+				b.setInt32(p++ << 2, s + 1);
+				b.setInt32(p++ << 2, s + width + 1);
+				b.setInt32(p++ << 2, s + 1);
+				b.setInt32(p++ << 2, s + width + 2);
+				b.setInt32(p++ << 2, s + width + 1);
+			}
+		indexes.uploadBytes(b,0,indexes.count);
+	}
+
+}