Browse Source

fixed skin collider wrt split skins and optimized using bounds pre-check
improved skin bounds calculation using geometry data and bones offsets

Nicolas Cannasse 4 years ago
parent
commit
df7efda3ae
4 changed files with 191 additions and 34 deletions
  1. 4 0
      h3d/anim/Skin.hx
  2. 25 1
      h3d/col/SkinCollider.hx
  3. 30 16
      h3d/scene/Skin.hx
  4. 132 17
      hxd/fmt/hmd/Library.hx

+ 4 - 0
h3d/anim/Skin.hx

@@ -10,6 +10,10 @@ class Joint {
 	public var transPos : h3d.Matrix; // inverse pose matrix
 	public var parent : Joint;
 	public var subs : Array<Joint>;
+
+	public var offsets : h3d.col.Bounds;
+	public var offsetRay : Float;
+
 	/**
 		When animated, we will use the default bind pose translation instead of the animated translation,
 		enabling retargeting on a skeleton with different proportions

+ 25 - 1
h3d/col/SkinCollider.hx

@@ -6,22 +6,31 @@ class SkinCollider implements hxd.impl.Serializable implements Collider {
 
 	@:s var obj : h3d.scene.Skin;
 	@:s var col : PolygonBuffer;
+	var currentBounds : h3d.col.Bounds;
 	var transform : PolygonBuffer;
-	var lastFrame : Int;
+	var lastFrame = -1;
+	var lastBoundsFrame = -1;
 
 	public function new( obj, col ) {
 		this.obj = obj;
 		this.col = col;
 		this.transform = new PolygonBuffer();
 		this.transform.setData(col.buffer.copy(), col.indexes, col.startIndex, col.triCount);
+		currentBounds = new h3d.col.Bounds();
 	}
 
 	public function contains(p) {
+		checkBounds();
+		if( !currentBounds.contains(p) )
+			return false;
 		applyTransform();
 		return transform.contains(p);
 	}
 
 	public function inFrustum(p, ?m : h3d.Matrix ) {
+		checkBounds();
+		if( !currentBounds.inFrustum(p,m) )
+			return false;
 		if( m != null )
 			throw "Not implemented";
 		applyTransform();
@@ -29,15 +38,30 @@ class SkinCollider implements hxd.impl.Serializable implements Collider {
 	}
 
 	public function inSphere( s : Sphere ) {
+		checkBounds();
+		if( !currentBounds.inSphere(s) )
+			return false;
+		applyTransform();
 		throw "Not implemented";
 		return false;
 	}
 
 	public function rayIntersection(r, bestMatch) {
+		checkBounds();
+		if( currentBounds.rayIntersection(r, false) < 0 )
+			return -1.;
 		applyTransform();
 		return transform.rayIntersection(r, bestMatch);
 	}
 
+	function checkBounds() {
+		if( !obj.jointsUpdated && lastBoundsFrame == obj.lastFrame ) return;
+		lastBoundsFrame = obj.lastFrame;
+		obj.syncJoints();
+		currentBounds.empty();
+		obj.getBoundsRec(currentBounds);
+	}
+
 	function applyTransform() {
 		if( !obj.jointsUpdated && lastFrame == obj.lastFrame ) return;
 		lastFrame = obj.lastFrame;

+ 30 - 16
h3d/scene/Skin.hx

@@ -94,23 +94,37 @@ class Skin extends MultiMaterial {
 	}
 
 	override function getBoundsRec( b : h3d.col.Bounds ) {
+		// ignore primitive bounds !
+		var old = primitive;
+		primitive = null;
 		b = super.getBoundsRec(b);
-		var tmp = primitive.getBounds().clone();
-		var b0 = skinData.allJoints[0];
-		// not sure if that's the good joint
-		if( b0 != null && b0.parent == null ) {
-			var mtmp = absPos.clone();
-			var r = currentRelPose[b0.index];
-			if( r != null )
-				mtmp.multiply3x4(r, mtmp);
-			else
-				mtmp.multiply3x4(b0.defMat, mtmp);
-			if( b0.transPos != null )
-				mtmp.multiply3x4(b0.transPos, mtmp);
-			tmp.transform(mtmp);
-		} else
-			tmp.transform(absPos);
-		b.add(tmp);
+		primitive = old;
+		if( flags.has(FIgnoreBounds) )
+			return b;
+		syncJoints();
+		if( skinData.vertexWeights == null )
+			cast(primitive, h3d.prim.HMDModel).loadSkin(skinData);
+		for( j in skinData.allJoints ) {
+			if( j.offsetRay < 0 ) continue;
+			var m = currentPalette[j.bindIndex];
+			var pt = j.offsets.getMin();
+			pt.transform(m);
+			b.addSpherePos(pt.x, pt.y, pt.z, j.offsetRay);
+			var pt = j.offsets.getMax();
+			pt.transform(m);
+			b.addSpherePos(pt.x, pt.y, pt.z, j.offsetRay);
+		}
+		return b;
+	}
+
+	public function getCurrentSkeletonBounds() {
+		syncJoints();
+		var b = new h3d.col.Bounds();
+		for( j in skinData.allJoints ) {
+			if( j.bindIndex < 0 ) continue;
+			var r = currentAbsPose[j.index];
+			b.addSpherePos(r.tx, r.ty, r.tz, 0);
+		}
 		return b;
 	}
 

+ 132 - 17
hxd/fmt/hmd/Library.hx

@@ -28,7 +28,6 @@ class Library {
 	var cachedPrimitives : Array<h3d.prim.HMDModel>;
 	var cachedAnimations : Map<String, h3d.anim.Animation>;
 	var cachedSkin : Map<String, h3d.anim.Skin>;
-	var tmp = haxe.io.Bytes.alloc(4);
 
 	public function new(res,  header) {
 		this.resource = res;
@@ -567,30 +566,146 @@ class Library {
 	}
 
 	@:allow(h3d.anim.Skin)
-	public function loadSkin( geom : Geometry, skin : h3d.anim.Skin ) {
+	public function loadSkin( geom : Geometry, skin : h3d.anim.Skin, optimize = true ) {
 		if( skin.vertexWeights != null )
 			return;
+
+		if( skin.bonesPerVertex != 3 )
+			throw "assert";
+
 		@:privateAccess skin.vertexCount = geom.vertexCount;
-		var w = getBuffers(geom, [new hxd.fmt.hmd.Data.GeometryFormat("weights", DVec3)]).vertexes;
+		var data = getBuffers(geom, [new hxd.fmt.hmd.Data.GeometryFormat("position",DVec3),new hxd.fmt.hmd.Data.GeometryFormat("weights",DVec3),new hxd.fmt.hmd.Data.GeometryFormat("indexes",DBytes4)]);
 		skin.vertexWeights = new haxe.ds.Vector(skin.vertexCount * skin.bonesPerVertex);
 		skin.vertexJoints = new haxe.ds.Vector(skin.vertexCount * skin.bonesPerVertex);
-		for( i in 0...skin.vertexWeights.length )
-			skin.vertexWeights[i] = w[i];
-		var vidx = getBuffers(geom, [new hxd.fmt.hmd.Data.GeometryFormat("indexes", DBytes4)]).vertexes;
-		var j = 0;
-		for( i in 0...skin.vertexCount ) {
-			var v = ftoint32(vidx[i]);
-			skin.vertexJoints[j++] = v & 0xFF;
-			skin.vertexJoints[j++] = (v >> 8) & 0xFF;
-			skin.vertexJoints[j++] = (v >> 16) & 0xFF;
+
+		for( j in skin.boundJoints )
+			j.offsets = new h3d.col.Bounds();
+
+		var vbuf = data.vertexes;
+		var idx = 0;
+		var bounds = new h3d.col.Bounds();
+		var out = Math.NaN;
+		var ranges;
+		if( skin.splitJoints == null ) {
+			var jointsByBind = [];
+			for( j in skin.boundJoints )
+				jointsByBind[j.bindIndex] = j;
+			ranges = [{ index : 0, pos : 0, count : data.indexes.length, joints : jointsByBind }];
+		} else {
+			var idx = 0;
+			var triPos = [], pos = 0;
+			for( n in geom.indexCounts ) {
+				triPos.push(pos);
+				pos += n;
+			}
+			ranges = [for( j in skin.splitJoints ) @:privateAccess {
+				index : idx,
+				pos : triPos[idx],
+				count : geom.indexCounts[idx++],
+				joints : j.joints,
+			}];
 		}
-	}
 
-	function ftoint32( v : hxd.impl.Float32 ) : Int {
-		tmp.setFloat(0, v);
-		return tmp.getInt32(0);
-	}
 
+		// for each joint, calculate the bounds of vertexes skinned to this joint, in absolute position
+		for( r in ranges ) {
+			for( idx in r.pos...r.pos+r.count ) {
+				var vidx = data.indexes[idx];
+				var p = vidx * 7;
+				var x = vbuf[p];
+				if( x != x ) {
+					// already processed
+					continue;
+				}
+				vbuf[p++] = out;
+				var y = vbuf[p++];
+				var z = vbuf[p++];
+				var w1 = vbuf[p++];
+				var w2 = vbuf[p++];
+				var w3 = vbuf[p++];
+
+				var vout = vidx * 3;
+				skin.vertexWeights[vout] = w1;
+				skin.vertexWeights[vout+1] = w2;
+				skin.vertexWeights[vout+2] = w3;
+
+				var w = (w1 == 0 ? 1 : 0) | (w2 == 0 ? 2 : 0) | (w3 == 0 ? 4 : 0);
+				var idx = haxe.io.FPHelper.floatToI32(vbuf[p++]);
+				bounds.addPos(x,y,z);
+				for( i in 0...3 ) {
+					if( w & (1<<i) != 0 ) {
+						skin.vertexJoints[vout++] = -1;
+						continue;
+					}
+					var idx = (idx >> (i<<3)) & 0xFF;
+					var j = r.joints[idx];
+					j.offsets.addPos(x,y,z);
+					skin.vertexJoints[vout++] = j.bindIndex;
+				}
+			}
+		}
 
+		if( optimize ) {
+			var idx = skin.allJoints.length - 1;
+			var optOut = 0;
+			var refVolume = bounds.getVolume();
+			while( idx >= 0 ) {
+				var j = skin.allJoints[idx--];
+				if( j.offsets == null || j.parent == null || j.parent.offsets == null ) continue;
+				var poff = j.parent.offsets;
+
+				// assume our joints will only rotate
+				var sp = j.offsets.toSphere();
+				if( poff.containsSphere(sp) ) {
+					j.offsets = null;
+					optOut++;
+					continue;
+				}
+
+				var pext = poff.clone();
+				pext.addSphere(sp);
+
+				// heuristic to allow children bounds to be merged within parent
+				// this allow to calculate less joints when getting skin bounds
+				var ratio = Math.sqrt((refVolume * 1.5) / pext.getVolume());
+				var k = pext.getVolume() / poff.getVolume();
+
+				if( k < ratio ) {
+					j.parent.offsets = pext;
+					j.offsets = null;
+					optOut++;
+					continue;
+				}
+			}
+		}
+
+		// transform bounds into two spheres aligned on largest
+		// size. this allows Skin.getBounds to perform two transforms
+		// insteas of height for each bounds corners
+		for( j in skin.allJoints ) {
+			if( j.offsets == null ) {
+				j.offsetRay = -1;
+				continue;
+			}
+			var b = j.offsets;
+			var pt1, pt2, off = b.getCenter(), r;
+			if( b.xSize > b.ySize && b.xSize > b.zSize ) {
+				r = Math.max(b.ySize, b.zSize) * 0.5;
+				pt1 = new h3d.col.Point(b.xMin + r, off.y, off.z);
+				pt2 = new h3d.col.Point(b.xMax - r, off.y, off.z);
+			} else if( b.ySize > b.zSize ) {
+				r = Math.max(b.xSize, b.zSize) * 0.5;
+				pt1 = new h3d.col.Point(off.x, b.yMin + r, off.z);
+				pt2 = new h3d.col.Point(off.x, b.yMax - r, off.z);
+			} else {
+				r = Math.max(b.xSize, b.ySize) * 0.5;
+				pt1 = new h3d.col.Point(off.x, off.y, b.zMin + r);
+				pt2 = new h3d.col.Point(off.x, off.y, b.zMax - r);
+			}
+			b.setMin(pt1);
+			b.setMax(pt2);
+			j.offsetRay = r;
+		}
+	}
 
 }