387 lines
8.8 KiB
Go
387 lines
8.8 KiB
Go
package world
|
|
|
|
const (
|
|
maxManifoldPoints = 2
|
|
faceTolerance = 0.0005
|
|
cornerTolerance = 0.01
|
|
)
|
|
|
|
type ManifoldPoint struct {
|
|
Point Vec2
|
|
AnchorA Vec2
|
|
AnchorB Vec2
|
|
Separation float32
|
|
NormalImpulse float32
|
|
TangentImpulse float32
|
|
TotalNormalImpulse float32
|
|
ID uint16
|
|
Persisted bool
|
|
}
|
|
|
|
type Manifold struct {
|
|
Normal Vec2
|
|
Points [maxManifoldPoints]ManifoldPoint
|
|
Count int
|
|
}
|
|
|
|
func (m *Manifold) minSeparation() float32 {
|
|
s := float32(1e30)
|
|
for i := 0; i < m.Count; i++ {
|
|
s = min(s, m.Points[i].Separation)
|
|
}
|
|
return s
|
|
}
|
|
|
|
func makeID(a, b int) uint16 {
|
|
return uint16(a&0xff)<<8 | uint16(b&0xff)
|
|
}
|
|
|
|
func swapID(id uint16) uint16 {
|
|
return id>>8 | id<<8
|
|
}
|
|
|
|
func collideConvex(a *convex, xfA xform, b *convex, xfB xform, spec float32, m *Manifold) {
|
|
m.Count = 0
|
|
if a.count == 1 && b.count > 1 {
|
|
collideConvex(b, xfB, a, xfA, spec, m)
|
|
m.Normal = m.Normal.Neg()
|
|
for i := 0; i < m.Count; i++ {
|
|
m.Points[i].ID = swapID(m.Points[i].ID)
|
|
}
|
|
return
|
|
}
|
|
xf := invMulXf(xfA, xfB)
|
|
lb := b.transformed(xf)
|
|
switch {
|
|
case a.count == 1:
|
|
collideCircles(a, &lb, spec, m)
|
|
case b.count == 1:
|
|
collidePolyCircle(a, &lb, spec, m)
|
|
default:
|
|
collidePolys(a, &lb, spec, m)
|
|
}
|
|
for i := 0; i < m.Count; i++ {
|
|
m.Points[i].Point = xfA.apply(m.Points[i].Point)
|
|
}
|
|
m.Normal = xfA.q.apply(m.Normal)
|
|
}
|
|
|
|
func collideCircles(a, b *convex, spec float32, m *Manifold) {
|
|
pA, pB := a.v[0], b.v[0]
|
|
n, dist := normalizeLen(pB.Sub(pA))
|
|
sep := dist - a.radius - b.radius
|
|
if sep > spec {
|
|
return
|
|
}
|
|
if dist == 0 {
|
|
n = Vec2{X: 1}
|
|
}
|
|
cA := pA.Add(n.Scale(a.radius))
|
|
cB := pB.Sub(n.Scale(b.radius))
|
|
m.Normal = n
|
|
m.Points[0] = ManifoldPoint{Point: cA.Lerp(cB, 0.5), Separation: sep}
|
|
m.Count = 1
|
|
}
|
|
|
|
func collidePolyCircle(a, b *convex, spec float32, m *Manifold) {
|
|
c := b.v[0]
|
|
rA, rB := a.radius, b.radius
|
|
idx := 0
|
|
sep := float32(-3.4e38)
|
|
for i := 0; i < a.count; i++ {
|
|
s := a.n[i].Dot(c.Sub(a.v[i]))
|
|
if s > sep {
|
|
sep, idx = s, i
|
|
}
|
|
}
|
|
if sep-rA-rB > spec {
|
|
return
|
|
}
|
|
j := idx + 1
|
|
if j == a.count {
|
|
j = 0
|
|
}
|
|
v1, v2 := a.v[idx], a.v[j]
|
|
u1 := c.Sub(v1).Dot(v2.Sub(v1))
|
|
u2 := c.Sub(v2).Dot(v1.Sub(v2))
|
|
vertex := func(v Vec2, id int) {
|
|
n, d := normalizeLen(c.Sub(v))
|
|
if d == 0 {
|
|
n = a.n[idx]
|
|
}
|
|
s := d - rA - rB
|
|
if s > spec {
|
|
return
|
|
}
|
|
cA := v.Add(n.Scale(rA))
|
|
cB := c.Sub(n.Scale(rB))
|
|
m.Normal = n
|
|
m.Points[0] = ManifoldPoint{Point: cA.Lerp(cB, 0.5), Separation: s, ID: makeID(id, 0)}
|
|
m.Count = 1
|
|
}
|
|
switch {
|
|
case u1 < 0 && sep > fltEpsilon:
|
|
vertex(v1, idx)
|
|
case u2 < 0 && sep > fltEpsilon:
|
|
vertex(v2, j)
|
|
default:
|
|
n := a.n[idx]
|
|
cA := c.Add(n.Scale(rA - c.Sub(v1).Dot(n)))
|
|
cB := c.Sub(n.Scale(rB))
|
|
s := cB.Sub(cA).Dot(n)
|
|
if s > spec {
|
|
return
|
|
}
|
|
m.Normal = n
|
|
m.Points[0] = ManifoldPoint{Point: cA.Lerp(cB, 0.5), Separation: s, ID: makeID(idx, 0)}
|
|
m.Count = 1
|
|
}
|
|
}
|
|
|
|
func findMaxSeparation(p1, p2 *convex) (int, float32) {
|
|
best := 0
|
|
maxSep := float32(-3.4e38)
|
|
for i := 0; i < p1.count; i++ {
|
|
n, v1 := p1.n[i], p1.v[i]
|
|
si := float32(3.4e38)
|
|
for j := 0; j < p2.count; j++ {
|
|
si = min(si, n.Dot(p2.v[j].Sub(v1)))
|
|
}
|
|
if si > maxSep {
|
|
maxSep, best = si, i
|
|
}
|
|
}
|
|
return best, maxSep
|
|
}
|
|
|
|
func incident(ref Vec2, p *convex) int {
|
|
best := 0
|
|
minDot := float32(3.4e38)
|
|
for i := 0; i < p.count; i++ {
|
|
if d := ref.Dot(p.n[i]); d < minDot {
|
|
minDot, best = d, i
|
|
}
|
|
}
|
|
return best
|
|
}
|
|
|
|
func next(i, count int) int {
|
|
if i+1 < count {
|
|
return i + 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
type segDist struct {
|
|
f1, f2 float32
|
|
c1, c2 Vec2
|
|
distanceSq float32
|
|
}
|
|
|
|
func segmentDistance(p1, q1, p2, q2 Vec2) segDist {
|
|
var r segDist
|
|
d1, d2 := q1.Sub(p1), q2.Sub(p2)
|
|
w := p1.Sub(p2)
|
|
dd1, dd2 := d1.Dot(d1), d2.Dot(d2)
|
|
rd1, rd2 := w.Dot(d1), w.Dot(d2)
|
|
const epsSq = fltEpsilon * fltEpsilon
|
|
switch {
|
|
case dd1 < epsSq || dd2 < epsSq:
|
|
switch {
|
|
case dd1 >= epsSq:
|
|
r.f1 = clampf(-rd1/dd1, 0, 1)
|
|
case dd2 >= epsSq:
|
|
r.f2 = clampf(rd2/dd2, 0, 1)
|
|
}
|
|
default:
|
|
d12 := d1.Dot(d2)
|
|
denom := dd1*dd2 - d12*d12
|
|
var f1 float32
|
|
if denom != 0 {
|
|
f1 = clampf((d12*rd2-rd1*dd2)/denom, 0, 1)
|
|
}
|
|
f2 := (d12*f1 + rd2) / dd2
|
|
switch {
|
|
case f2 < 0:
|
|
f2 = 0
|
|
f1 = clampf(-rd1/dd1, 0, 1)
|
|
case f2 > 1:
|
|
f2 = 1
|
|
f1 = clampf((d12-rd1)/dd1, 0, 1)
|
|
}
|
|
r.f1, r.f2 = f1, f2
|
|
}
|
|
r.c1 = p1.Add(d1.Scale(r.f1))
|
|
r.c2 = p2.Add(d2.Scale(r.f2))
|
|
r.distanceSq = r.c1.Sub(r.c2).LenSq()
|
|
return r
|
|
}
|
|
|
|
func collidePolys(a, b *convex, spec float32, m *Manifold) {
|
|
edgeA, sepA := findMaxSeparation(a, b)
|
|
edgeB, sepB := findMaxSeparation(b, a)
|
|
radius := a.radius + b.radius
|
|
if sepA > spec+radius || sepB > spec+radius {
|
|
return
|
|
}
|
|
const linearSlop = 0.005
|
|
flip := sepB > 0.1*linearSlop+sepA
|
|
if flip {
|
|
edgeA = incident(b.n[edgeB], a)
|
|
} else {
|
|
edgeB = incident(a.n[edgeA], b)
|
|
}
|
|
if (!flip && sepA > 0.1*linearSlop) || (flip && sepB > 0.1*linearSlop) {
|
|
i11, i12 := edgeA, next(edgeA, a.count)
|
|
i21, i22 := edgeB, next(edgeB, b.count)
|
|
v11, v12 := a.v[i11], a.v[i12]
|
|
v21, v22 := b.v[i21], b.v[i22]
|
|
r := segmentDistance(v11, v12, v21, v22)
|
|
vertex := func(pa, pb Vec2, ia, ib int) {
|
|
n, dist := normalizeLen(pb.Sub(pa))
|
|
if dist == 0 || dist > spec+radius {
|
|
return
|
|
}
|
|
c1 := pa.Add(n.Scale(a.radius))
|
|
c2 := pb.Sub(n.Scale(b.radius))
|
|
m.Normal = n
|
|
m.Points[0] = ManifoldPoint{Point: c1.Lerp(c2, 0.5), Separation: dist - radius, ID: makeID(ia, ib)}
|
|
m.Count = 1
|
|
}
|
|
switch {
|
|
case edgesOverlap(v11, v12, v21, v22, linearSlop):
|
|
case r.f1 == 0 && r.f2 == 0:
|
|
vertex(v11, v21, i11, i21)
|
|
return
|
|
case r.f1 == 0 && r.f2 == 1:
|
|
vertex(v11, v22, i11, i22)
|
|
return
|
|
case r.f1 == 1 && r.f2 == 0:
|
|
vertex(v12, v21, i12, i21)
|
|
return
|
|
case r.f1 == 1 && r.f2 == 1:
|
|
vertex(v12, v22, i12, i22)
|
|
return
|
|
}
|
|
}
|
|
clipPolygons(a, b, edgeA, edgeB, flip, spec, m)
|
|
}
|
|
|
|
func edgesOverlap(p1, q1, p2, q2 Vec2, slop float32) bool {
|
|
t, l := normalizeLen(q1.Sub(p1))
|
|
e2 := q2.Sub(p2)
|
|
if l == 0 || abs32(t.Cross(e2)) > 1e-4*e2.Len() {
|
|
return false
|
|
}
|
|
a, b := p2.Sub(p1).Dot(t), q2.Sub(p1).Dot(t)
|
|
return min(max(a, b), l)-max(min(a, b), 0) > slop
|
|
}
|
|
|
|
func clipPolygons(a, b *convex, edgeA, edgeB int, flip bool, spec float32, m *Manifold) {
|
|
p1, p2 := a, b
|
|
i11, i21 := edgeA, edgeB
|
|
if flip {
|
|
p1, p2 = b, a
|
|
i11, i21 = edgeB, edgeA
|
|
}
|
|
i12, i22 := next(i11, p1.count), next(i21, p2.count)
|
|
normal := p1.n[i11]
|
|
v11, v12 := p1.v[i11], p1.v[i12]
|
|
v21, v22 := p2.v[i21], p2.v[i22]
|
|
tangent := crossSV(1, normal)
|
|
lower1 := float32(0)
|
|
upper1 := v12.Sub(v11).Dot(tangent)
|
|
upper2 := v21.Sub(v11).Dot(tangent)
|
|
lower2 := v22.Sub(v11).Dot(tangent)
|
|
vLower, vUpper := v22, v21
|
|
if lower2 < lower1 && upper2-lower2 > fltEpsilon {
|
|
vLower = v22.Lerp(v21, (lower1-lower2)/(upper2-lower2))
|
|
}
|
|
if upper2 > upper1 && upper2-lower2 > fltEpsilon {
|
|
vUpper = v22.Lerp(v21, (upper1-lower2)/(upper2-lower2))
|
|
}
|
|
sepLower := vLower.Sub(v11).Dot(normal)
|
|
sepUpper := vUpper.Sub(v11).Dot(normal)
|
|
r1, r2 := p1.radius, p2.radius
|
|
vLower = vLower.Add(normal.Scale(0.5 * (r1 - r2 - sepLower)))
|
|
vUpper = vUpper.Add(normal.Scale(0.5 * (r1 - r2 - sepUpper)))
|
|
radius := r1 + r2
|
|
add := func(p Vec2, s float32, id uint16) {
|
|
if s <= spec {
|
|
m.Points[m.Count] = ManifoldPoint{Point: p, Separation: s, ID: id}
|
|
m.Count++
|
|
}
|
|
}
|
|
if !flip {
|
|
m.Normal = normal
|
|
add(vLower, sepLower-radius, makeID(i11, i22))
|
|
add(vUpper, sepUpper-radius, makeID(i12, i21))
|
|
return
|
|
}
|
|
m.Normal = normal.Neg()
|
|
add(vUpper, sepUpper-radius, makeID(i21, i12))
|
|
add(vLower, sepLower-radius, makeID(i22, i11))
|
|
}
|
|
|
|
func collideChainSegment(seg *convex, g0, g3 Vec2, oneSided bool, xfA xform, b *convex, xfB xform, spec float32, m *Manifold) {
|
|
collideConvex(seg, xfA, b, xfB, spec, m)
|
|
if m.Count == 0 {
|
|
return
|
|
}
|
|
v1, v2 := seg.v[0], seg.v[1]
|
|
e, _ := normalizeLen(v2.Sub(v1))
|
|
n := rightPerp(e)
|
|
nl := xfA.q.invApply(m.Normal)
|
|
if oneSided {
|
|
cb := xfA.invApply(xfB.apply(b.centroid))
|
|
if cb.Sub(v1).Dot(n) < 0 {
|
|
m.Count = 0
|
|
return
|
|
}
|
|
}
|
|
s := float32(1)
|
|
if !oneSided && nl.Dot(n) < 0 {
|
|
s, n = -1, n.Neg()
|
|
}
|
|
if nl.Dot(n) >= 1-faceTolerance {
|
|
return
|
|
}
|
|
snap := oneSided && nl.Dot(n) < 0
|
|
if !snap {
|
|
var adj Vec2
|
|
var convexCorner bool
|
|
if nl.Dot(e) < 0 {
|
|
e0, l := normalizeLen(v1.Sub(g0))
|
|
convexCorner = l > 0 && s*e0.Cross(e) > cornerTolerance
|
|
adj = rightPerp(e0).Scale(s)
|
|
} else {
|
|
e2, l := normalizeLen(g3.Sub(v2))
|
|
convexCorner = l > 0 && s*e.Cross(e2) > cornerTolerance
|
|
adj = rightPerp(e2).Scale(s)
|
|
}
|
|
if convexCorner {
|
|
span := adj.Cross(n)
|
|
if adj.Cross(nl)*span < 0 || nl.Cross(n)*span < 0 {
|
|
m.Count = 0
|
|
}
|
|
return
|
|
}
|
|
}
|
|
nw := xfA.q.apply(n)
|
|
k := 0
|
|
for i := 0; i < m.Count; i++ {
|
|
p := m.Points[i]
|
|
cb := p.Point.Add(m.Normal.Scale(0.5 * p.Separation))
|
|
sep := xfA.invApply(cb).Sub(v1).Dot(n)
|
|
if sep > spec {
|
|
continue
|
|
}
|
|
p.Separation = sep
|
|
p.Point = cb.Sub(nw.Scale(0.5 * sep))
|
|
m.Points[k] = p
|
|
k++
|
|
}
|
|
m.Count = k
|
|
m.Normal = nw
|
|
}
|