Răsfoiți Sursa

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 ani în urmă
părinte
comite
df7efda3ae
4 a modificat fișierele cu 191 adăugiri și 34 ștergeri
  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 transPos : h3d.Matrix; // inverse pose matrix
 	public var parent : Joint;
 	public var parent : Joint;
 	public var subs : Array<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,
 		When animated, we will use the default bind pose translation instead of the animated translation,
 		enabling retargeting on a skeleton with different proportions
 		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 obj : h3d.scene.Skin;
 	@:s var col : PolygonBuffer;
 	@:s var col : PolygonBuffer;
+	var currentBounds : h3d.col.Bounds;
 	var transform : PolygonBuffer;
 	var transform : PolygonBuffer;
-	var lastFrame : Int;
+	var lastFrame = -1;
+	var lastBoundsFrame = -1;
 
 
 	public function new( obj, col ) {
 	public function new( obj, col ) {
 		this.obj = obj;
 		this.obj = obj;
 		this.col = col;
 		this.col = col;
 		this.transform = new PolygonBuffer();
 		this.transform = new PolygonBuffer();
 		this.transform.setData(col.buffer.copy(), col.indexes, col.startIndex, col.triCount);
 		this.transform.setData(col.buffer.copy(), col.indexes, col.startIndex, col.triCount);
+		currentBounds = new h3d.col.Bounds();
 	}
 	}
 
 
 	public function contains(p) {
 	public function contains(p) {
+		checkBounds();
+		if( !currentBounds.contains(p) )
+			return false;
 		applyTransform();
 		applyTransform();
 		return transform.contains(p);
 		return transform.contains(p);
 	}
 	}
 
 
 	public function inFrustum(p, ?m : h3d.Matrix ) {
 	public function inFrustum(p, ?m : h3d.Matrix ) {
+		checkBounds();
+		if( !currentBounds.inFrustum(p,m) )
+			return false;
 		if( m != null )
 		if( m != null )
 			throw "Not implemented";
 			throw "Not implemented";
 		applyTransform();
 		applyTransform();
@@ -29,15 +38,30 @@ class SkinCollider implements hxd.impl.Serializable implements Collider {
 	}
 	}
 
 
 	public function inSphere( s : Sphere ) {
 	public function inSphere( s : Sphere ) {
+		checkBounds();
+		if( !currentBounds.inSphere(s) )
+			return false;
+		applyTransform();
 		throw "Not implemented";
 		throw "Not implemented";
 		return false;
 		return false;
 	}
 	}
 
 
 	public function rayIntersection(r, bestMatch) {
 	public function rayIntersection(r, bestMatch) {
+		checkBounds();
+		if( currentBounds.rayIntersection(r, false) < 0 )
+			return -1.;
 		applyTransform();
 		applyTransform();
 		return transform.rayIntersection(r, bestMatch);
 		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() {
 	function applyTransform() {
 		if( !obj.jointsUpdated && lastFrame == obj.lastFrame ) return;
 		if( !obj.jointsUpdated && lastFrame == obj.lastFrame ) return;
 		lastFrame = obj.lastFrame;
 		lastFrame = obj.lastFrame;

+ 30 - 16
h3d/scene/Skin.hx

@@ -94,23 +94,37 @@ class Skin extends MultiMaterial {
 	}
 	}
 
 
 	override function getBoundsRec( b : h3d.col.Bounds ) {
 	override function getBoundsRec( b : h3d.col.Bounds ) {
+		// ignore primitive bounds !
+		var old = primitive;
+		primitive = null;
 		b = super.getBoundsRec(b);
 		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;
 		return b;
 	}
 	}
 
 

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

@@ -28,7 +28,6 @@ class Library {
 	var cachedPrimitives : Array<h3d.prim.HMDModel>;
 	var cachedPrimitives : Array<h3d.prim.HMDModel>;
 	var cachedAnimations : Map<String, h3d.anim.Animation>;
 	var cachedAnimations : Map<String, h3d.anim.Animation>;
 	var cachedSkin : Map<String, h3d.anim.Skin>;
 	var cachedSkin : Map<String, h3d.anim.Skin>;
-	var tmp = haxe.io.Bytes.alloc(4);
 
 
 	public function new(res,  header) {
 	public function new(res,  header) {
 		this.resource = res;
 		this.resource = res;
@@ -567,30 +566,146 @@ class Library {
 	}
 	}
 
 
 	@:allow(h3d.anim.Skin)
 	@: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 )
 		if( skin.vertexWeights != null )
 			return;
 			return;
+
+		if( skin.bonesPerVertex != 3 )
+			throw "assert";
+
 		@:privateAccess skin.vertexCount = geom.vertexCount;
 		@: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.vertexWeights = new haxe.ds.Vector(skin.vertexCount * skin.bonesPerVertex);
 		skin.vertexJoints = 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;
+		}
+	}
 
 
 }
 }