551 lines
12 KiB
Go
551 lines
12 KiB
Go
package world
|
|
|
|
import (
|
|
"slices"
|
|
"time"
|
|
)
|
|
|
|
func (w *World) Step(dt float32) {
|
|
if !finite(dt) || dt <= 0 {
|
|
return
|
|
}
|
|
w.lastForces, w.forces = w.forces, w.lastForces[:0]
|
|
w.events = append(w.events[:0], w.pending...)
|
|
w.pending = w.pending[:0]
|
|
w.releaseDead()
|
|
w.stats.Time = StageTimes{}
|
|
w.stats.BroadphaseCandidates = 0
|
|
w.stats.Islands = 0
|
|
w.inStep = true
|
|
w.stepOnce(dt)
|
|
w.inStep = false
|
|
items := w.bodies.items
|
|
for _, i := range w.awake {
|
|
b := &items[i]
|
|
b.force, b.torque = Vec2{}, 0
|
|
}
|
|
w.compactAwake()
|
|
w.steps++
|
|
w.countStats()
|
|
}
|
|
|
|
func (w *World) stepOnce(h float32) {
|
|
t := time.Now()
|
|
w.updatePairs()
|
|
t = w.lap(t, &w.stats.Time.Broad)
|
|
w.collide(h)
|
|
w.lap(t, &w.stats.Time.Narrow)
|
|
w.solve(h)
|
|
t = time.Now()
|
|
w.syncProxies(h)
|
|
t = w.lap(t, &w.stats.Time.Sync)
|
|
w.finishEvents()
|
|
w.lap(t, &w.stats.Time.Events)
|
|
}
|
|
|
|
func (w *World) lap(start time.Time, dst *time.Duration) time.Time {
|
|
now := time.Now()
|
|
*dst += now.Sub(start)
|
|
return now
|
|
}
|
|
|
|
func (w *World) releaseDead() {
|
|
for _, h := range w.deadS {
|
|
w.shapes.release(h)
|
|
}
|
|
w.deadS = append(w.deadS[:0], w.dyingS...)
|
|
w.dyingS = w.dyingS[:0]
|
|
for _, h := range w.deadB {
|
|
w.bodies.release(h)
|
|
}
|
|
w.deadB = append(w.deadB[:0], w.dyingB...)
|
|
w.dyingB = w.dyingB[:0]
|
|
}
|
|
|
|
func (w *World) ensureProx(id int32) {
|
|
for int(id) >= len(w.prox) {
|
|
w.prox = append(w.prox, proxyInfo{})
|
|
}
|
|
}
|
|
|
|
func (w *World) createProxies(sh ShapeHandle, s *shape, b *body) {
|
|
n := s.g.children()
|
|
s.proxies = s.proxies[:0]
|
|
m := w.margin()
|
|
for ci := 0; ci < n; ci++ {
|
|
tight := s.g.childAABB(ci, b.xf)
|
|
id := w.tree.create(boxExpand(tight, m), 0)
|
|
w.tree.setData(id, id)
|
|
w.ensureProx(id)
|
|
w.prox[id] = proxyInfo{shape: sh, child: int32(ci), tight: tight, alive: true, moved: true}
|
|
w.moveBuf = append(w.moveBuf, id)
|
|
s.proxies = append(s.proxies, id)
|
|
}
|
|
}
|
|
|
|
func (w *World) destroyProxies(s *shape) {
|
|
for _, id := range s.proxies {
|
|
w.tree.destroy(id)
|
|
w.prox[id] = proxyInfo{}
|
|
}
|
|
s.proxies = s.proxies[:0]
|
|
}
|
|
|
|
func (w *World) syncProxies(h float32) {
|
|
items := w.bodies.items
|
|
for _, i := range w.awake {
|
|
b := &items[i]
|
|
if b.dead || (!b.awake && b.mark != w.mark) {
|
|
continue
|
|
}
|
|
w.syncBody(b, h)
|
|
}
|
|
}
|
|
|
|
func (w *World) compactAwake() {
|
|
items := w.bodies.items
|
|
n := 0
|
|
for _, i := range w.awake {
|
|
b := &items[i]
|
|
if w.bodies.alive[i] && b.awake && !b.dead {
|
|
w.awake[n] = i
|
|
n++
|
|
continue
|
|
}
|
|
b.listed = false
|
|
}
|
|
w.awake = w.awake[:n]
|
|
}
|
|
|
|
func b2i(v bool) int {
|
|
if v {
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (w *World) syncBody(b *body, h float32) {
|
|
m := w.margin()
|
|
for _, sh := range b.shapes {
|
|
s := &w.shapes.items[sh.Index]
|
|
for ci, id := range s.proxies {
|
|
tight := s.g.childAABB(ci, b.xf)
|
|
p := &w.prox[id]
|
|
p.tight = tight
|
|
box := tight
|
|
if b.bullet && h > 0 {
|
|
box = boxSweep(tight, b.v.Scale(h))
|
|
}
|
|
if !b.bullet && boxContains(w.tree.fat(id), box) {
|
|
continue
|
|
}
|
|
w.tree.move(id, boxExpand(box, m))
|
|
if !p.moved {
|
|
p.moved = true
|
|
w.moveBuf = append(w.moveBuf, id)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func sortKey(p *proxyInfo) uint64 {
|
|
return uint64(p.shape.Index)<<32 | uint64(uint32(p.child))
|
|
}
|
|
|
|
func (w *World) updatePairs() {
|
|
w.newPairs = w.newPairs[:0]
|
|
for _, id := range w.moveBuf {
|
|
if !w.prox[id].alive {
|
|
continue
|
|
}
|
|
w.queryProxy = id
|
|
w.tree.query(w.tree.fat(id), w.pairFn)
|
|
}
|
|
for _, id := range w.moveBuf {
|
|
w.prox[id].moved = false
|
|
}
|
|
w.moveBuf = w.moveBuf[:0]
|
|
w.stats.BroadphaseCandidates += len(w.newPairs)
|
|
slices.SortFunc(w.newPairs, func(a, b candidate) int {
|
|
switch {
|
|
case a.lo != b.lo:
|
|
if a.lo < b.lo {
|
|
return -1
|
|
}
|
|
return 1
|
|
case a.hi != b.hi:
|
|
if a.hi < b.hi {
|
|
return -1
|
|
}
|
|
return 1
|
|
}
|
|
return 0
|
|
})
|
|
for i, c := range w.newPairs {
|
|
if i > 0 && w.newPairs[i-1].lo == c.lo && w.newPairs[i-1].hi == c.hi {
|
|
continue
|
|
}
|
|
if _, ok := w.pairs.get(pairKey(c.a, c.b)); ok {
|
|
continue
|
|
}
|
|
w.addContact(c.a, c.b)
|
|
}
|
|
}
|
|
|
|
func (w *World) pairQuery(other int32) bool {
|
|
id := w.queryProxy
|
|
if other == id {
|
|
return true
|
|
}
|
|
po := &w.prox[other]
|
|
if !po.alive || (po.moved && other < id) {
|
|
return true
|
|
}
|
|
pa := &w.prox[id]
|
|
if pa.shape == po.shape {
|
|
return true
|
|
}
|
|
if _, ok := w.pairs.get(pairKey(id, other)); ok {
|
|
return true
|
|
}
|
|
if !w.shouldCollide(pa.shape, po.shape) {
|
|
return true
|
|
}
|
|
ka, kb := sortKey(pa), sortKey(po)
|
|
if ka > kb {
|
|
ka, kb = kb, ka
|
|
}
|
|
w.newPairs = append(w.newPairs, candidate{a: id, b: other, lo: ka, hi: kb})
|
|
return true
|
|
}
|
|
|
|
func (w *World) shouldCollide(ha, hb ShapeHandle) bool {
|
|
a, b := &w.shapes.items[ha.Index], &w.shapes.items[hb.Index]
|
|
if a.dead || b.dead || a.body == b.body || (a.trigger && b.trigger) {
|
|
return false
|
|
}
|
|
if a.g.kind == KindChain && b.g.kind == KindChain {
|
|
return false
|
|
}
|
|
ba, bb := &w.bodies.items[a.body.Index], &w.bodies.items[b.body.Index]
|
|
if ba.typ != Dynamic && bb.typ != Dynamic {
|
|
if !a.trigger && !b.trigger {
|
|
return false
|
|
}
|
|
if ba.typ == Static && bb.typ == Static {
|
|
return false
|
|
}
|
|
}
|
|
if (w.layers[ba.layer]>>bb.layer)&1 == 0 || (ba.mask>>bb.layer)&1 == 0 || (bb.mask>>ba.layer)&1 == 0 {
|
|
return false
|
|
}
|
|
for _, jh := range ba.joints {
|
|
j := &w.joints.items[jh.Index]
|
|
if !j.collide && (j.a == b.body || j.b == b.body) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (w *World) addContact(pa, pb int32) {
|
|
A, B := w.prox[pa], w.prox[pb]
|
|
sa, sb := &w.shapes.items[A.shape.Index], &w.shapes.items[B.shape.Index]
|
|
swap := A.shape.Index > B.shape.Index
|
|
switch {
|
|
case sa.g.kind == KindChain:
|
|
swap = false
|
|
case sb.g.kind == KindChain:
|
|
swap = true
|
|
}
|
|
if swap {
|
|
A, B, sa, sb, pa, pb = B, A, sb, sa, pb, pa
|
|
}
|
|
var i int32
|
|
if n := len(w.freeC); n > 0 {
|
|
i = w.freeC[n-1]
|
|
w.freeC = w.freeC[:n-1]
|
|
} else {
|
|
w.contacts = append(w.contacts, contact{})
|
|
i = int32(len(w.contacts) - 1)
|
|
}
|
|
w.contacts[i] = contact{
|
|
shapeA: A.shape,
|
|
shapeB: B.shape,
|
|
bodyA: sa.body,
|
|
bodyB: sb.body,
|
|
childA: A.child,
|
|
childB: B.child,
|
|
proxyA: pa,
|
|
proxyB: pb,
|
|
key: pairKey(pa, pb),
|
|
friction: sqrtf(sa.friction * sb.friction),
|
|
restitution: max(sa.restitution, sb.restitution),
|
|
trigger: sa.trigger || sb.trigger,
|
|
alive: true,
|
|
}
|
|
w.pairs.put(w.contacts[i].key, i)
|
|
ba, bb := &w.bodies.items[sa.body.Index], &w.bodies.items[sb.body.Index]
|
|
ba.contacts = append(ba.contacts, i)
|
|
bb.contacts = append(bb.contacts, i)
|
|
}
|
|
|
|
func unlink(list []int32, i int32) []int32 {
|
|
for k, x := range list {
|
|
if x == i {
|
|
last := len(list) - 1
|
|
list[k] = list[last]
|
|
return list[:last]
|
|
}
|
|
}
|
|
return list
|
|
}
|
|
|
|
func (w *World) destroyContact(i int32, wake bool) {
|
|
c := &w.contacts[i]
|
|
if c.touching {
|
|
c.touching = false
|
|
w.touching--
|
|
w.touchEnd(c, &c.m)
|
|
}
|
|
w.manifolds -= b2i(c.m.Count > 0)
|
|
w.points -= c.m.Count
|
|
w.pairs.del(c.key)
|
|
ba, bb := &w.bodies.items[c.bodyA.Index], &w.bodies.items[c.bodyB.Index]
|
|
ba.contacts = unlink(ba.contacts, i)
|
|
bb.contacts = unlink(bb.contacts, i)
|
|
if wake {
|
|
w.wake(ba)
|
|
w.wake(bb)
|
|
}
|
|
c.alive = false
|
|
c.m = Manifold{}
|
|
w.freeC = append(w.freeC, i)
|
|
}
|
|
|
|
func (w *World) dropShapeContacts(sh ShapeHandle) {
|
|
for i := range w.contacts {
|
|
c := &w.contacts[i]
|
|
if c.alive && (c.shapeA == sh || c.shapeB == sh) {
|
|
w.destroyContact(int32(i), true)
|
|
}
|
|
}
|
|
}
|
|
|
|
func shapePairKey(c *contact) uint64 {
|
|
return pairKey(int32(c.shapeA.Index), int32(c.shapeB.Index))
|
|
}
|
|
|
|
func (w *World) touchBegin(i int32, c *contact) {
|
|
k := shapePairKey(c)
|
|
n, _ := w.touch.get(k)
|
|
w.touch.put(k, n+1)
|
|
if n == 0 {
|
|
var p Vec2
|
|
if c.m.Count > 0 {
|
|
p = c.m.Points[0].Point
|
|
}
|
|
w.began = append(w.began, beginRec{c: i, rel: w.relVel(c, p)})
|
|
}
|
|
}
|
|
|
|
func (w *World) touchEnd(c *contact, m *Manifold) {
|
|
k := shapePairKey(c)
|
|
n, _ := w.touch.get(k)
|
|
if n > 1 {
|
|
w.touch.put(k, n-1)
|
|
return
|
|
}
|
|
w.touch.del(k)
|
|
kind := EndContact
|
|
if c.trigger {
|
|
kind = EndTrigger
|
|
}
|
|
ev := w.event(c, m, kind)
|
|
ev.NormalImpulse = 0
|
|
if w.inStep {
|
|
w.events = append(w.events, ev)
|
|
} else {
|
|
w.pending = append(w.pending, ev)
|
|
}
|
|
}
|
|
|
|
func (w *World) relVel(c *contact, p Vec2) Vec2 {
|
|
ba, bb := &w.bodies.items[c.bodyA.Index], &w.bodies.items[c.bodyB.Index]
|
|
va := ba.v.Add(crossSV(ba.w, p.Sub(ba.center)))
|
|
vb := bb.v.Add(crossSV(bb.w, p.Sub(bb.center)))
|
|
return va.Sub(vb)
|
|
}
|
|
|
|
func (w *World) event(c *contact, m *Manifold, kind EventKind) ContactEvent {
|
|
ev := ContactEvent{
|
|
Kind: kind,
|
|
ShapeA: c.shapeA,
|
|
ShapeB: c.shapeB,
|
|
BodyA: c.bodyA,
|
|
BodyB: c.bodyB,
|
|
Normal: m.Normal.Neg(),
|
|
}
|
|
if m.Count > 0 {
|
|
var p Vec2
|
|
for j := 0; j < m.Count; j++ {
|
|
p = p.Add(m.Points[j].Point)
|
|
ev.NormalImpulse += m.Points[j].TotalNormalImpulse
|
|
}
|
|
ev.Point = p.Scale(1 / float32(m.Count))
|
|
}
|
|
ev.RelativeVelocity = w.relVel(c, ev.Point)
|
|
return ev
|
|
}
|
|
|
|
func (w *World) collide(h float32) {
|
|
w.seen++
|
|
items := w.bodies.items
|
|
cand := w.cand[:0]
|
|
for _, bi := range w.awake {
|
|
b := &items[bi]
|
|
if !b.awake || b.dead {
|
|
continue
|
|
}
|
|
for _, ci := range b.contacts {
|
|
if c := &w.contacts[ci]; c.seen != w.seen {
|
|
c.seen = w.seen
|
|
cand = append(cand, ci)
|
|
}
|
|
}
|
|
}
|
|
slices.Sort(cand)
|
|
w.cand = cand
|
|
for _, ci := range cand {
|
|
c := &w.contacts[ci]
|
|
if !c.alive {
|
|
continue
|
|
}
|
|
if !boxOverlap(w.tree.fat(c.proxyA), w.tree.fat(c.proxyB)) {
|
|
w.destroyContact(ci, false)
|
|
continue
|
|
}
|
|
w.updateContact(ci, c, &items[c.bodyA.Index], &items[c.bodyB.Index], h)
|
|
}
|
|
}
|
|
|
|
func (w *World) updateContact(i int32, c *contact, ba, bb *body, h float32) {
|
|
sa, sb := &w.shapes.items[c.shapeA.Index], &w.shapes.items[c.shapeB.Index]
|
|
old := c.m
|
|
spec := w.cfg.ContactOffset
|
|
switch {
|
|
case c.trigger:
|
|
spec = 0
|
|
case ba.bullet || bb.bullet:
|
|
spec = max(spec, bb.v.Sub(ba.v).Len()*h)
|
|
}
|
|
gb := sb.g.child(int(c.childB))
|
|
if sa.g.kind == KindChain {
|
|
seg, g0, g3 := sa.g.chain.segment(int(c.childA))
|
|
collideChainSegment(&seg, g0, g3, sa.oneSided, ba.xf, &gb, bb.xf, spec, &c.m)
|
|
} else {
|
|
ga := sa.g.c
|
|
collideConvex(&ga, ba.xf, &gb, bb.xf, spec, &c.m)
|
|
}
|
|
for k := 0; k < c.m.Count; k++ {
|
|
p := &c.m.Points[k]
|
|
p.AnchorA = p.Point.Sub(ba.center)
|
|
p.AnchorB = p.Point.Sub(bb.center)
|
|
p.NormalImpulse, p.TangentImpulse, p.TotalNormalImpulse, p.Persisted = 0, 0, 0, false
|
|
for j := 0; j < old.Count; j++ {
|
|
if old.Points[j].ID == p.ID {
|
|
p.NormalImpulse, p.TangentImpulse, p.Persisted = old.Points[j].NormalImpulse, old.Points[j].TangentImpulse, true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
w.manifolds += b2i(c.m.Count > 0) - b2i(old.Count > 0)
|
|
w.points += c.m.Count - old.Count
|
|
touching := c.m.Count > 0
|
|
if touching && !c.trigger {
|
|
touching = c.m.minSeparation() <= w.cfg.LinearSlop
|
|
}
|
|
switch {
|
|
case touching && !c.touching:
|
|
c.touching = true
|
|
w.touching++
|
|
w.touchBegin(i, c)
|
|
w.wake(ba)
|
|
w.wake(bb)
|
|
case !touching && c.touching:
|
|
c.touching = false
|
|
w.touching--
|
|
w.touchEnd(c, &old)
|
|
}
|
|
}
|
|
|
|
func (w *World) finishEvents() {
|
|
for _, r := range w.began {
|
|
c := &w.contacts[r.c]
|
|
if !c.alive || !c.touching {
|
|
continue
|
|
}
|
|
kind := BeginContact
|
|
if c.trigger {
|
|
kind = BeginTrigger
|
|
}
|
|
ev := w.event(c, &c.m, kind)
|
|
ev.RelativeVelocity = r.rel
|
|
w.events = append(w.events, ev)
|
|
}
|
|
w.began = w.began[:0]
|
|
}
|
|
|
|
func (w *World) countStats() {
|
|
s := &w.stats
|
|
s.Steps = w.steps
|
|
s.Bodies = w.liveBodies
|
|
s.Active = len(w.awake)
|
|
s.Sleeping = w.liveMovable - s.Active
|
|
s.Shapes = w.liveShapes
|
|
s.TouchingPairs = w.touching
|
|
s.Manifolds = w.manifolds
|
|
s.ContactPoints = w.points
|
|
s.Joints = w.joints.count
|
|
}
|
|
|
|
func (w *World) rebuildDerived() {
|
|
w.awake = w.awake[:0]
|
|
w.seen = 0
|
|
w.liveBodies, w.liveMovable, w.liveShapes = 0, 0, 0
|
|
w.touching, w.manifolds, w.points = 0, 0, 0
|
|
for i := range w.bodies.items {
|
|
if !w.bodies.alive[i] {
|
|
continue
|
|
}
|
|
b := &w.bodies.items[i]
|
|
b.listed = false
|
|
if b.dead {
|
|
continue
|
|
}
|
|
w.liveBodies++
|
|
if b.typ == Static {
|
|
continue
|
|
}
|
|
w.liveMovable++
|
|
if b.awake {
|
|
w.listAwake(b)
|
|
}
|
|
}
|
|
for i := range w.shapes.items {
|
|
if w.shapes.alive[i] && !w.shapes.items[i].dead {
|
|
w.liveShapes++
|
|
}
|
|
}
|
|
for i := range w.contacts {
|
|
c := &w.contacts[i]
|
|
c.seen = 0
|
|
if !c.alive {
|
|
continue
|
|
}
|
|
w.touching += b2i(c.touching)
|
|
w.manifolds += b2i(c.m.Count > 0)
|
|
w.points += c.m.Count
|
|
}
|
|
}
|