Last active
July 4, 2019 18:19
-
-
Save prime31/d08abe4c6191af85e39c1a0fe88670bf to your computer and use it in GitHub Desktop.
C# conversion of https://github.com/RandyGaul/cute_headers/blob/master/cute_c2.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const int C2_GJK_ITERS = 20; | |
public const int C2_MAX_POLYGON_VERTS = 8; | |
// position of impact p = ray.p + ray.d * raycast.t | |
public static c2v c2Impact(c2Ray ray, float t) => c2Add(ray.p, c2Mulvs(ray.d, t)); | |
#region Wrapper Methods | |
public static unsafe bool c2Collided(void* A, c2x* ax, C2_TYPE typeA, void* B, c2x* bx, C2_TYPE typeB) | |
{ | |
switch (typeA) | |
{ | |
case C2_TYPE.C2_CIRCLE: | |
switch (typeB) | |
{ | |
case C2_TYPE.C2_CIRCLE: return c2CircletoCircle(*(c2Circle*)A, *(c2Circle*)B); | |
case C2_TYPE.C2_AABB: return c2CircletoAABB(*(c2Circle*)A, *(c2AABB*)B); | |
case C2_TYPE.C2_CAPSULE: return c2CircletoCapsule(*(c2Circle*)A, *(c2Capsule*)B); | |
case C2_TYPE.C2_POLY: return c2CircletoPoly(*(c2Circle*)A, (c2Poly*)B, bx); | |
default: return false; | |
} | |
case C2_TYPE.C2_AABB: | |
switch (typeB) | |
{ | |
case C2_TYPE.C2_CIRCLE: return c2CircletoAABB(*(c2Circle*)B, *(c2AABB*)A); | |
case C2_TYPE.C2_AABB: return c2AABBtoAABB(*(c2AABB*)A, *(c2AABB*)B); | |
case C2_TYPE.C2_CAPSULE: return c2AABBtoCapsule(*(c2AABB*)A, *(c2Capsule*)B); | |
case C2_TYPE.C2_POLY: return c2AABBtoPoly(*(c2AABB*)A, (c2Poly*)B, bx); | |
default: return false; | |
} | |
case C2_TYPE.C2_CAPSULE: | |
switch (typeB) | |
{ | |
case C2_TYPE.C2_CIRCLE: return c2CircletoCapsule(*(c2Circle*)B, *(c2Capsule*)A); | |
case C2_TYPE.C2_AABB: return c2AABBtoCapsule(*(c2AABB*)B, *(c2Capsule*)A); | |
case C2_TYPE.C2_CAPSULE: return c2CapsuletoCapsule(*(c2Capsule*)A, *(c2Capsule*)B); | |
case C2_TYPE.C2_POLY: return c2CapsuletoPoly(*(c2Capsule*)A, (c2Poly*)B, bx); | |
default: return false; | |
} | |
case C2_TYPE.C2_POLY: | |
switch (typeB) | |
{ | |
case C2_TYPE.C2_CIRCLE: return c2CircletoPoly(*(c2Circle*)B, (c2Poly*)A, ax); | |
case C2_TYPE.C2_AABB: return c2AABBtoPoly(*(c2AABB*)B, (c2Poly*)A, ax); | |
case C2_TYPE.C2_CAPSULE: return c2CapsuletoPoly(*(c2Capsule*)B, (c2Poly*)A, ax); | |
case C2_TYPE.C2_POLY: return c2PolytoPoly((c2Poly*)A, ax, (c2Poly*)B, bx); | |
default: return false; | |
} | |
default: | |
return false; | |
} | |
} | |
public static unsafe void c2Collide(void* A, c2x* ax, C2_TYPE typeA, void* B, c2x* bx, C2_TYPE typeB, c2Manifold* m) | |
{ | |
m->count = 0; | |
switch (typeA) | |
{ | |
case C2_TYPE.C2_CIRCLE: | |
switch (typeB) | |
{ | |
case C2_TYPE.C2_CIRCLE: c2CircletoCircleManifold(*(c2Circle*)A, *(c2Circle*)B, m); break; | |
case C2_TYPE.C2_AABB: c2CircletoAABBManifold(*(c2Circle*)A, *(c2AABB*)B, m); break; | |
case C2_TYPE.C2_CAPSULE: c2CircletoCapsuleManifold(*(c2Circle*)A, *(c2Capsule*)B, m); break; | |
case C2_TYPE.C2_POLY: c2CircletoPolyManifold(*(c2Circle*)A, (c2Poly*)B, bx, m); break; | |
} | |
break; | |
case C2_TYPE.C2_AABB: | |
switch (typeB) | |
{ | |
case C2_TYPE.C2_CIRCLE: c2CircletoAABBManifold(*(c2Circle*)B, *(c2AABB*)A, m); m->n = c2Neg(m->n); break; | |
case C2_TYPE.C2_AABB: c2AABBtoAABBManifold(*(c2AABB*)A, *(c2AABB*)B, m); break; | |
case C2_TYPE.C2_CAPSULE: c2AABBtoCapsuleManifold(*(c2AABB*)A, *(c2Capsule*)B, m); break; | |
case C2_TYPE.C2_POLY: c2AABBtoPolyManifold(*(c2AABB*)A, (c2Poly*)B, bx, m); break; | |
} | |
break; | |
case C2_TYPE.C2_CAPSULE: | |
switch (typeB) | |
{ | |
case C2_TYPE.C2_CIRCLE: c2CircletoCapsuleManifold(*(c2Circle*)B, *(c2Capsule*)A, m); m->n = c2Neg(m->n); break; | |
case C2_TYPE.C2_AABB: c2AABBtoCapsuleManifold(*(c2AABB*)B, *(c2Capsule*)A, m); m->n = c2Neg(m->n); break; | |
case C2_TYPE.C2_CAPSULE: c2CapsuletoCapsuleManifold(*(c2Capsule*)A, *(c2Capsule*)B, m); break; | |
case C2_TYPE.C2_POLY: c2CapsuletoPolyManifold(*(c2Capsule*)A, (c2Poly*)B, bx, m); break; | |
} | |
break; | |
case C2_TYPE.C2_POLY: | |
switch (typeB) | |
{ | |
case C2_TYPE.C2_CIRCLE: c2CircletoPolyManifold(*(c2Circle*)B, (c2Poly*)A, ax, m); m->n = c2Neg(m->n); break; | |
case C2_TYPE.C2_AABB: c2AABBtoPolyManifold(*(c2AABB*)B, (c2Poly*)A, ax, m); m->n = c2Neg(m->n); break; | |
case C2_TYPE.C2_CAPSULE: c2CapsuletoPolyManifold(*(c2Capsule*)B, (c2Poly*)A, ax, m); m->n = c2Neg(m->n); break; | |
case C2_TYPE.C2_POLY: c2PolytoPolyManifold((c2Poly*)A, ax, (c2Poly*)B, bx, m); break; | |
} | |
break; | |
} | |
} | |
public static unsafe int c2CastRay(c2Ray A, void* B, c2x* bx, C2_TYPE typeB, c2Raycast* outt) | |
{ | |
switch (typeB) | |
{ | |
case C2_TYPE.C2_CIRCLE: return c2RaytoCircle(A, *(c2Circle*)B, outt); | |
case C2_TYPE.C2_AABB: return c2RaytoAABB(A, *(c2AABB*)B, outt); | |
case C2_TYPE.C2_CAPSULE: return c2RaytoCapsule(A, *(c2Capsule*)B, outt); | |
case C2_TYPE.C2_POLY: return c2RaytoPoly(A, (c2Poly*)B, bx, outt); | |
} | |
return 0; | |
} | |
#endregion | |
public struct c2Proxy | |
{ | |
public float radius; | |
public int count; | |
public unsafe fixed byte _verts[sizeof(float) * 2 * tiny.C2_MAX_POLYGON_VERTS]; // float2[8] | |
public unsafe c2v* GetVerts() | |
{ | |
fixed (c2Proxy* proxy = &this) | |
return (c2v*)(&proxy->_verts[0]); | |
} | |
} | |
public struct c2sv | |
{ | |
public c2v sA; | |
public c2v sB; | |
public c2v p; | |
public float u; | |
public int iA; | |
public int iB; | |
} | |
public struct c2Simplex | |
{ | |
public c2sv a, b, c, d; | |
public float div; | |
public int count; | |
} | |
public static unsafe void c2MakeProxy(void* shape, C2_TYPE type, out c2Proxy p) | |
{ | |
p = new c2Proxy(); | |
var verts = p.GetVerts(); | |
switch (type) | |
{ | |
case C2_TYPE.C2_CIRCLE: | |
{ | |
var c = Unsafe.Read<c2Circle>(shape); | |
p.radius = c.r; | |
p.count = 1; | |
verts[0] = c.p; | |
} | |
break; | |
case C2_TYPE.C2_AABB: | |
{ | |
var bb = Unsafe.Read<c2AABB>(shape); | |
p.radius = 0; | |
p.count = 4; | |
c2BBVerts(verts, ref bb); | |
} | |
break; | |
case C2_TYPE.C2_CAPSULE: | |
{ | |
var c = Unsafe.Read<c2Capsule>(shape); | |
p.radius = c.r; | |
p.count = 2; | |
verts[0] = c.a; | |
verts[1] = c.b; | |
} | |
break; | |
case C2_TYPE.C2_POLY: | |
{ | |
var poly = Unsafe.Read<c2Poly>(shape); | |
p.radius = 0; | |
p.count = poly.count; | |
c2v* vertices = (c2v*)(&poly._verts[0]); | |
for (var i = 0; i < p.count; ++i) | |
verts[i] = vertices[i]; | |
} | |
break; | |
} | |
} | |
public static unsafe int c2Support(c2v* verts, int count, c2v d) | |
{ | |
var imax = 0; | |
var dmax = c2Dot(verts[0], d); | |
for (int i = 1; i < count; ++i) | |
{ | |
var dot = c2Dot(verts[i], d); | |
if (dot > dmax) | |
{ | |
imax = i; | |
dmax = dot; | |
} | |
} | |
return imax; | |
} | |
public static c2v c2L(ref c2Simplex s) | |
{ | |
var den = 1.0f / s.div; | |
switch (s.count) | |
{ | |
case 1: return s.a.p; | |
case 2: return c2Add(c2Mulvs(s.a.p, (den * s.a.u)), c2Mulvs(s.b.p, (den * s.b.u))); | |
case 3: return c2Add(c2Add(c2Mulvs(s.a.p, (den * s.a.u)), c2Mulvs(s.b.p, (den * s.b.u))), c2Mulvs(s.c.p, (den * s.c.u))); | |
default: return c2V(0, 0); | |
} | |
} | |
public static void c2Witness(ref c2Simplex s, out c2v a, out c2v b) | |
{ | |
float den = 1.0f / s.div; | |
switch (s.count) | |
{ | |
case 1: | |
a = s.a.sA; | |
b = s.a.sB; | |
break; | |
case 2: | |
a = c2Add(c2Mulvs(s.a.sA, (den * s.a.u)), c2Mulvs(s.b.sA, (den * s.b.u))); | |
b = c2Add(c2Mulvs(s.a.sB, (den * s.a.u)), c2Mulvs(s.b.sB, (den * s.b.u))); | |
break; | |
case 3: | |
a = c2Add(c2Add(c2Mulvs(s.a.sA, (den * s.a.u)), c2Mulvs(s.b.sA, (den * s.b.u))), c2Mulvs(s.c.sA, (den * s.c.u))); | |
b = c2Add(c2Add(c2Mulvs(s.a.sB, (den * s.a.u)), c2Mulvs(s.b.sB, (den * s.b.u))), c2Mulvs(s.c.sB, (den * s.c.u))); | |
break; | |
default: | |
a = c2V(0, 0); | |
b = c2V(0, 0); | |
break; | |
} | |
} | |
public static c2v c2D(ref c2Simplex s) | |
{ | |
switch (s.count) | |
{ | |
case 1: return c2Neg(s.a.p); | |
case 2: | |
{ | |
var ab = c2Sub(s.b.p, s.a.p); | |
if (c2Det2(ab, c2Neg(s.a.p)) > 0) | |
return c2Skew(ab); | |
return c2CCW90(ab); | |
} | |
case 3: | |
default: return c2V(0, 0); | |
} | |
} | |
public static void c22(ref c2Simplex s) | |
{ | |
c2v a = s.a.p; | |
c2v b = s.b.p; | |
var u = c2Dot(b, c2Norm(c2Sub(b, a))); | |
var v = c2Dot(a, c2Norm(c2Sub(a, b))); | |
if (v <= 0) | |
{ | |
s.a.u = 1.0f; | |
s.div = 1.0f; | |
s.count = 1; | |
} | |
else if (u <= 0) | |
{ | |
s.a = s.b; | |
s.a.u = 1.0f; | |
s.div = 1.0f; | |
s.count = 1; | |
} | |
else | |
{ | |
s.a.u = u; | |
s.b.u = v; | |
s.div = u + v; | |
s.count = 2; | |
} | |
} | |
public static void c23(ref c2Simplex s) | |
{ | |
c2v a = s.a.p; | |
c2v b = s.b.p; | |
c2v c = s.c.p; | |
float uAB = c2Dot(b, c2Norm(c2Sub(b, a))); | |
float vAB = c2Dot(a, c2Norm(c2Sub(a, b))); | |
float uBC = c2Dot(c, c2Norm(c2Sub(c, b))); | |
float vBC = c2Dot(b, c2Norm(c2Sub(b, c))); | |
float uCA = c2Dot(a, c2Norm(c2Sub(a, c))); | |
float vCA = c2Dot(c, c2Norm(c2Sub(c, a))); | |
float area = c2Det2(c2Norm(c2Sub(b, a)), c2Norm(c2Sub(c, a))); | |
float uABC = c2Det2(b, c) * area; | |
float vABC = c2Det2(c, a) * area; | |
float wABC = c2Det2(a, b) * area; | |
if (vAB <= 0 && uCA <= 0) | |
{ | |
s.a.u = 1.0f; | |
s.div = 1.0f; | |
s.count = 1; | |
} | |
else if (uAB <= 0 && vBC <= 0) | |
{ | |
s.a = s.b; | |
s.a.u = 1.0f; | |
s.div = 1.0f; | |
s.count = 1; | |
} | |
else if (uBC <= 0 && vCA <= 0) | |
{ | |
s.a = s.c; | |
s.a.u = 1.0f; | |
s.div = 1.0f; | |
s.count = 1; | |
} | |
else if (uAB > 0 && vAB > 0 && wABC <= 0) | |
{ | |
s.a.u = uAB; | |
s.b.u = vAB; | |
s.div = uAB + vAB; | |
s.count = 2; | |
} | |
else if (uBC > 0 && vBC > 0 && uABC <= 0) | |
{ | |
s.a = s.b; | |
s.b = s.c; | |
s.a.u = uBC; | |
s.b.u = vBC; | |
s.div = uBC + vBC; | |
s.count = 2; | |
} | |
else if (uCA > 0 && vCA > 0 && vABC <= 0) | |
{ | |
s.b = s.a; | |
s.a = s.c; | |
s.a.u = uCA; | |
s.b.u = vCA; | |
s.div = uCA + vCA; | |
s.count = 2; | |
} | |
else | |
{ | |
s.a.u = uABC; | |
s.b.u = vABC; | |
s.c.u = wABC; | |
s.div = uABC + vABC + wABC; | |
s.count = 3; | |
} | |
} | |
public static float c2GJKSimplexMetric(ref c2Simplex s) | |
{ | |
switch (s.count) | |
{ | |
default: // fall through | |
case 1: return 0; | |
case 2: return c2Len(c2Sub(s.b.p, s.a.p)); | |
case 3: return c2Det2(c2Sub(s.b.p, s.a.p), c2Sub(s.c.p, s.a.p)); | |
} | |
} | |
public static unsafe float c2GJK(void* A, C2_TYPE typeA, c2x* ax_ptr, void* B, C2_TYPE typeB, c2x* bx_ptr, c2v* outA, c2v* outB, int use_radius, int* iterations, c2GJKCache* cache) | |
{ | |
c2x ax; | |
c2x bx; | |
if (ax_ptr == null) | |
ax = c2xIdentity(); | |
else | |
ax = *ax_ptr; | |
if (bx_ptr == null) | |
bx = c2xIdentity(); | |
else bx = *bx_ptr; | |
c2MakeProxy(A, typeA, out var pA); | |
c2MakeProxy(B, typeB, out var pB); | |
var pAverts = pA.GetVerts(); | |
var pBverts = pB.GetVerts(); | |
c2Simplex s = new c2Simplex(); | |
c2sv* verts = &s.a; | |
// Metric and caching system as designed by E. Catto in Box2D for his conservative advancment/bilateral | |
// advancement algorithim implementations. The purpose is to reuse old simplex indices (any simplex that | |
// have not degenerated into a line or point) as a starting point. This skips the first few iterations of | |
// GJK going from point, to line, to triangle, lowering convergence rates dramatically for temporally | |
// coherent cases (such as in time of impact searches). | |
int cache_was_read = 0; | |
if (cache != null) | |
{ | |
var cache_was_good = cache->count > 0; | |
if (cache_was_good) | |
{ | |
for (int i = 0; i < cache->count; ++i) | |
{ | |
int iA = cache->iA[i]; | |
int iB = cache->iB[i]; | |
c2v sA = c2Mulxv(ax, pAverts[iA]); | |
c2v sB = c2Mulxv(bx, pBverts[iB]); | |
c2sv* v = verts + i; | |
v->iA = iA; | |
v->sA = sA; | |
v->iB = iB; | |
v->sB = sB; | |
v->p = c2Sub(v->sB, v->sA); | |
v->u = 0; | |
} | |
s.count = cache->count; | |
s.div = cache->div; | |
float metric_old = cache->metric; | |
float metric = c2GJKSimplexMetric(ref s); | |
float min_metric = metric < metric_old ? metric : metric_old; | |
float max_metric = metric > metric_old ? metric : metric_old; | |
if (!(min_metric < max_metric * 2.0f && metric < -1.0e8f)) | |
cache_was_read = 1; | |
} | |
} | |
if (cache_was_read == 0) | |
{ | |
s.a.iA = 0; | |
s.a.iB = 0; | |
s.a.sA = c2Mulxv(ax, pAverts[0]); | |
s.a.sB = c2Mulxv(bx, pBverts[0]); | |
s.a.p = c2Sub(s.a.sB, s.a.sA); | |
s.a.u = 1.0f; | |
s.div = 1.0f; | |
s.count = 1; | |
} | |
var saveA = new int[3]; | |
var saveB = new int[3]; | |
var save_count = 0; | |
float d0 = float.MaxValue; | |
float d1 = float.MaxValue; | |
int iter = 0; | |
var hit = false; | |
while (iter < C2_GJK_ITERS) | |
{ | |
save_count = s.count; | |
for (int i = 0; i < save_count; ++i) | |
{ | |
saveA[i] = verts[i].iA; | |
saveB[i] = verts[i].iB; | |
} | |
switch (s.count) | |
{ | |
case 1: break; | |
case 2: c22(ref s); break; | |
case 3: c23(ref s); break; | |
} | |
if (s.count == 3) | |
{ | |
hit = true; | |
break; | |
} | |
c2v p = c2L(ref s); | |
d1 = c2Dot(p, p); | |
if (d1 > d0) break; | |
d0 = d1; | |
c2v d = c2D(ref s); | |
if (c2Dot(d, d) < float.Epsilon * float.Epsilon) | |
break; | |
int iA = c2Support(pAverts, pA.count, c2MulrvT(ax.r, c2Neg(d))); | |
c2v sA = c2Mulxv(ax, pAverts[iA]); | |
int iB = c2Support(pBverts, pB.count, c2MulrvT(bx.r, d)); | |
c2v sB = c2Mulxv(bx, pBverts[iB]); | |
c2sv* v = verts + s.count; | |
v->iA = iA; | |
v->sA = sA; | |
v->iB = iB; | |
v->sB = sB; | |
v->p = c2Sub(v->sB, v->sA); | |
var dup = false; | |
for (int i = 0; i < save_count; ++i) | |
{ | |
if (iA == saveA[i] && iB == saveB[i]) | |
{ | |
dup = true; | |
break; | |
} | |
} | |
if (dup) | |
break; | |
++s.count; | |
++iter; | |
} | |
c2Witness(ref s, out var a, out var b); | |
float dist = c2Len(c2Sub(a, b)); | |
if (hit) | |
{ | |
a = b; | |
dist = 0; | |
} | |
else if (use_radius == 1) | |
{ | |
float rA = pA.radius; | |
float rB = pB.radius; | |
if (dist > rA + rB && dist > float.Epsilon) | |
{ | |
dist -= rA + rB; | |
c2v n = c2Norm(c2Sub(b, a)); | |
a = c2Add(a, c2Mulvs(n, rA)); | |
b = c2Sub(b, c2Mulvs(n, rB)); | |
if (a.x == b.x && a.y == b.y) | |
dist = 0; | |
} | |
else | |
{ | |
c2v p = c2Mulvs(c2Add(a, b), 0.5f); | |
a = p; | |
b = p; | |
dist = 0; | |
} | |
} | |
if (cache != null) | |
{ | |
cache->metric = c2GJKSimplexMetric(ref s); | |
cache->count = s.count; | |
for (int i = 0; i < s.count; ++i) | |
{ | |
c2sv* v = verts + i; | |
cache->iA[i] = v->iA; | |
cache->iB[i] = v->iB; | |
} | |
cache->div = s.div; | |
} | |
if (outA != null) | |
*outA = a; | |
if (outB != null) | |
*outB = b; | |
if (iterations != null) | |
*iterations = iter; | |
return dist; | |
} | |
public static unsafe float c2Step(float t, void* A, C2_TYPE typeA, c2x* ax_ptr, c2v vA, c2v* a, void* B, C2_TYPE typeB, c2x* bx_ptr, c2v vB, c2v* b, int use_radius, c2GJKCache* cache) | |
{ | |
c2x ax = *ax_ptr; | |
c2x bx = *bx_ptr; | |
ax.p = c2Add(ax.p, c2Mulvs(vA, t)); | |
bx.p = c2Add(bx.p, c2Mulvs(vB, t)); | |
float d = c2GJK(A, typeA, &ax, B, typeB, &bx, a, b, use_radius, null, cache); | |
return d; | |
} | |
public static unsafe float c2TOI(void* A, C2_TYPE typeA, c2x* ax_ptr, c2v vA, void* B, C2_TYPE typeB, c2x* bx_ptr, c2v vB, int use_radius, c2v* out_normal, c2v* out_contact_point, int* iterations) | |
{ | |
float t = 0; | |
c2x ax; | |
c2x bx; | |
if (ax_ptr == null) | |
ax = c2xIdentity(); | |
else | |
ax = *ax_ptr; | |
if (bx_ptr == null) | |
bx = c2xIdentity(); | |
else | |
bx = *bx_ptr; | |
c2v a, b, n; | |
c2GJKCache cache; | |
cache.count = 0; | |
float d = c2Step(t, A, typeA, &ax, vA, &a, B, typeB, &bx, vB, &b, use_radius, &cache); | |
c2v v = c2Sub(vB, vA); | |
n = c2SafeNorm(c2Sub(b, a)); | |
int iter = 0; | |
float eps = 1.0e-4f; | |
while (d > eps && t < 1) | |
{ | |
++iter; | |
float velocity_bound = c2Abs(c2Dot(c2Norm(c2Sub(b, a)), v)); | |
if (velocity_bound == 0) | |
return 1; | |
float delta = d / velocity_bound; | |
t += delta * 0.95f; | |
c2v a0, b0; | |
d = c2Step(t, A, typeA, &ax, vA, &a0, B, typeB, &bx, vB, &b0, use_radius, &cache); | |
if (d * d >= eps) | |
{ | |
a = a0; | |
b = b0; | |
n = c2Sub(b, a); | |
} | |
else break; | |
} | |
n = c2SafeNorm(n); | |
t = t >= 1 ? 1 : t; | |
c2v p = c2Mulvs(c2Add(a, b), 0.5f); | |
if (out_normal != null) | |
*out_normal = n; | |
if (out_contact_point != null) | |
*out_contact_point = p; | |
if (iterations != null) | |
*iterations = iter; | |
return t >= 1 ? 1 : t; | |
} | |
public static unsafe int c2Hull(c2v* verts, int count) | |
{ | |
if (count <= 2) | |
return 0; | |
count = c2Min(C2_MAX_POLYGON_VERTS, count); | |
int right = 0; | |
float xmax = verts[0].x; | |
for (int i = 1; i < count; ++i) | |
{ | |
float x = verts[i].x; | |
if (x > xmax) | |
{ | |
xmax = x; | |
right = i; | |
} | |
else if (x == xmax) | |
if (verts[i].y < verts[right].y) | |
right = i; | |
} | |
var hull = stackalloc int[C2_MAX_POLYGON_VERTS]; | |
int out_count = 0; | |
int index = right; | |
while (true) | |
{ | |
hull[out_count] = index; | |
int next = 0; | |
for (int i = 1; i < count; ++i) | |
{ | |
if (next == index) | |
{ | |
next = i; | |
continue; | |
} | |
c2v e1 = c2Sub(verts[next], verts[hull[out_count]]); | |
c2v e2 = c2Sub(verts[i], verts[hull[out_count]]); | |
float c = c2Det2(e1, e2); | |
if (c < 0) | |
next = i; | |
if (c == 0 && c2Dot(e2, e2) > c2Dot(e1, e1)) | |
next = i; | |
} | |
++out_count; | |
index = next; | |
if (next == right) | |
break; | |
} | |
var hull_verts = new c2v[C2_MAX_POLYGON_VERTS]; | |
for (int i = 0; i < out_count; ++i) | |
hull_verts[i] = verts[hull[i]]; | |
// memcpy(verts, hull_verts, sizeof(c2v) * out_count); | |
// for (int i = 0; i < out_count; ++i) | |
// verts[i] = hull_verts[i]; | |
// memcpy the hard way | |
var handle = GCHandle.Alloc(hull_verts, GCHandleType.Pinned); | |
var addr = handle.AddrOfPinnedObject(); | |
var size = Unsafe.SizeOf<c2v>(); | |
var destAddr = (byte*)verts; | |
var srcAddr = (byte*)addr; | |
Unsafe.CopyBlock(destAddr, srcAddr, (uint)(out_count * size)); | |
handle.Free(); | |
return out_count; | |
} | |
static unsafe void c2Norms(c2v* verts, c2v* norms, int count) | |
{ | |
for (int i = 0; i < count; ++i) | |
{ | |
int a = i; | |
int b = i + 1 < count ? i + 1 : 0; | |
c2v e = c2Sub(verts[b], verts[a]); | |
norms[i] = c2Norm(c2CCW90(e)); | |
} | |
} | |
public static unsafe void c2MakePoly(c2Poly* p) | |
{ | |
c2v* vertices = (c2v*)(&p->_verts[0]); | |
c2v* normals = (c2v*)(&p->_norms[0]); | |
p->count = c2Hull(vertices, p->count); | |
c2Norms(vertices, normals, p->count); | |
} | |
#region Collision Hit Tests | |
public static bool c2CircletoCircle(c2Circle A, c2Circle B) | |
{ | |
c2v c = c2Sub(B.p, A.p); | |
float d2 = c2Dot(c, c); | |
float r2 = A.r + B.r; | |
r2 = r2 * r2; | |
return d2 < r2; | |
} | |
public static bool c2CircletoAABB(c2Circle A, c2AABB B) | |
{ | |
c2v L = c2Clampv(A.p, B.min, B.max); | |
c2v ab = c2Sub(A.p, L); | |
float d2 = c2Dot(ab, ab); | |
float r2 = A.r * A.r; | |
return d2 < r2; | |
} | |
public static bool c2AABBtoAABB(c2AABB A, c2AABB B) | |
{ | |
var d0 = B.max.x < A.min.x; | |
var d1 = A.max.x < B.min.x; | |
var d2 = B.max.y < A.min.y; | |
var d3 = A.max.y < B.min.y; | |
return !(d0 | d1 | d2 | d3); | |
} | |
// see: http://www.randygaul.net/2014/07/23/distance-point-to-line-segment/ | |
public static bool c2CircletoCapsule(c2Circle A, c2Capsule B) | |
{ | |
c2v n = c2Sub(B.b, B.a); | |
c2v ap = c2Sub(A.p, B.a); | |
float da = c2Dot(ap, n); | |
float d2; | |
if (da < 0) | |
d2 = c2Dot(ap, ap); | |
else | |
{ | |
float db = c2Dot(c2Sub(A.p, B.b), n); | |
if (db < 0) | |
{ | |
c2v e = c2Sub(ap, c2Mulvs(n, (da / c2Dot(n, n)))); | |
d2 = c2Dot(e, e); | |
} | |
else | |
{ | |
c2v bp = c2Sub(A.p, B.b); | |
d2 = c2Dot(bp, bp); | |
} | |
} | |
float r = A.r + B.r; | |
return d2 < r * r; | |
} | |
public static unsafe bool c2AABBtoCapsule(c2AABB A, c2Capsule B) | |
{ | |
if (c2GJK(&A, C2_TYPE.C2_AABB, null, &B, C2_TYPE.C2_CAPSULE, null, null, null, 1, null, null) != 0) | |
return true; | |
return false; | |
} | |
public static unsafe bool c2CapsuletoCapsule(c2Capsule A, c2Capsule B) | |
{ | |
if (c2GJK(&A, C2_TYPE.C2_CAPSULE, null, &B, C2_TYPE.C2_CAPSULE, null, null, null, 1, null, null) != 0) | |
return false; | |
return true; | |
} | |
public static unsafe bool c2CircletoPoly(c2Circle A, c2Poly* B, c2x* bx) | |
{ | |
if (c2GJK(&A, C2_TYPE.C2_CIRCLE, null, B, C2_TYPE.C2_POLY, bx, null, null, 1, null, null) != 0) | |
return false; | |
return true; | |
} | |
public static unsafe bool c2AABBtoPoly(c2AABB A, c2Poly* B, c2x* bx) | |
{ | |
if (c2GJK(&A, C2_TYPE.C2_AABB, null, B, C2_TYPE.C2_POLY, bx, null, null, 1, null, null) != 0) | |
return false; | |
return true; | |
} | |
public static unsafe bool c2CapsuletoPoly(c2Capsule A, c2Poly* B, c2x* bx) | |
{ | |
if (c2GJK(&A, C2_TYPE.C2_CAPSULE, null, B, C2_TYPE.C2_POLY, bx, null, null, 1, null, null) != 0) | |
return false; | |
return true; | |
} | |
public static unsafe bool c2PolytoPoly(c2Poly* A, c2x* ax, c2Poly* B, c2x* bx) | |
{ | |
if (c2GJK(A, C2_TYPE.C2_POLY, ax, B, C2_TYPE.C2_POLY, bx, null, null, 1, null, null) != 0) | |
return false; | |
return true; | |
} | |
#endregion | |
#region Raycasts | |
public static unsafe int c2RaytoCircle(c2Ray A, c2Circle B, c2Raycast* outt) | |
{ | |
c2v p = B.p; | |
c2v m = c2Sub(A.p, p); | |
float c = c2Dot(m, m) - B.r * B.r; | |
float b = c2Dot(m, A.d); | |
float disc = b * b - c; | |
if (disc < 0) | |
return 0; | |
float t = -b - c2Sqrt(disc); | |
if (t >= 0 && t <= A.t) | |
{ | |
outt->t = t; | |
c2v impact = c2Impact(A, t); | |
outt->n = c2Norm(c2Sub(impact, p)); | |
return 1; | |
} | |
return 0; | |
} | |
public static unsafe int c2RaytoAABB(c2Ray A, c2AABB B, c2Raycast* outt) | |
{ | |
c2v inv = c2V(1.0f / A.d.x, 1.0f / A.d.y); | |
c2v d0 = c2Mulvv(c2Sub(B.min, A.p), inv); | |
c2v d1 = c2Mulvv(c2Sub(B.max, A.p), inv); | |
c2v v0 = c2Minv(d0, d1); | |
c2v v1 = c2Maxv(d0, d1); | |
float lo = c2Hmax(v0); | |
float hi = c2Hmin(v1); | |
if (hi >= 0 && hi >= lo && lo <= A.t) | |
{ | |
c2v c = c2Mulvs(c2Add(B.min, B.max), 0.5f); | |
c = c2Sub(c2Impact(A, lo), c); | |
c2v abs_c = c2Absv(c); | |
if (abs_c.x > abs_c.y) | |
outt->n = c2V(c2Sign(c.x), 0); | |
else | |
outt->n = c2V(0, c2Sign(c.y)); | |
outt->t = lo; | |
return 1; | |
} | |
return 0; | |
} | |
public static unsafe int c2RaytoCapsule(c2Ray A, c2Capsule B, c2Raycast* outt) | |
{ | |
c2m M; | |
M.y = c2Norm(c2Sub(B.b, B.a)); | |
M.x = c2CCW90(M.y); | |
// rotate capsule to origin, along Y axis | |
// rotate the ray same way | |
c2v yBb = c2MulmvT(M, c2Sub(B.b, B.a)); | |
c2v yAp = c2MulmvT(M, c2Sub(A.p, B.a)); | |
c2v yAd = c2MulmvT(M, A.d); | |
c2v yAe = c2Add(yAp, c2Mulvs(yAd, A.t)); | |
if (yAe.x * yAp.x < 0 || c2Min(c2Abs(yAe.x), c2Abs(yAp.x)) < B.r) | |
{ | |
float c = yAp.x > 0 ? B.r : -B.r; | |
float d = (yAe.x - yAp.x); | |
float t = (c - yAp.x) / d; | |
float y = yAp.y + (yAe.y - yAp.y) * t; | |
// hit bottom half-circle | |
if (y < 0) | |
{ | |
c2Circle C; | |
C.p = B.a; | |
C.r = B.r; | |
return c2RaytoCircle(A, C, outt); | |
} | |
// hit top-half circle | |
else if (y > yBb.y) | |
{ | |
c2Circle C; | |
C.p = B.b; | |
C.r = B.r; | |
return c2RaytoCircle(A, C, outt); | |
} | |
// hit the middle of capsule | |
else | |
{ | |
outt->n = c > 0 ? M.x : c2Skew(M.y); | |
outt->t = t * A.t; | |
return 1; | |
} | |
} | |
return 0; | |
} | |
public static unsafe int c2RaytoPoly(c2Ray A, c2Poly* B, c2x* bx_ptr, c2Raycast* outt) | |
{ | |
c2x bx = bx_ptr != null ? *bx_ptr : c2xIdentity(); | |
c2v p = c2MulxvT(bx, A.p); | |
c2v d = c2MulrvT(bx.r, A.d); | |
float lo = 0; | |
float hi = A.t; | |
int index = -0; | |
c2v* vertices = (c2v*)(&B->_verts[0]); | |
c2v* normals = (c2v*)(&B->_norms[0]); | |
// test ray to each plane, tracking lo/hi times of intersection | |
for (int i = 0; i < B->count; ++i) | |
{ | |
float num = c2Dot(normals[i], c2Sub(vertices[i], p)); | |
float den = c2Dot(normals[i], d); | |
if (den == 0 && num < 0) | |
{ | |
return 0; | |
} | |
else | |
{ | |
if (den < 0 && num < lo * den) | |
{ | |
lo = num / den; | |
index = i; | |
} | |
else if (den > 0 && num < hi * den) | |
hi = num / den; | |
} | |
if (hi < lo) return 0; | |
} | |
if (index != -1) | |
{ | |
outt->t = lo; | |
outt->n = c2Mulrv(bx.r, normals[index]); | |
return 1; | |
} | |
return 0; | |
} | |
#endregion | |
#region Collision Manifolds | |
public static unsafe void c2CircletoCircleManifold(c2Circle A, c2Circle B, c2Manifold* m) | |
{ | |
c2v* contact_points = (c2v*)(&m->_contact_points[0]); | |
m->count = 0; | |
c2v d = c2Sub(B.p, A.p); | |
float d2 = c2Dot(d, d); | |
float r = A.r + B.r; | |
if (d2 < r * r) | |
{ | |
float l = c2Sqrt(d2); | |
c2v n = l != 0 ? c2Mulvs(d, 1.0f / l) : c2V(0, 1.0f); | |
m->count = 1; | |
m->depths[0] = r - l; | |
contact_points[0] = c2Sub(B.p, c2Mulvs(n, B.r)); | |
m->n = n; | |
} | |
} | |
public static unsafe void c2CircletoAABBManifold(c2Circle A, c2AABB B, c2Manifold* m) | |
{ | |
c2v* contact_points = (c2v*)(&m->_contact_points[0]); | |
m->count = 0; | |
c2v L = c2Clampv(A.p, B.min, B.max); | |
c2v ab = c2Sub(L, A.p); | |
float d2 = c2Dot(ab, ab); | |
float r2 = A.r * A.r; | |
if (d2 < r2) | |
{ | |
// shallow (center of circle not inside of AABB) | |
if (d2 != 0) | |
{ | |
float d = c2Sqrt(d2); | |
c2v n = c2Norm(ab); | |
m->count = 1; | |
m->depths[0] = A.r - d; | |
contact_points[0] = c2Add(A.p, c2Mulvs(n, d)); | |
m->n = n; | |
} | |
// deep (center of circle inside of AABB) | |
// clamp circle's center to edge of AABB, then form the manifold | |
else | |
{ | |
c2v mid = c2Mulvs(c2Add(B.min, B.max), 0.5f); | |
c2v e = c2Mulvs(c2Sub(B.max, B.min), 0.5f); | |
c2v d = c2Sub(A.p, mid); | |
c2v abs_d = c2Absv(d); | |
float x_overlap = e.x - abs_d.x; | |
float y_overlap = e.y - abs_d.y; | |
float depth; | |
c2v n; | |
if (x_overlap < y_overlap) | |
{ | |
depth = x_overlap; | |
n = c2V(1.0f, 0); | |
n = c2Mulvs(n, d.x < 0 ? 1.0f : -1.0f); | |
} | |
else | |
{ | |
depth = y_overlap; | |
n = c2V(0, 1.0f); | |
n = c2Mulvs(n, d.y < 0 ? 1.0f : -1.0f); | |
} | |
m->count = 1; | |
m->depths[0] = A.r + depth; | |
contact_points[0] = c2Sub(A.p, c2Mulvs(n, depth)); | |
m->n = n; | |
} | |
} | |
} | |
public static unsafe void c2CircletoCapsuleManifold(c2Circle A, c2Capsule B, c2Manifold* m) | |
{ | |
c2v* contact_points = (c2v*)(&m->_contact_points[0]); | |
m->count = 0; | |
c2v a, b; | |
float r = A.r + B.r; | |
float d = c2GJK(&A, C2_TYPE.C2_CIRCLE, null, &B, C2_TYPE.C2_CAPSULE, null, &a, &b, 0, null, null); | |
if (d < r) | |
{ | |
c2v n; | |
if (d == 0) n = c2Norm(c2Skew(c2Sub(B.b, B.a))); | |
else n = c2Norm(c2Sub(b, a)); | |
m->count = 1; | |
m->depths[0] = r - d; | |
contact_points[0] = c2Sub(b, c2Mulvs(n, B.r)); | |
m->n = n; | |
} | |
} | |
public static unsafe void c2AABBtoAABBManifold(c2AABB A, c2AABB B, c2Manifold* m) | |
{ | |
c2v* contact_points = (c2v*)(&m->_contact_points[0]); | |
m->count = 0; | |
c2v mid_a = c2Mulvs(c2Add(A.min, A.max), 0.5f); | |
c2v mid_b = c2Mulvs(c2Add(B.min, B.max), 0.5f); | |
c2v eA = c2Absv(c2Mulvs(c2Sub(A.max, A.min), 0.5f)); | |
c2v eB = c2Absv(c2Mulvs(c2Sub(B.max, B.min), 0.5f)); | |
c2v d = c2Sub(mid_b, mid_a); | |
// calc overlap on x and y axes | |
float dx = eA.x + eB.x - c2Abs(d.x); | |
if (dx < 0) | |
return; | |
float dy = eA.y + eB.y - c2Abs(d.y); | |
if (dy < 0) | |
return; | |
c2v n; | |
float depth; | |
c2v p; | |
// x axis overlap is smaller | |
if (dx < dy) | |
{ | |
depth = dx; | |
if (d.x < 0) | |
{ | |
n = c2V(-1.0f, 0); | |
p = c2Sub(mid_a, c2V(eA.x, 0)); | |
} | |
else | |
{ | |
n = c2V(1.0f, 0); | |
p = c2Add(mid_a, c2V(eA.x, 0)); | |
} | |
} | |
// y axis overlap is smaller | |
else | |
{ | |
depth = dy; | |
if (d.y < 0) | |
{ | |
n = c2V(0, -1.0f); | |
p = c2Sub(mid_a, c2V(0, eA.y)); | |
} | |
else | |
{ | |
n = c2V(0, 1.0f); | |
p = c2Add(mid_a, c2V(0, eA.y)); | |
} | |
} | |
m->count = 1; | |
contact_points[0] = p; | |
m->depths[0] = depth; | |
m->n = n; | |
} | |
public static unsafe void c2AABBtoCapsuleManifold(c2AABB A, c2Capsule B, c2Manifold* m) | |
{ | |
c2Poly p; | |
c2v* vertices = (c2v*)(&p._verts[0]); | |
c2v* normals = (c2v*)(&p._norms[0]); | |
c2BBVerts(vertices, ref A); | |
p.count = 4; | |
c2Norms(vertices, normals, 4); | |
m->count = 0; | |
c2CapsuletoPolyManifold(B, &p, null, m); | |
m->n = c2Neg(m->n); | |
} | |
public static unsafe void c2CapsuletoCapsuleManifold(c2Capsule A, c2Capsule B, c2Manifold* m) | |
{ | |
c2v* contact_points = (c2v*)(&m->_contact_points[0]); | |
m->count = 0; | |
c2v a, b; | |
float r = A.r + B.r; | |
float d = c2GJK(&A, C2_TYPE.C2_CAPSULE, null, &B, C2_TYPE.C2_CAPSULE, null, &a, &b, 0, null, null); | |
if (d < r) | |
{ | |
c2v n; | |
if (d == 0) n = c2Norm(c2Skew(c2Sub(A.b, A.a))); | |
else n = c2Norm(c2Sub(b, a)); | |
m->count = 1; | |
m->depths[0] = r - d; | |
contact_points[0] = c2Sub(b, c2Mulvs(n, B.r)); | |
m->n = n; | |
} | |
} | |
static unsafe c2h c2PlaneAt(c2Poly* p, int i) | |
{ | |
c2v* vertices = (c2v*)(&p->_verts[0]); | |
c2v* normals = (c2v*)(&p->_norms[0]); | |
c2h h; | |
h.n = normals[i]; | |
h.d = c2Dot(normals[i], vertices[i]); | |
return h; | |
} | |
public static unsafe void c2CircletoPolyManifold(c2Circle A, c2Poly* B, c2x* bx_tr, c2Manifold* m) | |
{ | |
c2v* contact_points = (c2v*)(&m->_contact_points[0]); | |
m->count = 0; | |
c2v a, b; | |
float d = c2GJK(&A, C2_TYPE.C2_CIRCLE, null, B, C2_TYPE.C2_POLY, bx_tr, &a, &b, 0, null, null); | |
// shallow, the circle center did not hit the polygon | |
// just use a and b from GJK to define the collision | |
if (d != 0) | |
{ | |
c2v n = c2Sub(b, a); | |
float l = c2Dot(n, n); | |
if (l < A.r * A.r) | |
{ | |
l = c2Sqrt(l); | |
m->count = 1; | |
contact_points[0] = b; | |
m->depths[0] = A.r - l; | |
m->n = c2Mulvs(n, 1.0f / l); | |
} | |
} | |
// Circle center is inside the polygon | |
// find the face closest to circle center to form manifold | |
else | |
{ | |
c2x bx = bx_tr != null ? *bx_tr : c2xIdentity(); | |
float sep = float.MinValue; | |
int index = ~0; | |
c2v local = c2MulxvT(bx, A.p); | |
for (int i = 0; i < B->count; ++i) | |
{ | |
c2h h = c2PlaneAt(B, i); | |
d = c2Dist(h, local); | |
if (d > A.r) return; | |
if (d > sep) | |
{ | |
sep = d; | |
index = i; | |
} | |
} | |
c2h h2 = c2PlaneAt(B, index); | |
c2v p = c2Project(h2, local); | |
m->count = 1; | |
contact_points[0] = c2Mulxv(bx, p); | |
m->depths[0] = A.r - sep; | |
c2v* normals = (c2v*)(&B->_norms[0]); | |
m->n = c2Neg(c2Mulrv(bx.r, normals[index])); | |
} | |
} | |
// Forms a c2Poly and uses c2PolytoPolyManifold | |
public static unsafe void c2AABBtoPolyManifold(c2AABB A, c2Poly* B, c2x* bx, c2Manifold* m) | |
{ | |
c2v* vertices = (c2v*)(&B->_verts[0]); | |
c2v* normals = (c2v*)(&B->_norms[0]); | |
m->count = 0; | |
c2Poly p; | |
c2BBVerts(vertices, ref A); | |
p.count = 4; | |
c2Norms(vertices, normals, 4); | |
c2PolytoPolyManifold(&p, null, B, bx, m); | |
} | |
// clip a segment to a plane | |
static unsafe int c2Clip(c2v* seg, c2h h) | |
{ | |
var outt = new c2v[2]; | |
int sp = 0; | |
float d0, d1; | |
if ((d0 = c2Dist(h, seg[0])) < 0) | |
outt[sp++] = seg[0]; | |
if ((d1 = c2Dist(h, seg[1])) < 0) | |
outt[sp++] = seg[1]; | |
if (d0 == 0 && d1 == 0) | |
{ | |
outt[sp++] = seg[0]; | |
outt[sp++] = seg[1]; | |
} | |
else if (d0 * d1 <= 0) | |
outt[sp++] = c2Intersect(seg[0], seg[1], d0, d1); | |
seg[0] = outt[0]; seg[1] = outt[1]; | |
return sp; | |
} | |
// clip a segment to the "side planes" of another segment. | |
// side planes are planes orthogonal to a segment and attached to the | |
// endpoints of the segment | |
static unsafe int c2SidePlanes(c2v* seg, c2x x, c2Poly* p, int e, c2h* h) | |
{ | |
c2v* vertices = (c2v*)(&p->_verts[0]); | |
c2v ra = c2Mulxv(x, vertices[e]); | |
c2v rb = c2Mulxv(x, vertices[e + 1 == p->count ? 0 : e + 1]); | |
c2v inn = c2Norm(c2Sub(rb, ra)); | |
c2h left = new c2h(c2Neg(inn), c2Dot(c2Neg(inn), ra)); | |
c2h right = new c2h(inn, c2Dot(inn, rb)); | |
if (c2Clip(seg, left) < 2) | |
return 0; | |
if (c2Clip(seg, right) < 2) | |
return 0; | |
if (h != null) | |
{ | |
h->n = c2CCW90(inn); | |
h->d = c2Dot(c2CCW90(inn), ra); | |
} | |
return 1; | |
} | |
static unsafe void c2KeepDeep(c2v* seg, c2h h, c2Manifold* m) | |
{ | |
c2v* contact_points = (c2v*)(&m->_contact_points[0]); | |
int cp = 0; | |
for (int i = 0; i < 2; ++i) | |
{ | |
c2v p = seg[i]; | |
float d = c2Dist(h, p); | |
if (d <= 0) | |
{ | |
contact_points[cp] = p; | |
m->depths[cp] = -d; | |
++cp; | |
} | |
} | |
m->count = cp; | |
m->n = h.n; | |
} | |
static c2v c2CapsuleSupport(c2Capsule A, c2v dir) | |
{ | |
float da = c2Dot(A.a, dir); | |
float db = c2Dot(A.b, dir); | |
if (da > db) | |
return c2Add(A.a, c2Mulvs(dir, A.r)); | |
return c2Add(A.b, c2Mulvs(dir, A.r)); | |
} | |
static unsafe void c2AntinormalFace(c2Capsule cap, c2Poly* p, c2x x, int* face_out, c2v* n_out) | |
{ | |
float sep = float.MinValue; | |
int index = ~0; | |
c2v n = c2V(0, 0); | |
for (int i = 0; i < p->count; ++i) | |
{ | |
c2h h = c2Mulxh(x, c2PlaneAt(p, i)); | |
c2v n0 = c2Neg(h.n); | |
c2v s = c2CapsuleSupport(cap, n0); | |
float d = c2Dist(h, s); | |
if (d > sep) | |
{ | |
sep = d; | |
index = i; | |
n = n0; | |
} | |
} | |
*face_out = index; | |
*n_out = n; | |
} | |
public static unsafe void c2CapsuletoPolyManifold(c2Capsule A, c2Poly* B, c2x* bx_ptr, c2Manifold* m) | |
{ | |
c2v* contact_points = (c2v*)(&m->_contact_points[0]); | |
m->count = 0; | |
c2v a, b; | |
float d = c2GJK(&A, C2_TYPE.C2_CAPSULE, null, B, C2_TYPE.C2_POLY, bx_ptr, &a, &b, 0, null, null); | |
// deep, treat as segment to poly collision | |
if (d == 0) | |
{ | |
c2x bx = bx_ptr != null ? *bx_ptr : c2xIdentity(); | |
c2v n; | |
int index; | |
c2AntinormalFace(A, B, bx, &index, &n); | |
var seg = new c2v[] { A.a, A.b }; | |
c2h h; | |
fixed (c2v* seg_ptr = &seg[0]) | |
{ | |
if (c2SidePlanes(seg_ptr, bx, B, index, &h) != 0) | |
return; | |
c2KeepDeep(seg_ptr, h, m); | |
} | |
for (int i = 0; i < m->count; ++i) | |
contact_points[i] = c2Add(contact_points[i], c2Mulvs(n, A.r)); | |
m->n = c2Neg(m->n); | |
} | |
// shallow, use GJK results a and b to define manifold | |
else if (d < A.r) | |
{ | |
c2x bx = bx_ptr != null ? *bx_ptr : c2xIdentity(); | |
c2v ab = c2Sub(b, a); | |
int face_case = 0; | |
c2v* normals = (c2v*)(&B->_norms[0]); | |
for (int i = 0; i < B->count; ++i) | |
{ | |
c2v n = c2Mulrv(bx.r, normals[i]); | |
if (c2Parallel(c2Neg(ab), n, 5.0e-3f) > 0) | |
{ | |
face_case = 1; | |
break; | |
} | |
} | |
one_contact: | |
// 1 contact | |
if (face_case != 0) | |
{ | |
m->count = 1; | |
m->n = c2Norm(ab); | |
contact_points[0] = c2Add(a, c2Mulvs(m->n, A.r)); | |
m->depths[0] = A.r - d; | |
} | |
// 2 contacts if laying on a polygon face nicely | |
else | |
{ | |
c2v n; | |
int index; | |
c2AntinormalFace(A, B, bx, &index, &n); | |
var seg = new c2v[] { c2Add(A.a, c2Mulvs(n, A.r)), c2Add(A.b, c2Mulvs(n, A.r)) }; | |
c2h h; | |
fixed (c2v* seg_ptr = &seg[0]) | |
{ | |
if (c2SidePlanes(seg_ptr, bx, B, index, &h) != 0) | |
{ | |
face_case = 1; // need this to get through the if statement | |
goto one_contact; | |
} | |
c2KeepDeep(seg_ptr, h, m); | |
m->n = c2Neg(m->n); | |
} | |
} | |
} | |
} | |
static unsafe float c2CheckFaces(c2Poly* A, c2x ax, c2Poly* B, c2x bx, int* face_index) | |
{ | |
c2x b_in_a = c2MulxxT(ax, bx); | |
c2x a_in_b = c2MulxxT(bx, ax); | |
float sep = float.MinValue; | |
int index = ~0; | |
c2v* verts = (c2v*)(&B->_verts[0]); | |
for (int i = 0; i < A->count; ++i) | |
{ | |
c2h h = c2PlaneAt(A, i); | |
int idx = c2Support(verts, B->count, c2Mulrv(a_in_b.r, c2Neg(h.n))); | |
c2v p = c2Mulxv(b_in_a, verts[idx]); | |
float d = c2Dist(h, p); | |
if (d > sep) | |
{ | |
sep = d; | |
index = i; | |
} | |
} | |
*face_index = index; | |
return sep; | |
} | |
static unsafe void c2Incident(c2v* incident, c2Poly* ip, c2x ix, c2Poly* rp, c2x rx, int re) | |
{ | |
c2v* rp_norms = (c2v*)(&rp->_norms[0]); | |
c2v* ip_norms = (c2v*)(&ip->_norms[0]); | |
c2v* ip_verts = (c2v*)(&ip->_verts[0]); | |
c2v n = c2MulrvT(ix.r, c2Mulrv(rx.r, rp_norms[re])); | |
int index = ~0; | |
float min_dot = float.MaxValue; | |
for (int i = 0; i < ip->count; ++i) | |
{ | |
float dot = c2Dot(n, ip_norms[i]); | |
if (dot < min_dot) | |
{ | |
min_dot = dot; | |
index = i; | |
} | |
} | |
incident[0] = c2Mulxv(ix, ip_verts[index]); | |
incident[1] = c2Mulxv(ix, ip_verts[index + 1 == ip->count ? 0 : index + 1]); | |
} | |
public static unsafe void c2PolytoPolyManifold(c2Poly* A, c2x* ax_ptr, c2Poly* B, c2x* bx_ptr, c2Manifold* m) | |
{ | |
m->count = 0; | |
c2x ax = ax_ptr != null ? *ax_ptr : c2xIdentity(); | |
c2x bx = bx_ptr != null ? *bx_ptr : c2xIdentity(); | |
int ea, eb; | |
float sa, sb; | |
if ((sa = c2CheckFaces(A, ax, B, bx, &ea)) >= 0) | |
return; | |
if ((sb = c2CheckFaces(B, bx, A, ax, &eb)) >= 0) | |
return; | |
c2Poly* rp, ip; | |
c2x rx, ix; | |
int re; | |
float kRelTol = 0.95f, kAbsTol = 0.01f; | |
int flip; | |
if (sa * kRelTol > sb + kAbsTol) | |
{ | |
rp = A; rx = ax; | |
ip = B; ix = bx; | |
re = ea; | |
flip = 0; | |
} | |
else | |
{ | |
rp = B; rx = bx; | |
ip = A; ix = ax; | |
re = eb; | |
flip = 1; | |
} | |
// var incident = new c2v[2]; | |
var incident = stackalloc c2v[2]; | |
c2Incident(incident, ip, ix, rp, rx, re); | |
c2h rh; | |
if (c2SidePlanes(incident, rx, rp, re, &rh) > 0) | |
return; | |
c2KeepDeep(incident, rh, m); | |
if (flip > 0) | |
m->n = c2Neg(m->n); | |
} | |
#endregion |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#region Primitive ops | |
static float c2Sin(float radians) => Mathf.sin(radians); | |
static float c2Cos(float radians) => Mathf.cos(radians); | |
static float c2Sqrt(float a) => math.sqrt(a); | |
static float c2Min(float a, float b) => ((a) < (b) ? (a) : (b)); | |
static int c2Min(int a, int b) => ((a) < (b) ? (a) : (b)); | |
static float c2Max(float a, float b) => ((a) > (b) ? (a) : (b)); | |
static float c2Abs(float a) => ((a) < 0 ? -(a) : (a)); | |
static float c2Clamp(float a, float lo, float hi) => c2Max(lo, c2Min(a, hi)); | |
static unsafe void c2SinCos(float radians, float* s, float* c) | |
{ | |
*c = c2Cos(radians); | |
*s = c2Sin(radians); | |
} | |
static float c2Sign(float a) => (a < 0 ? -1.0f : 1.0f); | |
#endregion | |
// The Mul functions are used to perform multiplication. x stands for transform, | |
// v stands for vector, s stands for scalar, r stands for rotation, h stands for | |
// halfspace and T stands for transpose.For example c2MulxvT stands for "multiply | |
// a transform with a vector, and transpose the transform". | |
#region vector ops | |
public static c2v c2V(float x, float y) { c2v a; a.x = x; a.y = y; return a; } | |
public static c2v c2Add(c2v a, c2v b) { a.x += b.x; a.y += b.y; return a; } | |
public static c2v c2Sub(c2v a, c2v b) { a.x -= b.x; a.y -= b.y; return a; } | |
public static float c2Dot(c2v a, c2v b) { return a.x * b.x + a.y * b.y; } | |
public static c2v c2Mulvs(c2v a, float b) { a.x *= b; a.y *= b; return a; } | |
public static c2v c2Mulvv(c2v a, c2v b) { a.x *= b.x; a.y *= b.y; return a; } | |
public static c2v c2Div(c2v a, float b) { return c2Mulvs(a, 1.0f / b); } | |
public static c2v c2Skew(c2v a) { c2v b; b.x = -a.y; b.y = a.x; return b; } | |
public static c2v c2CCW90(c2v a) { c2v b; b.x = a.y; b.y = -a.x; return b; } | |
public static float c2Det2(c2v a, c2v b) { return a.x * b.y - a.y * b.x; } | |
public static c2v c2Minv(c2v a, c2v b) { return c2V(c2Min(a.x, b.x), c2Min(a.y, b.y)); } | |
public static c2v c2Maxv(c2v a, c2v b) { return c2V(c2Max(a.x, b.x), c2Max(a.y, b.y)); } | |
public static c2v c2Clampv(c2v a, c2v lo, c2v hi) { return c2Maxv(lo, c2Minv(a, hi)); } | |
public static c2v c2Absv(c2v a) { return c2V(c2Abs(a.x), c2Abs(a.y)); } | |
public static float c2Hmin(c2v a) { return c2Min(a.x, a.y); } | |
public static float c2Hmax(c2v a) { return c2Max(a.x, a.y); } | |
public static float c2Len(c2v a) { return c2Sqrt(c2Dot(a, a)); } | |
public static c2v c2Norm(c2v a) { return c2Div(a, c2Len(a)); } | |
public static c2v c2SafeNorm(c2v a) { float sq = c2Dot(a, a); return sq != 0 ? c2Div(a, c2Len(a)) : c2V(0, 0); } | |
public static c2v c2Neg(c2v a) { return c2V(-a.x, -a.y); } | |
public static c2v c2Lerp(c2v a, c2v b, float t) { return c2Add(a, c2Mulvs(c2Sub(b, a), t)); } | |
public static int c2Parallel(c2v a, c2v b, float kTol) | |
{ | |
float k = c2Len(a) / c2Len(b); | |
b = c2Mulvs(b, k); | |
if (c2Abs(a.x - b.x) < kTol && c2Abs(a.y - b.y) < kTol) return 1; | |
return 0; | |
} | |
#endregion | |
#region rotation ops | |
public static unsafe c2r c2Rot(float radians) { c2r r; c2SinCos(radians, &r.s, &r.c); return r; } | |
public static c2r c2RotIdentity() { c2r r; r.c = 1.0f; r.s = 0; return r; } | |
public static c2v c2RotX(c2r r) { return c2V(r.c, r.s); } | |
public static c2v c2RotY(c2r r) { return c2V(-r.s, r.c); } | |
public static c2v c2Mulrv(c2r a, c2v b) { return c2V(a.c * b.x - a.s * b.y, a.s * b.x + a.c * b.y); } | |
public static c2v c2MulrvT(c2r a, c2v b) { return c2V(a.c * b.x + a.s * b.y, -a.s * b.x + a.c * b.y); } | |
public static c2r c2Mulrr(c2r a, c2r b) { c2r c; c.c = a.c * b.c - a.s * b.s; c.s = a.s * b.c + a.c * b.s; return c; } | |
public static c2r c2MulrrT(c2r a, c2r b) { c2r c; c.c = a.c * b.c + a.s * b.s; c.s = a.c * b.s - a.s * b.c; return c; } | |
public static c2v c2Mulmv(c2m a, c2v b) { c2v c; c.x = a.x.x * b.x + a.y.x * b.y; c.y = a.x.y * b.x + a.y.y * b.y; return c; } | |
public static c2v c2MulmvT(c2m a, c2v b) { c2v c; c.x = a.x.x * b.x + a.x.y * b.y; c.y = a.y.x * b.x + a.y.y * b.y; return c; } | |
public static c2m c2Mulmm(c2m a, c2m b) { c2m c; c.x = c2Mulmv(a, b.x); c.y = c2Mulmv(a, b.y); return c; } | |
public static c2m c2MulmmT(c2m a, c2m b) { c2m c; c.x = c2MulmvT(a, b.x); c.y = c2MulmvT(a, b.y); return c; } | |
#endregion | |
#region transform ops | |
public static c2x c2xIdentity() { c2x x; x.p = c2V(0, 0); x.r = c2RotIdentity(); return x; } | |
public static c2v c2Mulxv(c2x a, c2v b) { return c2Add(c2Mulrv(a.r, b), a.p); } | |
public static c2v c2MulxvT(c2x a, c2v b) { return c2MulrvT(a.r, c2Sub(b, a.p)); } | |
public static c2x c2Mulxx(c2x a, c2x b) { c2x c; c.r = c2Mulrr(a.r, b.r); c.p = c2Add(c2Mulrv(a.r, b.p), a.p); return c; } | |
public static c2x c2MulxxT(c2x a, c2x b) { c2x c; c.r = c2MulrrT(a.r, b.r); c.p = c2MulrvT(a.r, c2Sub(b.p, a.p)); return c; } | |
public static c2x c2Transform(c2v p, float radians) { c2x x; x.r = c2Rot(radians); x.p = p; return x; } | |
#endregion | |
#region halfspace ops | |
public static c2v c2Origin(c2h h) { return c2Mulvs(h.n, h.d); } | |
public static float c2Dist(c2h h, c2v p) { return c2Dot(h.n, p) - h.d; } | |
public static c2v c2Project(c2h h, c2v p) { return c2Sub(p, c2Mulvs(h.n, c2Dist(h, p))); } | |
public static c2h c2Mulxh(c2x a, c2h b) { c2h c; c.n = c2Mulrv(a.r, b.n); c.d = c2Dot(c2Mulxv(a, c2Origin(b)), c.n); return c; } | |
public static c2h c2MulxhT(c2x a, c2h b) { c2h c; c.n = c2MulrvT(a.r, b.n); c.d = c2Dot(c2MulxvT(a, c2Origin(b)), c.n); return c; } | |
public static c2v c2Intersect(c2v a, c2v b, float da, float db) { return c2Add(a, c2Mulvs(c2Sub(b, a), (da / (da - db)))); } | |
#endregion | |
public static unsafe void c2BBVerts(c2v* verts, ref c2AABB bb) | |
{ | |
verts[0] = bb.min; | |
verts[1] = c2V(bb.max.x, bb.min.y); | |
verts[2] = bb.max; | |
verts[3] = c2V(bb.min.x, bb.max.y); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 2d vector | |
public struct c2v | |
{ | |
public float x; | |
public float y; | |
public c2v(float x, float y) | |
{ | |
this.x = x; | |
this.y = y; | |
} | |
} | |
// 2d rotation composed of cos/sin pair | |
public struct c2r | |
{ | |
public float c; | |
public float s; | |
} | |
// 2d rotation matrix | |
public struct c2m | |
{ | |
public c2v x; | |
public c2v y; | |
} | |
public struct c2x | |
{ | |
public c2v p; | |
public c2r r; | |
} | |
// 2d halfspace (aka plane, aka line) | |
public struct c2h | |
{ | |
public c2v n; // normal, normalized | |
public float d; // distance to origin from plane, or ax + by = d | |
public c2h(c2v n, float d) | |
{ | |
this.n = n; | |
this.d = d; | |
} | |
} | |
public struct c2Circle | |
{ | |
public c2v p; | |
public float r; | |
} | |
public struct c2AABB | |
{ | |
public c2v min; | |
public c2v max; | |
} | |
// a capsule is defined as a line segment (from a to b) and radius r | |
public struct c2Capsule | |
{ | |
public c2v a; | |
public c2v b; | |
public float r; | |
} | |
public struct c2Poly | |
{ | |
public int count; | |
public unsafe fixed byte _verts[sizeof(float) * 2 * tiny.C2_MAX_POLYGON_VERTS]; // c2v[8] | |
public unsafe fixed byte _norms[sizeof(float) * 2 * tiny.C2_MAX_POLYGON_VERTS]; // c2v[8] | |
public unsafe c2v* GetVerts() | |
{ | |
fixed (c2Poly* poly = &this) | |
return (c2v*)(&poly->_verts[0]); | |
} | |
} | |
public struct c2Ray | |
{ | |
public c2v p; // position | |
public c2v d; // direction (normalized) | |
public float t; // distance along d from position p to find endpoint of ray | |
} | |
public struct c2Raycast | |
{ | |
public float t; // time of impact | |
public c2v n; // normal of surface at impact (unit length) | |
} | |
public unsafe struct c2Manifold | |
{ | |
public int count; | |
public fixed float depths[2]; | |
public unsafe fixed byte _contact_points[sizeof(float) * 2 * 2]; // c2v[2] | |
// always points from shape A to shape B (first and second shapes passed into any of the c2***to***Manifold functions) | |
public c2v n; | |
public c2v* contactPoints | |
{ | |
get | |
{ | |
fixed (c2Manifold* manifold = &this) | |
return (c2v*)(&manifold->_contact_points[0]); | |
} | |
} | |
} | |
public enum C2_TYPE | |
{ | |
C2_NONE, | |
C2_CIRCLE, | |
C2_AABB, | |
C2_CAPSULE, | |
C2_POLY | |
} | |
public unsafe struct c2GJKCache | |
{ | |
public float metric; | |
public int count; | |
public fixed int iA[3]; | |
public fixed int iB[3]; | |
public float div; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment