412 lines
9.3 KiB
Go
412 lines
9.3 KiB
Go
package world
|
|
|
|
type queryState struct {
|
|
w *World
|
|
filter QueryFilter
|
|
origin Vec2
|
|
dir Vec2
|
|
maxDist float32
|
|
best RayHit
|
|
found bool
|
|
hits []RayHit
|
|
n int
|
|
shapes []ShapeHandle
|
|
point Vec2
|
|
probe convex
|
|
probeXf xform
|
|
pick ShapeHandle
|
|
picked bool
|
|
rayFn func(int32, Vec2, Vec2, float32) float32
|
|
allFn func(int32, Vec2, Vec2, float32) float32
|
|
pointFn func(int32) bool
|
|
shapeFn func(int32) bool
|
|
atFn func(int32) bool
|
|
}
|
|
|
|
func (q *queryState) init(w *World) {
|
|
q.w = w
|
|
q.rayFn = q.rayClosest
|
|
q.allFn = q.rayAll
|
|
q.pointFn = q.pointHit
|
|
q.shapeFn = q.shapeHit
|
|
q.atFn = q.atHit
|
|
}
|
|
|
|
func (q *queryState) accept(s *shape) bool {
|
|
if s.dead {
|
|
return false
|
|
}
|
|
f := &q.filter
|
|
if s.trigger && !f.IncludeTriggers {
|
|
return false
|
|
}
|
|
if !f.ExcludeBody.IsZero() && s.body == f.ExcludeBody {
|
|
return false
|
|
}
|
|
return (f.LayerMask>>q.w.bodies.items[s.body.Index].layer)&1 != 0
|
|
}
|
|
|
|
func (q *queryState) cast(s *shape, child int, maxT float32) (float32, Vec2, bool) {
|
|
b := &q.w.bodies.items[s.body.Index]
|
|
lp := b.xf.invApply(q.origin)
|
|
ld := b.xf.q.invApply(q.dir)
|
|
var t float32
|
|
var n Vec2
|
|
var ok bool
|
|
if s.g.kind == KindChain {
|
|
seg, _, _ := s.g.chain.segment(child)
|
|
t, n, ok = raySegment(seg.v[0], seg.v[1], lp, ld, maxT, s.oneSided)
|
|
} else {
|
|
t, n, ok = rayConvex(&s.g.c, lp, ld, maxT)
|
|
}
|
|
if !ok {
|
|
return 0, Vec2{}, false
|
|
}
|
|
return t, b.xf.q.apply(n), true
|
|
}
|
|
|
|
func (q *queryState) hitAt(t float32, n Vec2, sh ShapeHandle, bh BodyHandle) RayHit {
|
|
return RayHit{
|
|
Point: q.origin.Add(q.dir.Scale(t)),
|
|
Normal: n,
|
|
Distance: t,
|
|
Fraction: t / q.maxDist,
|
|
Shape: sh,
|
|
Body: bh,
|
|
}
|
|
}
|
|
|
|
func hitLess(a, b RayHit) bool {
|
|
if a.Distance != b.Distance {
|
|
return a.Distance < b.Distance
|
|
}
|
|
return a.Shape.Index < b.Shape.Index
|
|
}
|
|
|
|
func (q *queryState) rayClosest(id int32, _, _ Vec2, maxFraction float32) float32 {
|
|
pi := &q.w.prox[id]
|
|
s := &q.w.shapes.items[pi.shape.Index]
|
|
if !q.accept(s) {
|
|
return -1
|
|
}
|
|
t, n, ok := q.cast(s, int(pi.child), maxFraction*q.maxDist)
|
|
if !ok {
|
|
return -1
|
|
}
|
|
hit := q.hitAt(t, n, pi.shape, s.body)
|
|
if q.found && !hitLess(hit, q.best) {
|
|
return q.best.Fraction
|
|
}
|
|
q.best, q.found = hit, true
|
|
return hit.Fraction
|
|
}
|
|
|
|
func (q *queryState) rayAll(id int32, _, _ Vec2, maxFraction float32) float32 {
|
|
pi := &q.w.prox[id]
|
|
s := &q.w.shapes.items[pi.shape.Index]
|
|
if !q.accept(s) {
|
|
return -1
|
|
}
|
|
t, n, ok := q.cast(s, int(pi.child), maxFraction*q.maxDist)
|
|
if !ok {
|
|
return -1
|
|
}
|
|
size := len(q.hits)
|
|
hit := q.hitAt(t, n, pi.shape, s.body)
|
|
if q.n == size {
|
|
if !hitLess(hit, q.hits[size-1]) {
|
|
return q.hits[size-1].Fraction
|
|
}
|
|
q.n--
|
|
}
|
|
i := q.n
|
|
for i > 0 && hitLess(hit, q.hits[i-1]) {
|
|
q.hits[i] = q.hits[i-1]
|
|
i--
|
|
}
|
|
q.hits[i] = hit
|
|
q.n++
|
|
if q.n == size {
|
|
return q.hits[size-1].Fraction
|
|
}
|
|
return 1
|
|
}
|
|
|
|
func (q *queryState) add(h ShapeHandle) {
|
|
for _, x := range q.shapes[:q.n] {
|
|
if x == h {
|
|
return
|
|
}
|
|
}
|
|
q.shapes[q.n] = h
|
|
q.n++
|
|
}
|
|
|
|
func (q *queryState) pointHit(id int32) bool {
|
|
pi := &q.w.prox[id]
|
|
s := &q.w.shapes.items[pi.shape.Index]
|
|
if s.g.kind == KindChain || !q.accept(s) {
|
|
return true
|
|
}
|
|
b := &q.w.bodies.items[s.body.Index]
|
|
if pointInConvex(&s.g.c, b.xf.invApply(q.point)) {
|
|
q.add(pi.shape)
|
|
}
|
|
return q.n < len(q.shapes)
|
|
}
|
|
|
|
func (q *queryState) shapeHit(id int32) bool {
|
|
pi := &q.w.prox[id]
|
|
s := &q.w.shapes.items[pi.shape.Index]
|
|
if !q.accept(s) {
|
|
return true
|
|
}
|
|
b := &q.w.bodies.items[s.body.Index]
|
|
var m Manifold
|
|
g := s.g.child(int(pi.child))
|
|
collideConvex(&g, b.xf, &q.probe, q.probeXf, 0, &m)
|
|
if m.Count > 0 {
|
|
q.add(pi.shape)
|
|
}
|
|
return q.n < len(q.shapes)
|
|
}
|
|
|
|
func (q *queryState) atHit(id int32) bool {
|
|
pi := &q.w.prox[id]
|
|
s := &q.w.shapes.items[pi.shape.Index]
|
|
if s.g.kind == KindChain || !q.accept(s) {
|
|
return true
|
|
}
|
|
if q.picked && pi.shape.Index >= q.pick.Index {
|
|
return true
|
|
}
|
|
b := &q.w.bodies.items[s.body.Index]
|
|
if pointInConvex(&s.g.c, b.xf.invApply(q.point)) {
|
|
q.pick, q.picked = pi.shape, true
|
|
}
|
|
return true
|
|
}
|
|
|
|
func sortHandles(list []ShapeHandle) {
|
|
for i := 1; i < len(list); i++ {
|
|
for j := i; j > 0 && list[j].Index < list[j-1].Index; j-- {
|
|
list[j], list[j-1] = list[j-1], list[j]
|
|
}
|
|
}
|
|
}
|
|
|
|
func (w *World) Raycast(origin, dir Vec2, maxDist float32, f QueryFilter) (RayHit, bool) {
|
|
d, l := normalizeLen(dir)
|
|
if l == 0 || !finite2(origin) || !finite2(dir) || !finite(maxDist) || maxDist <= 0 {
|
|
return RayHit{}, false
|
|
}
|
|
q := &w.q
|
|
q.filter, q.origin, q.dir, q.maxDist = f, origin, d, maxDist
|
|
q.best, q.found = RayHit{}, false
|
|
w.tree.raycast(origin, origin.Add(d.Scale(maxDist)), 1, q.rayFn)
|
|
return q.best, q.found
|
|
}
|
|
|
|
func (w *World) RaycastAll(origin, dir Vec2, maxDist float32, f QueryFilter, out []RayHit) int {
|
|
d, l := normalizeLen(dir)
|
|
if len(out) == 0 || l == 0 || !finite2(origin) || !finite2(dir) || !finite(maxDist) || maxDist <= 0 {
|
|
return 0
|
|
}
|
|
q := &w.q
|
|
q.filter, q.origin, q.dir, q.maxDist = f, origin, d, maxDist
|
|
q.hits, q.n = out, 0
|
|
w.tree.raycast(origin, origin.Add(d.Scale(maxDist)), 1, q.allFn)
|
|
n := q.n
|
|
q.hits = nil
|
|
return n
|
|
}
|
|
|
|
func (w *World) OverlapPoint(p Vec2, f QueryFilter, out []ShapeHandle) int {
|
|
if len(out) == 0 || !finite2(p) {
|
|
return 0
|
|
}
|
|
q := &w.q
|
|
q.filter, q.point, q.shapes, q.n = f, p, out, 0
|
|
w.tree.query(AABB{Min: p, Max: p}, q.pointFn)
|
|
n := q.n
|
|
q.shapes = nil
|
|
sortHandles(out[:n])
|
|
return n
|
|
}
|
|
|
|
func (w *World) overlapProbe(f QueryFilter, out []ShapeHandle) int {
|
|
q := &w.q
|
|
q.filter, q.shapes, q.n = f, out, 0
|
|
w.tree.query(q.probe.aabb(q.probeXf), q.shapeFn)
|
|
n := q.n
|
|
q.shapes = nil
|
|
sortHandles(out[:n])
|
|
return n
|
|
}
|
|
|
|
func (w *World) OverlapCircle(c Vec2, r float32, f QueryFilter, out []ShapeHandle) int {
|
|
if len(out) == 0 || !finite2(c) || !finite(r) || r <= 0 {
|
|
return 0
|
|
}
|
|
q := &w.q
|
|
q.probe = convex{count: 1, radius: r}
|
|
q.probeXf = xform{p: c, q: Rot{C: 1}}
|
|
return w.overlapProbe(f, out)
|
|
}
|
|
|
|
func (w *World) OverlapBox(c, half Vec2, angle float32, f QueryFilter, out []ShapeHandle) int {
|
|
if len(out) == 0 || !finite2(c) || !finite2(half) || !finite(angle) || half.X <= 0 || half.Y <= 0 {
|
|
return 0
|
|
}
|
|
q := &w.q
|
|
q.probe = convex{count: 4}
|
|
q.probe.v[0] = Vec2{X: -half.X, Y: -half.Y}
|
|
q.probe.v[1] = Vec2{X: half.X, Y: -half.Y}
|
|
q.probe.v[2] = Vec2{X: half.X, Y: half.Y}
|
|
q.probe.v[3] = Vec2{X: -half.X, Y: half.Y}
|
|
q.probe.computeNormals()
|
|
q.probeXf = xform{p: c, q: makeRot(angle)}
|
|
return w.overlapProbe(f, out)
|
|
}
|
|
|
|
func (w *World) ShapeAt(p Vec2, f QueryFilter) (ShapeHandle, bool) {
|
|
if !finite2(p) {
|
|
return ShapeHandle{}, false
|
|
}
|
|
q := &w.q
|
|
q.filter, q.point, q.picked = f, p, false
|
|
w.tree.query(AABB{Min: p, Max: p}, q.atFn)
|
|
return q.pick, q.picked
|
|
}
|
|
|
|
func rayConvex(c *convex, p, d Vec2, maxT float32) (float32, Vec2, bool) {
|
|
switch c.count {
|
|
case 1:
|
|
return rayCircle(c.v[0], c.radius, p, d, maxT)
|
|
case 2:
|
|
if c.radius == 0 {
|
|
return raySegment(c.v[0], c.v[1], p, d, maxT, false)
|
|
}
|
|
return rayCapsule(c.v[0], c.v[1], c.radius, p, d, maxT)
|
|
}
|
|
return rayPolygon(c, p, d, maxT)
|
|
}
|
|
|
|
func rayCircle(center Vec2, r float32, p, d Vec2, maxT float32) (float32, Vec2, bool) {
|
|
s := p.Sub(center)
|
|
b := s.Dot(s) - r*r
|
|
if b < 0 {
|
|
return 0, Vec2{}, false
|
|
}
|
|
c := s.Dot(d)
|
|
sigma := c*c - b
|
|
if sigma < 0 || c > 0 {
|
|
return 0, Vec2{}, false
|
|
}
|
|
t := -c - sqrtf(sigma)
|
|
if t < 0 || t > maxT {
|
|
return 0, Vec2{}, false
|
|
}
|
|
n, _ := normalizeLen(s.Add(d.Scale(t)))
|
|
return t, n, true
|
|
}
|
|
|
|
func raySegment(v1, v2, p, d Vec2, maxT float32, oneSided bool) (float32, Vec2, bool) {
|
|
e := v2.Sub(v1)
|
|
en, l := normalizeLen(e)
|
|
if l == 0 {
|
|
return 0, Vec2{}, false
|
|
}
|
|
n := rightPerp(en)
|
|
num := n.Dot(v1.Sub(p))
|
|
den := n.Dot(d)
|
|
if den == 0 || (oneSided && num > 0) {
|
|
return 0, Vec2{}, false
|
|
}
|
|
t := num / den
|
|
if t < 0 || t > maxT {
|
|
return 0, Vec2{}, false
|
|
}
|
|
q := p.Add(d.Scale(t))
|
|
s := q.Sub(v1).Dot(e) / e.Dot(e)
|
|
if s < 0 || s > 1 {
|
|
return 0, Vec2{}, false
|
|
}
|
|
if num > 0 {
|
|
n = n.Neg()
|
|
}
|
|
return t, n, true
|
|
}
|
|
|
|
func distSqToSegment(p, a, b Vec2) float32 {
|
|
e := b.Sub(a)
|
|
t := float32(0)
|
|
if l := e.LenSq(); l > 0 {
|
|
t = clampf(p.Sub(a).Dot(e)/l, 0, 1)
|
|
}
|
|
return p.Sub(a.Add(e.Scale(t))).LenSq()
|
|
}
|
|
|
|
func rayCapsule(v0, v1 Vec2, r float32, p, d Vec2, maxT float32) (float32, Vec2, bool) {
|
|
if distSqToSegment(p, v0, v1) < r*r {
|
|
return 0, Vec2{}, false
|
|
}
|
|
e, _ := normalizeLen(v1.Sub(v0))
|
|
nn := rightPerp(e).Scale(r)
|
|
best := maxT
|
|
var bn Vec2
|
|
found := false
|
|
try := func(t float32, n Vec2, hit bool) {
|
|
if hit && t <= best {
|
|
best, bn, found = t, n, true
|
|
}
|
|
}
|
|
try(raySegment(v0.Add(nn), v1.Add(nn), p, d, best, true))
|
|
try(raySegment(v1.Sub(nn), v0.Sub(nn), p, d, best, true))
|
|
try(rayCircle(v0, r, p, d, best))
|
|
try(rayCircle(v1, r, p, d, best))
|
|
return best, bn, found
|
|
}
|
|
|
|
func rayPolygon(c *convex, p, d Vec2, maxT float32) (float32, Vec2, bool) {
|
|
lower, upper := float32(0), maxT
|
|
index := -1
|
|
for i := 0; i < c.count; i++ {
|
|
num := c.n[i].Dot(c.v[i].Sub(p))
|
|
den := c.n[i].Dot(d)
|
|
switch {
|
|
case den == 0:
|
|
if num < 0 {
|
|
return 0, Vec2{}, false
|
|
}
|
|
case den < 0 && num < lower*den:
|
|
lower = num / den
|
|
index = i
|
|
case den > 0 && num < upper*den:
|
|
upper = num / den
|
|
}
|
|
if upper < lower {
|
|
return 0, Vec2{}, false
|
|
}
|
|
}
|
|
if index < 0 {
|
|
return 0, Vec2{}, false
|
|
}
|
|
return lower, c.n[index], true
|
|
}
|
|
|
|
func pointInConvex(c *convex, p Vec2) bool {
|
|
switch c.count {
|
|
case 1:
|
|
return p.Sub(c.v[0]).LenSq() <= c.radius*c.radius
|
|
case 2:
|
|
return distSqToSegment(p, c.v[0], c.v[1]) <= c.radius*c.radius
|
|
}
|
|
for i := 0; i < c.count; i++ {
|
|
if c.n[i].Dot(p.Sub(c.v[i])) > c.radius {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|