Ver código fonte

Adding Collision data (baked using convex hull decomposition) into HMD.

clementlandrin 6 meses atrás
pai
commit
07cf21e892
7 arquivos alterados com 387 adições e 27 exclusões
  1. 65 0
      h3d/prim/Collider.hx
  2. 21 5
      h3d/prim/HMDModel.hx
  3. 238 18
      hxd/fmt/fbx/HMDOut.hx
  4. 12 0
      hxd/fmt/hmd/Data.hx
  5. 24 4
      hxd/fmt/hmd/Reader.hx
  6. 20 0
      hxd/fmt/hmd/Writer.hx
  7. 7 0
      hxd/fs/Convert.hx

+ 65 - 0
h3d/prim/Collider.hx

@@ -0,0 +1,65 @@
+package h3d.prim;
+
+import hxd.fmt.hmd.Library.GeometryBuffer;
+
+@:access(h3d.prim.HMDModel)
+class Collider {
+
+	var hmdModel : HMDModel;
+	var data : hxd.fmt.hmd.Data.Collider;
+
+	public static function fromHmd(hmdModel : HMDModel) {
+		var header = hmdModel.lib.header;
+		for( h in header.models )
+			if( header.geometries[h.geometry] == hmdModel.data ) {
+				return new Collider(hmdModel, header.colliders[h.collider]);
+			}
+		return null;
+	}
+
+	function new(hmdModel : HMDModel, data : hxd.fmt.hmd.Data.Collider) {
+		this.hmdModel = hmdModel;
+		this.data = data;
+	}
+
+	public function getBuffers() {
+		var vertexPosition = hmdModel.dataPosition + data.vertexPosition;
+		var indexPosition = hmdModel.dataPosition + data.indexPosition;
+
+		var buffers = [];
+		for ( i in 0...data.vertexCounts.length ) {
+			var vertexCount = data.vertexCounts[i];
+			var indexCount = data.indexCounts[i];
+
+			var is32 = vertexCount > 0x10000;
+			var vSize = vertexCount * 3 * 4;
+
+			var vertexBytes = haxe.io.Bytes.alloc(vSize);
+			hmdModel.lib.resource.entry.readBytes(vertexBytes, 0, vertexPosition, vSize);
+
+			var buf = new GeometryBuffer();
+			buf.vertexes = new haxe.ds.Vector(3 * vertexCount);
+			for ( i in 0...3 * vertexCount)
+				buf.vertexes[i] = vertexBytes.getFloat(i * 4);
+
+			var iSize = indexCount * (is32 ? 4 : 2);
+			var indexBytes = haxe.io.Bytes.alloc(iSize);
+			hmdModel.lib.resource.entry.readBytes(indexBytes, 0, indexPosition, iSize);
+
+			buf.indexes = new haxe.ds.Vector(indexCount);
+			var stride = is32 ? 4 : 2;
+			if ( is32 )
+				for ( i in 0...indexCount )
+					buf.indexes[i] = indexBytes.getInt32(i * stride);
+			else
+				for ( i in 0...indexCount )
+					buf.indexes[i] = indexBytes.getUInt16(i * stride);
+
+			buffers.push(buf);
+
+			vertexPosition += vSize;
+			indexPosition += iSize;
+		}
+		return buffers;
+	}
+}

+ 21 - 5
h3d/prim/HMDModel.hx

@@ -14,6 +14,7 @@ class HMDModel extends MeshPrimitive {
 	var normalsRecomputed : String;
 	var blendshape : Blendshape;
 	var lodConfig : Array<Float> = null;
+	var colliderData : Collider;
 
 	public static var lodExportKeyword : String = "LOD";
 
@@ -28,6 +29,8 @@ class HMDModel extends MeshPrimitive {
 
 		if (lib.header.shapes != null && lib.header.shapes.length > 0)
 			this.blendshape = new Blendshape(this);
+		if ( lib.header.colliders != null && lib.header.colliders.length > 0 )
+			this.colliderData = Collider.fromHmd(this);
 	}
 
 	override function hasInput( name : String ) {
@@ -267,11 +270,24 @@ class HMDModel extends MeshPrimitive {
 	}
 
 	function initCollider( poly : h3d.col.PolygonBuffer ) {
-		var buf= lib.getBuffers(data, hxd.BufferFormat.POS3D);
-		poly.setData(buf.vertexes, buf.indexes);
-		if( collider == null ) {
-			var sphere = data.bounds.toSphere();
-			collider = new h3d.col.Collider.OptimizedCollider(sphere, poly);
+		var sphere = data.bounds.toSphere();
+		if ( colliderData == null ) {
+			var buf = lib.getBuffers(data, hxd.BufferFormat.POS3D);
+			poly.setData(buf.vertexes, buf.indexes);
+			if( collider == null )
+				collider = new h3d.col.Collider.OptimizedCollider(sphere, poly);
+		} else {
+			var buffers = colliderData.getBuffers();
+			var hulls : Array<h3d.col.Collider> = [];
+			hulls.resize(buffers.length);
+			for ( i => buf in buffers ) {
+				var p = new h3d.col.PolygonBuffer();
+				p.source = poly.source;
+				p.setData(buf.vertexes, buf.indexes);
+				hulls[i] = p;
+			}
+			var convexHulls = new h3d.col.Collider.GroupCollider(hulls);
+			collider = new h3d.col.Collider.OptimizedCollider(sphere, convexHulls);
 		}
 	}
 

+ 238 - 18
hxd/fmt/fbx/HMDOut.hx

@@ -4,6 +4,12 @@ import hxd.fmt.fbx.BaseLibrary;
 import hxd.fmt.hmd.Data;
 import hxd.BufferFormat;
 
+typedef CollideParams = {
+	precision : Float,
+	maxSubdiv : Int,
+	maxConvexHulls : Int,
+}
+
 class HMDOut extends BaseLibrary {
 	public static var lodExportKeyword : String = "LOD";
 
@@ -17,6 +23,7 @@ class HMDOut extends BaseLibrary {
 	public var optimizeMesh = false;
 	public var generateNormals = false;
 	public var generateTangents = false;
+	public var generateCollides : CollideParams;
 	public var lowPrecConfig : Map<String,Precision>;
 	public var lodsDecimation : Array<Float>;
 
@@ -37,6 +44,16 @@ class HMDOut extends BaseLibrary {
 		return true;
 	}
 
+	#if (sys || nodejs)
+	function tmpFile(name : String) {
+		var tmp = Sys.getEnv("TMPDIR");
+		if( tmp == null ) tmp = Sys.getEnv("TMP");
+		if( tmp == null ) tmp = Sys.getEnv("TEMP");
+		if( tmp == null ) tmp = ".";
+		return tmp+"/"+name+Date.now().getTime()+"_"+Std.random(0x1000000)+".bin";
+	}
+	#end
+
 	function buildTangents( geom : hxd.fmt.fbx.Geometry ) {
 		var verts = geom.getVertices();
 		var normals = geom.getNormals();
@@ -85,11 +102,7 @@ class HMDOut extends BaseLibrary {
 		m.compute();
 		return m.tangents;
 		#elseif (sys || nodejs)
-		var tmp = Sys.getEnv("TMPDIR");
-		if( tmp == null ) tmp = Sys.getEnv("TMP");
-		if( tmp == null ) tmp = Sys.getEnv("TEMP");
-		if( tmp == null ) tmp = ".";
-		var fileName = tmp+"/mikktspace_data"+Date.now().getTime()+"_"+Std.random(0x1000000)+".bin";
+		var fileName = tmpFile("mikktspace_data");
 		var outFile = fileName+".out";
 		var outputData = new haxe.io.BytesBuffer();
 		outputData.addInt32(index.vidx.length);
@@ -173,7 +186,6 @@ class HMDOut extends BaseLibrary {
 				realIdx.push(pmap[i]);
 		}
 
-
 		var poly = new h3d.prim.Polygon(points, realIdx);
 		poly.addNormals();
 
@@ -259,11 +271,7 @@ class HMDOut extends BaseLibrary {
 			ibuf[i] = indices.getI32(i << 2) + startIndex;
 
 		#elseif (sys || nodejs)
-		var tmp = Sys.getEnv("TMPDIR");
-		if( tmp == null ) tmp = Sys.getEnv("TMP");
-		if( tmp == null ) tmp = Sys.getEnv("TEMP");
-		if( tmp == null ) tmp = ".";
-		var fileName = tmp+"/meshTools_data"+Date.now().getTime()+"_"+Std.random(0x1000000)+".bin";
+		var fileName = tmpFile("meshTools_data");
 		var outFile = fileName+".out";
 
 		var vertexSize = vertexFormat.stride << 2;
@@ -666,7 +674,7 @@ class HMDOut extends BaseLibrary {
 			var shape = new BlendShape();
 			shape.name = s.name;
 			shape.geom = -1;
-			var indexes = s.get("Indexes").getFloats();//shapeIndexes[i];
+			var indexes = s.get("Indexes").getFloats();
 			var verts = s.get("Vertices").getFloats();
 			var normals = s.get("Normals").getFloats();
 			var uvs = s.get("UVs", true)?.getFloats();
@@ -794,6 +802,218 @@ class HMDOut extends BaseLibrary {
 		return { lodLevel : -1, modelName : null };
 	}
 
+	function buildColliders(g : hxd.fmt.hmd.Data, model : Model, geom : hxd.fmt.fbx.Geometry, bounds : h3d.col.Bounds) {
+		var maxConvexHulls = generateCollides.maxConvexHulls;
+		var dim = bounds.dimension();
+		if ( dim <= generateCollides.precision )
+			return null;
+
+		var subdiv = Math.ceil(dim / generateCollides.precision);
+		subdiv = Math.imin(subdiv, generateCollides.maxSubdiv);
+		var maxResolution = subdiv * subdiv * subdiv;
+
+		var verts = geom.getVertices();
+		var index = geom.getPolygons();
+		var gm = geom.getGeomMatrix();
+		var mats = geom.getMaterials();
+
+		var vertexCount = Std.int(verts.length / 3);
+
+		var convexPoints : Array<Array<h3d.Vector>> = [];
+		var convexIndexes32 : Array<Array<Int>> = [];
+
+		function iterVertex(cb : Float -> Float -> Float -> Void) {
+			var tmp = new h3d.Vector();
+			for ( i in 0...Std.int(verts.length / 3) ) {
+				var x = verts[i*3];
+				var y = verts[i*3+1];
+				var z = verts[i*3+2];
+				if ( gm != null ) {
+					tmp.set(x, y, z);
+					tmp.transform(gm);
+					x = tmp.x;
+					y = tmp.y;
+					z = tmp.z;
+				}
+				cb(x, y, z);
+			}
+		}
+
+		function iterTriangle(cb : Int -> Void) {
+			inline function unpackIndex(i : Int) {
+				return i < 0 ? -i - 1 : i;
+			}
+			var triangleCount = 0;
+			for ( i in 0...Std.int(index.length / 3) ) {
+				var mat = mats == null ? 0 : mats[i];
+				if ( mat >= d.materials.length )
+					continue;
+				cb(unpackIndex(index[3*i]));
+				cb(unpackIndex(index[3*i+1]));
+				cb(unpackIndex(index[3*i+2]));
+				triangleCount++;
+			}
+			return triangleCount;
+		}
+
+		#if (hl && hl_ver >= version("1.15.0"))
+		var vertices = new hl.Bytes(verts.length * 3 * 4);
+		var i = 0;
+		iterVertex(function(x : Float, y : Float, z : Float) {
+			vertices.setF32(4 * i * 3, x);
+			vertices.setF32(4 * (i * 3 + 1), y);
+			vertices.setF32(4 * (i * 3 + 2), z);
+			i++;
+		});
+		var indexes = new hl.Bytes(index.length * 4);
+		var pos = 0;
+		var triangleCount = iterTriangle(function(index : Int) {
+			indexes.setI32(4 * pos++, index);
+		});
+		if ( triangleCount == 0 )
+			return null;
+
+		var startStamp = haxe.Timer.stamp();
+
+		var vhacdInstance = new hxd.tools.VHACD();
+		var params = new hxd.tools.VHACD.Parameters();
+		params.maxConvexHulls = maxConvexHulls;
+		params.maxResolution = maxResolution;
+		vhacdInstance.compute(vertices, vertexCount, indexes, triangleCount, params);
+		var convexHullCount = vhacdInstance.getConvexHullCount();
+		if ( convexHullCount == 0 )
+			return null;
+
+		var convexHull = new hxd.tools.VHACD.ConvexHull();
+		for ( i in 0...convexHullCount) {
+			vhacdInstance.getConvexHull(i, convexHull);
+			var pointCount = convexHull.pointCount;
+			var pos = 0;
+			var pointsBytes = convexHull.points;
+			var points = [];
+			for ( _ in 0...pointCount ) {
+				var x = pointsBytes.getF64(8*pos++);
+				var y = pointsBytes.getF64(8*pos++);
+				var z = pointsBytes.getF64(8*pos++);
+				points.push(new h3d.Vector(x, y, z));
+			}
+			convexPoints.push(points);
+
+			var triangleCount = convexHull.triangleCount;
+			var triangles = convexHull.triangles;
+			var pos = 0;
+			var indexes = [];
+			for ( _ in 0...triangleCount ) {
+				indexes.push(triangles.getI32(4*pos++));
+				indexes.push(triangles.getI32(4*pos++));
+				indexes.push(triangles.getI32(4*pos++));
+			}
+			convexIndexes32.push(indexes);
+		}
+		vhacdInstance.release();
+		#elseif (sys || nodejs)
+		var fileName = tmpFile("vhacd_data");
+		var outFile = fileName+".out";
+
+		var outputData = new haxe.io.BytesBuffer();
+		outputData.addInt32(vertexCount);
+		iterVertex(function(x : Float, y : Float, z : Float) {
+			outputData.addFloat(x);
+			outputData.addFloat(y);
+			outputData.addFloat(z);
+		});
+		var triangleCount = iterTriangle(function(_) {});
+		if ( triangleCount == 0 )
+			return null;
+
+		var startStamp = haxe.Timer.stamp();
+
+		outputData.addInt32(triangleCount);
+		iterTriangle(function(index : Int) {
+			outputData.addInt32(index);
+		});
+		sys.io.File.saveBytes(fileName, outputData.getBytes());
+		var ret = try Sys.command("meshTools",["vhacd",fileName,outFile,'$maxConvexHulls','$maxResolution']) catch( e : Dynamic ) -1;
+		if( ret != 0 ) {
+			sys.FileSystem.deleteFile(fileName);
+			throw "Failed to call 'vhacd' executable required to generate collision data. Please ensure it's in your PATH"+(filePath == null ? "" : ' ($filePath)');
+		}
+		var bytes = sys.io.File.getBytes(outFile);
+
+		var i = 0;
+		var convexHullCount = bytes.getInt32(i++<<2);
+		for ( _ in 0...convexHullCount ) {
+			var pointCount = bytes.getInt32(i++<<2);
+			var points = [];
+			for ( _ in 0...pointCount ) {
+				var x = bytes.getDouble(i<<2);
+				i += 2;
+				var y = bytes.getDouble(i<<2);
+				i += 2;
+				var z = bytes.getDouble(i<<2);
+				i += 2;
+				var point = new h3d.Vector(x, y, z);
+				points.push(point);
+			}
+			convexPoints.push(points);
+
+			var triangleCount = bytes.getInt32(i++<<2);
+			var indexes = [];
+			for ( _ in 0...triangleCount ) {
+				indexes.push(bytes.getInt32(i++<<2));
+				indexes.push(bytes.getInt32(i++<<2));
+				indexes.push(bytes.getInt32(i++<<2));
+			}
+			convexIndexes32.push(indexes);
+		}
+
+		sys.FileSystem.deleteFile(fileName);
+		sys.FileSystem.deleteFile(outFile);
+		#end
+
+		var collider = new Collider();
+		collider.vertexCounts = [];
+		collider.indexCounts = [];
+		var is32 = [];
+
+		collider.vertexPosition = dataOut.length;
+		for ( i in 0...convexPoints.length ) {
+			var points = convexPoints[i];
+			var indexes = convexIndexes32[i];
+			for ( p in points ) {
+				dataOut.writeFloat(p.x);
+				dataOut.writeFloat(p.y);
+				dataOut.writeFloat(p.z);
+			}
+
+			collider.vertexCounts.push(points.length);
+			collider.indexCounts.push(indexes.length);
+
+			is32.push(points.length > 0x10000);
+		}
+
+		collider.indexPosition = dataOut.length;
+		for ( i => indexes in convexIndexes32 ) {
+			var is32 = is32[i];
+			if( is32 ) {
+				for( i in indexes )
+					dataOut.writeInt32(i);
+			} else {
+				for( i in indexes )
+					dataOut.writeUInt16(i);
+			}
+		}
+
+		if ( d.colliders == null )
+			d.colliders = [];
+		model.collider = d.colliders.length;
+		if( model.props == null ) model.props = [];
+		model.props.push(HasCollider);
+		d.colliders.push(collider);
+
+		return collider;
+	}
+
 	function addModels(includeGeometry) {
 
 		var root = buildHierarchy().root;
@@ -1050,12 +1270,12 @@ class HMDOut extends BaseLibrary {
 
 			var gdata = hgeom.get(g.getId());
 			if( gdata == null ) {
-				var geom =
-				// try {
-					buildGeom(new hxd.fmt.fbx.Geometry(this, g), skin, dataOut, hasNormalMap || generateTangents);
-				// } catch ( e : Dynamic ) {
-				// 		throw e + " in " + model.name;
-				// }
+				var geomData = new hxd.fmt.fbx.Geometry(this, g);
+
+				var geom = buildGeom(geomData, skin, dataOut, hasNormalMap || generateTangents);
+				if ( generateCollides != null )
+					buildColliders(d, model, geomData, geom.g.bounds);
+
 				gdata = { gid : d.geometries.length, materials : geom.materials };
 				d.geometries.push(geom.g);
 				hgeom.set(g.getId(), gdata);

+ 12 - 0
hxd/fmt/hmd/Data.hx

@@ -12,6 +12,7 @@ enum Property<T> {
 	HasExtraTextures;
 	FourBonesByVertex;
 	HasLod;
+	HasCollider;
 }
 
 typedef Properties = Null<Array<Property<Dynamic>>>;
@@ -91,6 +92,15 @@ class BlendShape {
 	}
 }
 
+class Collider {
+	public var vertexCounts : Array<Int>;
+	public var vertexPosition : DataPosition;
+	public var indexCounts : Array<Int>;
+	public var indexPosition : DataPosition;
+	public function new() {
+	}
+}
+
 class Material {
 
 	public var name : String;
@@ -141,6 +151,7 @@ class Model {
 	public var materials : Null<Array<Index<Material>>>;
 	public var skin : Null<Skin>;
 	public var lods : Array<Index<Model>>;
+	public var collider : Null<Index<Collider>>;
 	public function new() {
 	}
 }
@@ -206,6 +217,7 @@ class Data {
 	public var models : Array<Model>;
 	public var animations : Array<Animation>;
 	public var shapes : Array<BlendShape>;
+	public var colliders : Array<Collider>;
 	public var dataPosition : Int;
 	public var data : haxe.io.Bytes;
 

+ 24 - 4
hxd/fmt/hmd/Reader.hx

@@ -25,6 +25,8 @@ class Reader {
 			return FourBonesByVertex;
 		case 4:
 			return HasLod;
+		case 5:
+			return HasCollider;
 		case unk:
 			throw "Unknown property #" + unk;
 		}
@@ -190,6 +192,7 @@ class Reader {
 		}
 
 		d.models = [];
+		var hasCollider = false;
 		for( k in 0...i.readInt32() ) {
 			var m = new Model();
 			m.props = readProps();
@@ -206,8 +209,13 @@ class Reader {
 			for( k in 0...matCount )
 				m.materials.push(i.readInt32());
 			m.skin = readSkin();
-			if ( m.props != null )
-				m.lods = m.props.indexOf(HasLod) >= 0 ? readLods() : null;
+			if ( m.props != null ) {
+				m.lods = m.props.contains(HasLod) ? readLods() : null;
+				if ( m.props.contains(HasCollider) ) {
+					m.collider = i.readInt32();
+					hasCollider = true;
+				}
+			}
 		}
 
 		d.animations = [];
@@ -243,8 +251,7 @@ class Reader {
 		}
 
 		if ( d.version >= 4 ) {
-			var shapeLength : Int;
-			shapeLength = i.readInt32();
+			var shapeLength = i.readInt32();
 			d.shapes = [];
 			for ( k in 0...shapeLength ) {
 				var s = new BlendShape();
@@ -259,6 +266,19 @@ class Reader {
 			}
 		}
 
+		if ( hasCollider ) {
+			d.colliders = [];
+			var colliderLength = i.readInt32();
+			for ( k in 0...colliderLength ) {
+				var c = new Collider();
+				var n = i.readInt32();
+				c.vertexCounts = [for ( v in 0...n) i.readInt32()];
+				c.vertexPosition = i.readInt32();
+				c.indexCounts = [for ( v in 0...n) i.readInt32()];
+				c.indexPosition = i.readInt32();
+				d.colliders.push(c);
+			}
+		}
 
 		return d;
 	}

+ 20 - 0
hxd/fmt/hmd/Writer.hx

@@ -19,6 +19,7 @@ class Writer {
 		case HasExtraTextures:
 		case FourBonesByVertex:
 		case HasLod:
+		case HasCollider:
 		}
 	}
 
@@ -37,6 +38,8 @@ class Writer {
 	}
 
 	function writeName( name : String ) {
+		if ( name == "" )
+			throw "?";
 		if( name == null ) {
 			out.writeByte(0xFF);
 			return;
@@ -175,6 +178,8 @@ class Writer {
 				for ( lod in m.lods )
 					out.writeInt32(lod);
 			}
+			if ( m.collider != null && m.collider >= 0 )
+				out.writeInt32(m.collider);
 		}
 
 		out.writeInt32(d.animations.length);
@@ -216,6 +221,21 @@ class Writer {
 			out.writeInt32(s.remapPosition);
 		}
 
+		if ( d.colliders != null ) {
+			out.writeInt32(d.colliders.length);
+			for ( c in d.colliders ) {
+				out.writeInt32(c.vertexCounts.length);
+				for ( v in c.vertexCounts )
+					out.writeInt32(v);
+				out.writeInt32(c.vertexPosition);
+				if ( c.indexCounts.length != c.vertexCounts.length )
+					throw "assert";
+				for ( i in c.indexCounts )
+					out.writeInt32(i);
+				out.writeInt32(c.indexPosition);
+			}
+		}
+
 		var bytes = header.getBytes();
 		out = old;
 

+ 7 - 0
hxd/fs/Convert.hx

@@ -90,6 +90,13 @@ class ConvertFBX2HMD extends Convert {
 				hmdout.maxBonesPerSkin = params.maxBones;
 			if (params.tangents != null)
 				hmdout.generateTangents = true;
+			if (params.collide != null) {
+				var collide = params.collide;
+				hmdout.generateCollides = { precision : collide.precision,
+					maxConvexHulls : collide.maxConvexHulls,
+					maxSubdiv : collide.maxSubdiv
+				};
+			}
 			if (params.lowp != null) {
 				var m:haxe.DynamicAccess<String> = params.lowp;
 				hmdout.lowPrecConfig = [];