MBHaxe/src/collision/Collision.hx

474 lines
11 KiB
Haxe

package collision;
import haxe.Exception;
import dif.math.Point3F;
import dif.math.PlaneF;
import h3d.col.Plane;
import h3d.Vector;
typedef ISCResult = {
var result:Bool;
var tSeg:Float;
var tCap:Float;
}
typedef CPSSResult = {
var result:Float;
var s:Float;
var t:Float;
var c1:Vector;
var c2:Vector;
}
typedef ITSResult = {
var result:Bool;
var normal:Vector;
var point:Vector;
}
class Collision {
public static function IntersectLineSphere(start:Vector, end:Vector, center:Vector, radius:Float) {
var d = end.sub(start);
var v = center.sub(start);
var t = v.dot(d) / d.lengthSq();
if (t < 0)
t = 0;
if (t > 1)
t = 1;
var p = start.add(d.multiply(t));
var dist = center.distance(p);
if (dist > radius) {
return null;
} else
return p;
}
public static function ClosestPointLine(start:Vector, end:Vector, center:Vector) {
var d = end.sub(start);
var v = center.sub(start);
var t = v.dot(d) / d.lengthSq();
if (t < 0)
t = 0;
if (t > 1)
t = 1;
var p = start.add(d.multiply(t));
return p;
}
// EdgeData is bitfield
// 001b: v0v1 is edge
// 010b: v1v2 is edge
// 100b: v0v2 is edge
public static function IntersectTriangleSphere(v0:Vector, v1:Vector, v2:Vector, normal:Vector, center:Vector, radius:Float, edgeData:Int,
edgeConcavities:Array<Bool>) {
var radiusSq = radius * radius;
var res:ITSResult = {
result: false,
point: null,
normal: null
};
var pnorm = normal.clone();
var d = -v0.dot(pnorm);
var pdist = center.dot(pnorm) + d;
if (pdist < 0.001) {
return res; // Dont collide internal edges
}
if (pdist < radius) {
var n = normal.normalized();
var t = center.dot(n) - v0.dot(n);
var pt = center.sub(n.multiply(t));
if (PointInTriangle(pt, v0, v1, v2)) {
res.result = true;
res.point = pt;
res.normal = pnorm;
return res;
}
// return res;
}
// Check edges
var r1 = ClosestPointLine(v0, v1, center);
var r2 = ClosestPointLine(v1, v2, center);
var r3 = ClosestPointLine(v2, v0, center);
var chosenEdge = 0; // Bitfield
var chosenPt:Vector;
if (r1.distanceSq(center) < r2.distanceSq(center)) {
chosenPt = r1;
chosenEdge = 1;
} else {
chosenPt = r2;
chosenEdge = 2;
}
if (chosenPt.distanceSq(center) < r3.distanceSq(center))
res.point = chosenPt;
else {
chosenEdge = 4;
res.point = r3;
}
if (res.point.distanceSq(center) <= radiusSq) {
res.result = true;
res.normal = center.sub(res.point).normalized();
if (res.normal.dot(normal) > 0.8) {
// Internal edge
if (chosenEdge & edgeData > 0) {
chosenEdge -= 1;
if (chosenEdge > 2)
chosenEdge--;
// if (edgeNormals[chosenEdge].length() < 0.5) {
// res.normal = center.sub(res.point).normalized();
// } else
if (edgeConcavities[chosenEdge]) { // Our edge is concave
res.normal = pnorm;
}
}
}
return res;
}
return res;
}
public static function TriangleSphereIntersection(A:Vector, B:Vector, C:Vector, N:Vector, P:Vector, r:Float, edgeData:Int, edgeConcavities:Array<Bool>) {
var res:ITSResult = {
result: false,
point: null,
normal: null
};
var v0 = A;
var v1 = B;
var v2 = C;
A = A.sub(P);
B = B.sub(P);
C = C.sub(P);
var ca = C.sub(A);
var ba = B.sub(A);
var radiusSq = r * r;
var cp = ba.cross(ca);
var aDotCp = A.dot(cp);
var cpLenSq = cp.lengthSq();
if (aDotCp * aDotCp > radiusSq * cpLenSq) {
return res;
}
var aSq = A.dot(A);
var aDotB = A.dot(B);
var aDotC = A.dot(C);
var bSq = B.dot(B);
var bDotC = B.dot(C);
var cSq = C.dot(C);
if (aSq > radiusSq && aDotB > aSq && aDotC > aSq) {
return res;
}
if (bSq > radiusSq && aDotB > bSq && bDotC > bSq) {
return res;
}
if (cSq > radiusSq && aDotC > cSq && bDotC > cSq) {
return res;
}
var cSubB = C.sub(B);
var aSubC = A.sub(C);
var baSq = ba.lengthSq();
var cSubBSq = cSubB.lengthSq();
var aSubCSq = aSubC.lengthSq();
var aTest = A.multiply(baSq).sub(ba.multiply(aDotB - aSq));
var bTest = B.multiply(cSubBSq).sub(cSubB.multiply(bDotC - bSq));
var cTest = C.multiply(aSubCSq).sub(aSubC.multiply(aDotC - cSq));
var rhs = C.multiply(baSq).sub(aTest);
var rhs2 = A.multiply(cSubBSq).sub(bTest);
var rhs3 = B.multiply(aSubCSq).sub(cTest);
if (aTest.dot(aTest) > radiusSq * baSq * baSq && aTest.dot(rhs) > 0) {
return res;
}
if (bTest.dot(bTest) > radiusSq * cSubBSq * cSubBSq && bTest.dot(rhs2) > 0) {
return res;
}
if (cTest.dot(cTest) > radiusSq * aSubCSq * aSubCSq && cTest.dot(rhs3) > 0) {
return res;
}
var lhs = P.sub(v0);
var baca = ba.dot(ca);
var caSq = ca.lengthSq();
var lhsBa = lhs.dot(ba);
var lhsCa = lhs.dot(ca);
var len = baSq * caSq - baca * baca;
var d1 = (caSq * lhsBa - baca * lhsCa) / len;
var d2 = (baSq * lhsCa - baca * lhsBa) / len;
if (1 - d1 - d2 >= 0 && d1 >= 0 && d2 >= 0) {
res.result = true;
res.normal = N.clone();
res.point = P.sub(N.multiply(P.sub(v0).dot(N)));
} else {
var closestPt = P.sub(N.multiply(P.sub(v0).dot(N)));
var r1 = ClosestPointLine(v0, v1, closestPt);
var r2 = ClosestPointLine(v1, v2, closestPt);
var r3 = ClosestPointLine(v2, v0, closestPt);
var chosenEdge = 0; // Bitfield
var chosenPt:Vector;
if (r1.distanceSq(P) < r2.distanceSq(P)) {
chosenPt = r1;
chosenEdge = 1;
} else {
chosenPt = r2;
chosenEdge = 2;
}
if (chosenPt.distanceSq(P) < r3.distanceSq(P))
res.point = chosenPt;
else {
chosenEdge = 4;
res.point = r3;
}
res.normal = P.sub(res.point).normalized();
res.result = true;
if (res.normal.dot(N) > 0.8) {
// Internal edge
if (chosenEdge & edgeData > 0) {
chosenEdge -= 1;
if (chosenEdge > 2)
chosenEdge--;
// if (edgeNormals[chosenEdge].length() < 0.5) {
// res.normal = center.sub(res.point).normalized();
// } else
if (edgeConcavities[chosenEdge]) { // Our edge is concave
res.normal = N.clone();
}
}
}
}
return res;
}
public static function IntersectSegmentCapsule(segStart:Vector, segEnd:Vector, capStart:Vector, capEnd:Vector, radius:Float) {
var cpssres = Collision.ClosestPtSegmentSegment(segStart, segEnd, capStart, capEnd);
var res:ISCResult = {
result: cpssres.result < radius * radius,
tSeg: cpssres.s,
tCap: cpssres.t
}
return res;
}
public static function ClosestPtSegmentSegment(p1:Vector, q1:Vector, p2:Vector, q2:Vector) {
var Epsilon = 0.0001;
var d3 = q1.sub(p1);
var d2 = q2.sub(p2);
var r = p1.sub(p2);
var a = d3.dot(d3);
var e = d2.dot(d2);
var f = d2.dot(r);
var res:CPSSResult = {
s: 0,
t: 0,
c1: null,
c2: null,
result: -1
}
if (a <= Epsilon && e <= Epsilon) {
res = {
s: 0,
t: 0,
c1: p1,
c2: p2,
result: p1.sub(p2).dot(p1.sub(p2))
}
return res;
}
if (a <= Epsilon) {
res.s = 0;
res.t = f / e;
if (res.t > 1)
res.t = 1;
if (res.t < 0)
res.t = 0;
} else {
var c3 = d3.dot(r);
if (e <= Epsilon) {
res.t = 0;
if (-c3 / a > 1)
res.s = 1;
else if (-c3 / a < 0)
res.s = 0;
else
res.s = (-c3 / a);
} else {
var b = d3.dot(d2);
var denom = a * e - b * b;
if (denom != 0) {
res.s = (b * f - c3 * e) / denom;
if (res.s > 1)
res.s = 1;
if (res.s < 0)
res.s = 0;
} else {
res.s = 0;
}
res.t = (b * res.s + f) / e;
if (res.t < 0) {
res.t = 0;
res.s = -c3 / a;
if (res.s > 1)
res.s = 1;
if (res.s < 0)
res.s = 0;
} else if (res.t > 1) {
res.t = 1;
res.s = (b - c3) / a;
if (res.s > 1)
res.s = 1;
if (res.s < 0)
res.s = 0;
}
}
}
res.c1 = p1.add(d3.multiply(res.s));
res.c2 = p2.add(d2.multiply(res.t));
res.result = res.c1.sub(res.c2).lengthSq();
return res;
}
public static function PointInTriangle(point:Vector, v0:Vector, v1:Vector, v2:Vector):Bool {
var u = v1.sub(v0);
var v = v2.sub(v0);
var w = point.sub(v0);
var vw = v.cross(w);
var vu = v.cross(u);
if (vw.dot(vu) < 0.0) {
return false;
}
var uw = u.cross(w);
var uv = u.cross(v);
if (uw.dot(uv) < 0.0) {
return false;
}
var d:Float = uv.length();
var r:Float = vw.length() / d;
var t:Float = uw.length() / d;
return (r + t) <= 1;
}
public static function PointInTriangle2(point:Vector, a:Vector, b:Vector, c:Vector):Bool {
var a1 = a.sub(point);
var b1 = b.sub(point);
var c1 = c.sub(point);
var u = b1.cross(c1);
var v = c1.cross(a1);
if (u.dot(v) < 0)
return false;
var w = a1.cross(b1);
return !(u.dot(w) < 0);
}
public static function IntersectTriangleCapsule(start:Vector, end:Vector, radius:Float, p1:Vector, p2:Vector, p3:Vector, normal:Vector, edgeData:Int,
edgeConcavities:Array<Bool>) {
var dir = end.sub(start);
var d = -(p1.dot(normal));
var t = -(start.dot(normal) - d) / dir.dot(normal);
if (t > 1)
t = 1;
if (t < 0)
t = 0;
var tracePoint = start.add(dir.multiply(t));
return IntersectTriangleSphere(p1, p2, p3, normal, tracePoint, radius, edgeData, edgeConcavities);
}
private static function GetLowestRoot(a:Float, b:Float, c:Float, max:Float):Null<Float> {
// check if solution exists
var determinant:Float = b * b - 4.0 * a * c;
// if negative there is no solution
if (determinant < 0.0) {
return null;
}
// calculate two roots
var sqrtD:Float = Math.sqrt(determinant);
var r1:Float = (-b - sqrtD) / (2 * a);
var r2:Float = (-b + sqrtD) / (2 * a);
// set x1 <= x2
if (r1 > r2) {
var temp:Float = r2;
r2 = r1;
r1 = temp;
}
// get lowest root
if (r1 > 0 && r1 < max) {
return r1;
}
if (r2 > 0 && r2 < max) {
return r2;
}
// no solutions
return null;
}
public static function ClosestPtPointTriangle(pt:Vector, radius:Float, p0:Vector, p1:Vector, p2:Vector, normal:Vector) {
var closest:Vector = null;
var ptDot = pt.dot(normal);
var triDot = p0.dot(normal);
if (Math.abs(ptDot - triDot) > radius * 1.1) {
return null;
}
closest = pt.add(normal.multiply(triDot - ptDot));
if (Collision.PointInTriangle2(closest, p0, p1, p2)) {
return closest;
}
var t = 10.0;
var r1 = Collision.IntersectSegmentCapsule(pt, pt, p0, p1, radius);
if (r1.result && r1.tSeg < t) {
closest = p0.add((p1.sub(p0).multiply(r1.tCap)));
t = r1.tSeg;
}
var r2 = Collision.IntersectSegmentCapsule(pt, pt, p1, p2, radius);
if (r2.result && r2.tSeg < t) {
closest = p1.add((p2.sub(p1).multiply(r2.tCap)));
t = r2.tSeg;
}
var r3 = Collision.IntersectSegmentCapsule(pt, pt, p2, p0, radius);
if (r3.result && r3.tSeg < t) {
closest = p2.add((p2.sub(p2).multiply(r3.tCap)));
t = r3.tSeg;
}
var res = t < 1;
if (res) {
return closest;
}
return null;
}
}