浏览代码

Added MeshOptimizer and VHACD to heaps. (#1251)

Added MeshOptimizer and VHACD to heaps. Changed mikktspace tool to meshTool to handle VHACD and meshoptimizer.
Benoit Toth 9 月之前
父节点
当前提交
a77a282442

+ 1 - 1
.gitignore

@@ -10,5 +10,5 @@ bin
 /samples/build
 /*.xml
 .vscode
-/tools/mikktspace/out
+/tools/meshtools/out
 *.exe

+ 93 - 0
hxd/tools/MeshOptimizer.hx

@@ -0,0 +1,93 @@
+package hxd.tools;
+
+#if (hl && hl_ver >= version("1.15.0"))
+class MeshOptimizer {
+	/**
+	Generates a vertex remap table from the vertex buffer and an optional index buffer and returns number of unique vertices
+	As a result, all vertices that are binary equivalent map to the same (new) location, with no gaps in the resulting sequence.
+	Resulting remap table maps old vertices to new vertices and can be used in remapVertexBuffer/remapIndexBuffer.
+	Note that binary equivalence considers all vertexSize bytes, including padding which should be zero-initialized.
+
+	remapOut must contain enough space for the resulting remap table (vertexCount elements)
+	indices can be null if the input is unindexed
+	**/
+	@:hlNative("heaps", "generate_vertex_remap")
+	public static function generateVertexRemap(remapOut:hl.Bytes, indices:hl.Bytes, indexCount:Int, vertices:hl.Bytes, vertexCount:Int, vertexSize:Int) : Int {
+		return 0;
+	}
+
+	/**
+	Generate index buffer from the source index buffer and remap table generated by generateVertexRemap
+
+	indicesOut must contain enough space for the resulting index buffer (indexCount elements)
+	indicesIn can be null if the input is unindexed
+	**/
+	@:hlNative("heaps", "remap_index_buffer")
+	public static function remapIndexBuffer(indicesOut:hl.Bytes, indicesIn:hl.Bytes, indexCount:Int, remap:hl.Bytes) {}
+
+	/**
+	Generates vertex buffer from the source vertex buffer and remap table generated by generateVertexRemap
+
+	vertexOut must contain enough space for the resulting vertex buffer (vertexCount elements, returned by generateVertexRemap)
+	vertexCount should be the initial vertex count and not the value returned by generateVertexRemap
+	**/
+	@:hlNative("heaps", "remap_vertex_buffer")
+	public static function remapVertexBuffer(verticesOut:hl.Bytes, verticesIn:hl.Bytes, vertexCount:Int, vertexSize:Int, remap:hl.Bytes) {}
+
+	/**
+	Mesh simplifier
+	Reduces the number of triangles in the mesh, attempting to preserve mesh appearance as much as possible
+	The algorithm tries to preserve mesh topology and can stop short of the target goal based on topology constraints or target error.
+	If not all attributes from the input mesh are required, it's recommended to reindex the mesh without them prior to simplification.
+	Returns the number of indices after simplification, with destination containing new index data
+	The resulting index buffer references vertices from the original vertex buffer.
+	If the original vertex data isn't required, creating a compact vertex buffer using optimizeVertexFetch is recommended.
+
+	indicesOut must contain enough space for the target index buffer, worst case is indexCount elements (*not* targetIndexCount)!
+	vertices should have float3 position in the first 12 bytes of each vertex
+	targetError represents the error relative to mesh extents that can be tolerated, e.g. 0.01 = 1% deformation; value range [0..1]
+	options must be a bitmask composed of meshopt_SimplifyX options; 0 is a safe default
+	resultErrorOut can be null; when it's not null, it will contain the resulting (relative) error after simplification
+	**/
+	@:hlNative("heaps", "simplify")
+	public static function simplify(indicesOut:hl.Bytes, indicesIn:hl.Bytes, indexCount:Int, vertices:hl.Bytes, vertexCount:Int, vertexSize:Int, targetIndexCount:Int, targetError:Single, options:Int, resultErrorOut:hl.Bytes) : Int {
+		return 0;
+	}
+
+	/**
+	Vertex transform cache optimizer
+	Reorders indices to reduce the number of GPU vertex shader invocations
+	If index buffer contains multiple ranges for multiple draw calls, this functions needs to be called on each range individually.
+
+	indicesOut must contain enough space for the resulting index buffer (indexCount elements)
+	**/
+	@:hlNative("heaps", "optimize_vertex_cache")
+	public static function optimizeVertexCache(indicesOut:hl.Bytes, indicesIn:hl.Bytes, indexCount:Int, vertexCount:Int) {}
+
+	/**
+	Overdraw optimizer
+	Reorders indices to reduce the number of GPU vertex shader invocations and the pixel overdraw
+	If index buffer contains multiple ranges for multiple draw calls, this functions needs to be called on each range individually.
+
+	indicesOut must contain enough space for the resulting index buffer (indexCount elements)
+	indicesIn must contain index data that is the result of optimizeVertexCache (*not* the original mesh indices!)
+	vertices should have float3 position in the first 12 bytes of each vertex
+	threshold indicates how much the overdraw optimizer can degrade vertex cache efficiency (1.05 = up to 5%) to reduce overdraw more efficiently
+	**/
+	@:hlNative("heaps", "optimize_overdraw")
+	public static function optimizeOverdraw(indicesOut:hl.Bytes, indicesIn:hl.Bytes, indexCount:Int, vertices:hl.Bytes, vertexCount:Int, vertexSize:Int, threshold:Single) {}
+
+	/**
+	Vertex fetch cache optimizer
+	Reorders vertices and changes indices to reduce the amount of GPU memory fetches during vertex processing
+	Returns the number of unique vertices, which is the same as input vertex count unless some vertices are unused
+
+	verticesOut must contain enough space for the resulting vertex buffer (vertexCount elements)
+	indices is used both as an input and as an output index buffer
+	**/
+	@:hlNative("heaps", "optimize_vertex_fetch")
+	public static function optimizeVertexFetch(verticesOut:hl.Bytes, indices:hl.Bytes, indexCount:Int, verticesIn:hl.Bytes, vertexCount:Int, vertexSize:Int) : Int {
+		return 0;
+	}
+}
+#end

+ 6 - 2
hxd/tools/Mikktspace.hx

@@ -1,5 +1,6 @@
 package hxd.tools;
 
+#if hl
 class Mikktspace {
 	public var buffer:hl.BytesAccess<Single>;
 	public var stride:Int;
@@ -14,15 +15,18 @@ class Mikktspace {
 
 	public function new() {}
 
-	#if hl
 	public function compute(threshold = 180.) {
 		if (!_compute(this, threshold))
 			throw "assert";
 	}
 
+	#if (hl_ver >= version("1.15.0"))
+	@:hlNative("heaps", "compute_mikkt_tangents")
+	#else
 	@:hlNative("fmt", "compute_mikkt_tangents")
+	#end
 	static function _compute(m:Dynamic, threshold:Float):Bool {
 		return false;
 	}
-	#end
 }
+#end

+ 103 - 0
hxd/tools/VHACD.hx

@@ -0,0 +1,103 @@
+package hxd.tools;
+
+#if (hl && hl_ver >= version("1.15.0"))
+enum abstract FillMode(Int) {
+	var FLOOD_FILL	= 0;
+	var SURFACE_ONLY = 1;
+	var RAYCAST_FILL = 2;
+}
+
+@:hlNative("heaps", "vhacd_")
+abstract Instance(hl.Abstract<"vhacd">) {
+	public function clean() {}
+
+	public function release() {}
+
+	public function compute(points:hl.Bytes, countPoints:Int, triangles:hl.Bytes, countTriangle:Int, params:Parameters) : Bool {
+		return false;
+	}
+
+	public function get_n_convex_hulls() : Int {
+		return 0;
+	}
+
+	public function get_convex_hull(index:Int, convexHullOut:ConvexHull) : Bool {
+		return false;
+	}
+}
+
+abstract Pointer(haxe.Int64) from haxe.Int64 to haxe.Int64 {}
+
+@:struct class Parameters {
+	var _unused0 : Pointer = 0;
+	var _unused1 : Pointer = 0;
+	var _unused2 : Pointer = 0;
+	public var maxConvexHulls : Int = 64;                       // The maximum number of convex hulls to produce
+	public var maxResolution : Int = 400000;                    // The voxel resolution to use
+	public var minimumVolumePercentErrorAllowed : Float = 1;    // if the voxels are within 1% of the volume of the hull, we consider this a close enough approximation
+	public var maxRecursionDepth : Int = 10;                    // The maximum recursion depth
+	public var shrinkWrap : Bool = true;                        // Whether or not to shrinkwrap the voxel positions to the source mesh on output
+	public var fillMode : FillMode = FLOOD_FILL;                // How to fill the interior of the voxelized mesh
+	public var maxNumVerticesPerCH : Int = 64;                  // The maximum number of vertices allowed in any output convex hull
+	public var asyncACD : Bool = false;                         // Whether or not to run asynchronously, taking advantage of additional cores
+	public var minEdgeLength : Int = 2;                         // Once a voxel patch has an edge length of less than 4 on all 3 sides, we don't keep recursing
+	public var findBestPlane : Bool = false;                    // Whether or not to attempt to split planes along the best location. Experimental feature. False by default.
+	public function new() {
+	}
+}
+
+@:struct class ConvexHull {
+	public var points : hl.Bytes;
+	public var triangles : hl.Bytes;
+	public var pointCount : Int;
+	public var triangleCount : Int;
+	public var volume : Float;
+	public var centerX : Float;
+	public var centerY : Float;
+	public var centerZ : Float;
+	public var meshId : Int;
+	public var boundsMinX : Float;
+	public var boundsMinY : Float;
+	public var boundsMinZ : Float;
+	public var boundsMaxX : Float;
+	public var boundsMaxY : Float;
+	public var boundsMaxZ : Float;
+	public function new(){
+	}
+}
+
+class VHACD {
+
+	var instance : Instance;
+
+	public function new() {
+		instance = createVhacd();
+	}
+
+	public function compute(points : hl.Bytes, countPoints : Int, triangles : hl.Bytes, countTriangle : Int, params : Parameters) {
+		return instance.compute(points, countPoints, triangles, countTriangle, params);
+	}
+
+	public function getConvexHullCount() : Int {
+		return instance.get_n_convex_hulls();
+	}
+
+	public function getConvexHull(index : Int, convexHull: ConvexHull) : Bool {
+		return instance.get_convex_hull(index, convexHull);
+	}
+
+	public function clean() {
+		instance.clean();
+	}
+
+	public function release() {
+		instance.release();
+		instance = null;
+	}
+
+	@:hlNative("heaps", "create_vhacd")
+	static function createVhacd() : Instance {
+		return null;
+	}
+}
+#end

+ 3 - 2
tools/mikktspace/Makefile → tools/meshTools/Makefile

@@ -8,7 +8,8 @@ HASHLINK=/usr/local/lib
 endif
 
 all: codegen
-	gcc -I $HASHLINK -I out out/main.c -lhl -L${HASHLINK_BIN}/fmt.hdll -o mikktspace
+	gcc -I $HASHLINK -I out out/main.c -lhl -L${HASHLINK_BIN}/heaps.hdll -o meshtools
 
 codegen:
-	haxe mikktspace.hxml -D no-compilation
+	haxe meshtools.hxml -D no-compilation
+

+ 153 - 0
tools/meshTools/MeshTools.hx

@@ -0,0 +1,153 @@
+class MeshTools {
+
+	static function main() {
+		var args = Sys.args();
+		if( args.length < 2 )
+			exit();
+
+		switch(args[0]) {
+			case "mikktspace":
+				mikktspace(args);
+			case "optimize":
+				optimize(args);
+			case "simplify":
+				simplify(args);
+			case "vhacd":
+				vhacd(args);
+			default:
+				exit();
+		}
+	}
+
+	static function mikktspace(args) {
+		if ( args.length < 3 )
+			exit();
+
+		var threshold = args.length > 3 ? Std.parseFloat(args[3]) : 180;
+
+		var input = new haxe.io.BytesInput(sys.io.File.getBytes(args[1]));
+		var m = new hxd.tools.Mikktspace();
+		var vertCount = input.readInt32();
+		m.stride = input.readInt32();
+		m.xPos = input.readInt32();
+		m.normalPos = input.readInt32();
+		m.uvPos = input.readInt32();
+		m.buffer = input.read(vertCount * m.stride * 4);
+
+		m.indices = input.readInt32();
+		m.indexes = input.read(m.indices * 4);
+
+		var tangents = haxe.io.Bytes.alloc(4 * 4 * vertCount);
+		tangents.fill(0,tangents.length,0);
+		for( i in 0...vertCount )
+			tangents.setFloat(i * 16, 1);
+		m.tangents = tangents;
+		m.tangentStride = 4;
+		m.tangentPos = 0;
+
+		m.compute(threshold);
+
+		sys.io.File.saveBytes(args[2], tangents);
+	}
+
+	static function optimize(args) {
+		if ( args.length < 3 )
+			exit();
+
+		var input = new haxe.io.BytesInput(sys.io.File.getBytes(args[1]));
+		var vertexCount = input.readInt32();
+		var vertexSize = input.readInt32();
+		var vertices = hl.Bytes.fromBytes(input.read(vertexCount * vertexSize));
+		var indexCount = input.readInt32();
+		var indices = hl.Bytes.fromBytes(input.read(indexCount * 4));
+		var remap = new hl.Bytes(vertexCount * 4);
+		var uniqueVertexCount = hxd.tools.MeshOptimizer.generateVertexRemap(remap, indices, indexCount, vertices, vertexCount, vertexSize);
+		hxd.tools.MeshOptimizer.remapIndexBuffer(indices, indices, indexCount, remap);
+		hxd.tools.MeshOptimizer.remapVertexBuffer(vertices, vertices, vertexCount, vertexSize, remap);
+		vertexCount = uniqueVertexCount;
+		hxd.tools.MeshOptimizer.optimizeVertexCache(indices, indices, indexCount, vertexCount);
+		hxd.tools.MeshOptimizer.optimizeOverdraw(indices, indices, indexCount, vertices, vertexCount, vertexSize, 1.05);
+		vertexCount = hxd.tools.MeshOptimizer.optimizeVertexFetch(vertices, indices, indexCount, vertices, vertexCount, vertexSize);
+
+		var outputData = new haxe.io.BytesBuffer();
+		outputData.addInt32(vertexCount);
+		outputData.add(haxe.io.Bytes.ofData(new haxe.io.BytesData(vertices, vertexCount * vertexSize)));
+		outputData.addInt32(indexCount);
+		outputData.add(haxe.io.Bytes.ofData(new haxe.io.BytesData(indices, indexCount * 4)));
+		sys.io.File.saveBytes(args[2], outputData.getBytes());
+	}
+
+	static function simplify(args) {
+		if ( args.length < 3 )
+			exit();
+
+		var input = new haxe.io.BytesInput(sys.io.File.getBytes(args[1]));
+		var targetIndexCount = Std.parseInt(args[3]);
+		var targetError = args.length > 3 ? Std.parseFloat(args[4]) : 0.05;
+		var vertexCount = input.readInt32();
+		var vertexSize = input.readInt32();
+		var vertices = hl.Bytes.fromBytes(input.read(vertexCount * vertexSize));
+		var indexCount = input.readInt32();
+		var indices = hl.Bytes.fromBytes(input.read(indexCount << 2));
+		var remap = new hl.Bytes(vertexCount << 2);
+		var uniqueVertexCount = hxd.tools.MeshOptimizer.generateVertexRemap(remap, indices, indexCount, vertices, vertexCount, vertexSize);
+		hxd.tools.MeshOptimizer.remapIndexBuffer(indices, indices, indexCount, remap);
+		hxd.tools.MeshOptimizer.remapVertexBuffer(vertices, vertices, vertexCount, vertexSize, remap);
+		vertexCount = uniqueVertexCount;
+		indexCount = hxd.tools.MeshOptimizer.simplify(indices, indices, indexCount, vertices, vertexCount, vertexSize, targetIndexCount, targetError, 0, null);
+		hxd.tools.MeshOptimizer.optimizeVertexCache(indices, indices, indexCount, vertexCount);
+		hxd.tools.MeshOptimizer.optimizeOverdraw(indices, indices, indexCount, vertices, vertexCount, vertexSize, 1.05);
+		vertexCount = hxd.tools.MeshOptimizer.optimizeVertexFetch(vertices, indices, indexCount, vertices, vertexCount, vertexSize);
+
+		var outputData = new haxe.io.BytesBuffer();
+		outputData.addInt32(vertexCount);
+		outputData.add(haxe.io.Bytes.ofData(new haxe.io.BytesData(vertices, vertexCount * vertexSize)));
+		outputData.addInt32(indexCount);
+		outputData.add(haxe.io.Bytes.ofData(new haxe.io.BytesData(indices, indexCount << 2)));
+		sys.io.File.saveBytes(args[2], outputData.getBytes());
+	}
+
+	static function vhacd(args) {
+		if ( args.length < 3 )
+			exit();
+
+		var input = new haxe.io.BytesInput(sys.io.File.getBytes(args[1]));
+		var dataSize = 3 << 2;
+		var pointCount = input.readInt32();
+		var points = hl.Bytes.fromBytes(input.read(pointCount * dataSize));
+		var triangleCount = input.readInt32();
+		var triangles = hl.Bytes.fromBytes(input.read(triangleCount * dataSize));
+		var vhacdInstance = new hxd.tools.VHACD();
+		var params = new hxd.tools.VHACD.Parameters();
+		if ( !vhacdInstance.compute(points, pointCount, triangles, triangleCount, params) ) {
+			Sys.println("Failed to compute convex hulls");
+			Sys.exit(1);
+		}
+		var convexHullCount = vhacdInstance.getConvexHullCount();
+		var convexHull = new hxd.tools.VHACD.ConvexHull();
+		var outputData = new haxe.io.BytesBuffer();
+		outputData.addInt32(convexHullCount);
+		for ( i in 0...convexHullCount) {
+			vhacdInstance.getConvexHull(i, convexHull);
+			var pointCount = convexHull.pointCount;
+			outputData.addInt32(pointCount);
+			outputData.add(haxe.io.Bytes.ofData(new haxe.io.BytesData(convexHull.points, pointCount * dataSize)));
+			var triangleCount = convexHull.triangleCount;
+			outputData.addInt32(triangleCount);
+			outputData.add(haxe.io.Bytes.ofData(new haxe.io.BytesData(convexHull.triangles, triangleCount * dataSize)));
+		}
+		vhacdInstance.clean();
+		vhacdInstance.release();
+	}
+
+	static function exit() {
+		Sys.println("MeshTools :\n
+			meshtools mikktspace [input] [ouput] (angle)\n
+			meshtools optimize [input] [output]\n
+			meshtools simplify [input] [output] [targetCount] (deformationFactor)\n
+			meshtools vhacd [input] [output]
+		");
+		Sys.exit(1);
+	}
+
+}

+ 3 - 0
tools/meshTools/build_msvc.bat

@@ -0,0 +1,3 @@
+haxe meshtools.hxml -D no-compilation
+call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" x64
+cl /Ox /Femeshtools.exe -I %HASHLINK_SRC%/src -I out out/main.c %HASHLINK_SRC%/x64/Release/libhl.lib %HASHLINK_SRC%/x64/Release/heaps.lib

+ 4 - 0
tools/meshTools/meshtools.hxml

@@ -0,0 +1,4 @@
+-hl out/main.c
+-lib heaps
+-main MeshTools
+-D hl-ver=1.15.0

+ 0 - 37
tools/mikktspace/Mikktspace.hx

@@ -1,37 +0,0 @@
-class Mikktspace {
-
-	static function main() {
-		var args = Sys.args();
-		if( args.length < 2 ) {
-			Sys.println("mikkspace [input] [output] (angle)");
-			Sys.exit(1);
-		}
-
-		var threshold = args.length > 2 ? Std.parseFloat(args[2]) : 180;
-
-		var input = new haxe.io.BytesInput(sys.io.File.getBytes(args[0]));
-		var m = new hl.Format.Mikktspace();
-		var vertCount = input.readInt32();
-		m.stride = input.readInt32();
-		m.xPos = input.readInt32();
-		m.normalPos = input.readInt32();
-		m.uvPos = input.readInt32();
-		m.buffer = input.read(vertCount * m.stride * 4);
-
-		m.indices = input.readInt32();
-		m.indexes = input.read(m.indices * 4);
-
-		var tangents = haxe.io.Bytes.alloc(4 * 4 * vertCount);
-		tangents.fill(0,tangents.length,0);
-		for( i in 0...vertCount )
-			tangents.setFloat(i * 16, 1);
-		m.tangents = tangents;
-		m.tangentStride = 4;
-		m.tangentPos = 0;
-
-		m.compute(threshold);
-
-		sys.io.File.saveBytes(args[1], tangents);
-	}
-
-}

+ 0 - 4
tools/mikktspace/build_msvc.bat

@@ -1,4 +0,0 @@
-@echo off
-haxe mikktspace.hxml -D no-compilation
-vcvarsall.bat x64
-cl /Ox /Femikktspace.exe -I %HASHLINK% -I out out/main.c %HASHLINK_BIN%/libhl.lib %HASHLINK_BIN%/fmt.lib

+ 0 - 2
tools/mikktspace/mikktspace.hxml

@@ -1,2 +0,0 @@
--hl out/main.c
--main Mikktspace