202 lines
6.2 KiB
Go
202 lines
6.2 KiB
Go
package stdlib
|
|
|
|
import (
|
|
"aego/contracts/physics2d"
|
|
"aego/core/mathx"
|
|
"aego/engine"
|
|
"aego/modules/physics/systems"
|
|
"aego/scene"
|
|
"aego/script/bind"
|
|
"aego/script/vm"
|
|
)
|
|
|
|
type Lib struct {
|
|
contact bind.Packer[Contact]
|
|
hits []physics2d.RayHit
|
|
nodes []scene.NodeHandle
|
|
}
|
|
|
|
func (l *Lib) Contact(w []vm.Value) Contact {
|
|
return l.contact.Get(w)
|
|
}
|
|
|
|
func (l *Lib) ContactSlots() int {
|
|
return l.contact.Slots()
|
|
}
|
|
|
|
func simOf(c *bind.Ctx) *systems.Sim {
|
|
if c.World == nil {
|
|
return nil
|
|
}
|
|
s, _ := engine.ServiceOf[*systems.Sim](c.World, systems.ServiceID)
|
|
return s
|
|
}
|
|
|
|
func need(c *bind.Ctx) *systems.Sim {
|
|
s := simOf(c)
|
|
if s == nil {
|
|
c.Fail("physics is not running in this world, queries work only during Play")
|
|
}
|
|
return s
|
|
}
|
|
|
|
func fail(c *bind.Ctx, err error) {
|
|
if err != nil {
|
|
c.Fail(err.Error())
|
|
}
|
|
}
|
|
|
|
func defaultFilter(c *bind.Ctx) physics2d.QueryFilter {
|
|
return physics2d.QueryFilter{LayerMask: ^uint32(0), ExcludeNode: c.Node}
|
|
}
|
|
|
|
func toHit(h physics2d.RayHit, ok bool) RayHit {
|
|
if !ok {
|
|
return RayHit{}
|
|
}
|
|
return RayHit{Hit: true, Point: h.Point, Normal: h.Normal, Distance: h.Distance, Node: h.Node, Collider: h.ColliderNode}
|
|
}
|
|
|
|
func (l *Lib) hitBuf(n int) []physics2d.RayHit {
|
|
if cap(l.hits) < n {
|
|
l.hits = make([]physics2d.RayHit, n)
|
|
}
|
|
return l.hits[:n]
|
|
}
|
|
|
|
func (l *Lib) nodeBuf(n int) []scene.NodeHandle {
|
|
if cap(l.nodes) < n {
|
|
l.nodes = make([]scene.NodeHandle, n)
|
|
}
|
|
return l.nodes[:n]
|
|
}
|
|
|
|
func copyNodes(out bind.Out[scene.NodeHandle], src []scene.NodeHandle) int {
|
|
for i, n := range src {
|
|
out.Set(i, n)
|
|
}
|
|
return len(src)
|
|
}
|
|
|
|
func Install(r *bind.Registry) *Lib {
|
|
m := r.Module(ModPhysics)
|
|
bind.Struct[Contact](m, "Contact",
|
|
bind.Doc("Contact describes a touch between two colliders from the receiver's side.\nNormal points from the other body into this one, RelativeVelocity is this body's velocity relative to the other at Point."))
|
|
bind.Struct[RayHit](m, "RayHit",
|
|
bind.Doc("RayHit is the result of a ray query. Hit is false when nothing was found; Node is the body, Collider the node of the shape that was hit."))
|
|
pk, err := bind.NewPacker[Contact](r)
|
|
if err != nil {
|
|
r.Fail(err)
|
|
return nil
|
|
}
|
|
l := &Lib{contact: pk}
|
|
|
|
bind.Func3(m, "Raycast", func(c *bind.Ctx, origin, dir mathx.Vec2, maxDist float32) RayHit {
|
|
s := need(c)
|
|
if s == nil {
|
|
return RayHit{}
|
|
}
|
|
return toHit(s.Raycast(origin, dir, maxDist, defaultFilter(c)))
|
|
}, bind.Doc("Raycast returns the closest hit along dir within maxDist. The caller's own body is ignored, triggers are skipped."),
|
|
bind.Params("origin", "dir", "maxDist"),
|
|
bind.Example("hit := physics.Raycast(pos, Vec2{Y: 1}, 0.6)\nif hit.Hit {\n\tgrounded = true\n}"))
|
|
|
|
bind.Func3(m, "Linecast", func(c *bind.Ctx, from, to mathx.Vec2, mask int) RayHit {
|
|
s := need(c)
|
|
d := to.Sub(from)
|
|
if s == nil || d.LenSq() == 0 {
|
|
return RayHit{}
|
|
}
|
|
f := defaultFilter(c)
|
|
f.LayerMask = uint32(mask)
|
|
return toHit(s.Raycast(from, d, d.Len(), f))
|
|
}, bind.Doc("Linecast returns the first hit between from and to on the layers in mask; -1 means every layer."),
|
|
bind.Params("from", "to", "mask"),
|
|
bind.Example("hit := physics.Linecast(eye, target, physics.LayerMask(\"World\"))"))
|
|
|
|
bind.Func3(m, "RaycastAll", func(c *bind.Ctx, from, to mathx.Vec2, out bind.Out[RayHit]) int {
|
|
s := need(c)
|
|
d := to.Sub(from)
|
|
if s == nil || d.LenSq() == 0 || out.Cap() == 0 {
|
|
return 0
|
|
}
|
|
buf := l.hitBuf(out.Cap())
|
|
n := s.RaycastAll(from, d, d.Len(), defaultFilter(c), buf)
|
|
for i := 0; i < n; i++ {
|
|
out.Set(i, toHit(buf[i], true))
|
|
}
|
|
return n
|
|
}, bind.Doc("RaycastAll fills out with hits between from and to, nearest first, and returns how many it wrote. It writes at most len(out)."),
|
|
bind.Params("from", "to", "out"))
|
|
|
|
bind.Func2(m, "OverlapPoint", func(c *bind.Ctx, p mathx.Vec2, out bind.Out[scene.NodeHandle]) int {
|
|
s := need(c)
|
|
if s == nil || out.Cap() == 0 {
|
|
return 0
|
|
}
|
|
buf := l.nodeBuf(out.Cap())
|
|
return copyNodes(out, buf[:s.OverlapPoint(p, defaultFilter(c), buf)])
|
|
}, bind.Doc("OverlapPoint writes the bodies whose colliders contain p into out and returns the count."),
|
|
bind.Params("point", "out"))
|
|
|
|
bind.Func3(m, "OverlapCircle", func(c *bind.Ctx, center mathx.Vec2, radius float32, out bind.Out[scene.NodeHandle]) int {
|
|
s := need(c)
|
|
if s == nil || out.Cap() == 0 {
|
|
return 0
|
|
}
|
|
buf := l.nodeBuf(out.Cap())
|
|
return copyNodes(out, buf[:s.OverlapCircle(center, radius, defaultFilter(c), buf)])
|
|
}, bind.Doc("OverlapCircle writes the bodies touching the circle into out and returns the count."),
|
|
bind.Params("center", "radius", "out"))
|
|
|
|
bind.Func3(m, "OverlapBox", func(c *bind.Ctx, center, size mathx.Vec2, out bind.Out[scene.NodeHandle]) int {
|
|
s := need(c)
|
|
if s == nil || out.Cap() == 0 {
|
|
return 0
|
|
}
|
|
buf := l.nodeBuf(out.Cap())
|
|
return copyNodes(out, buf[:s.OverlapBox(center, size.Scale(0.5), 0, defaultFilter(c), buf)])
|
|
}, bind.Doc("OverlapBox writes the bodies touching the axis-aligned box into out and returns the count."),
|
|
bind.Params("center", "size", "out"))
|
|
|
|
bind.Func0(m, "Gravity", func(c *bind.Ctx) mathx.Vec2 {
|
|
if s := need(c); s != nil {
|
|
return s.World().Gravity()
|
|
}
|
|
return mathx.Vec2{}
|
|
}, bind.Doc("Gravity returns the world gravity in units per second squared. +Y points down."))
|
|
|
|
bind.Proc1(m, "SetGravity", func(c *bind.Ctx, g mathx.Vec2) {
|
|
if s := need(c); s != nil {
|
|
fail(c, s.World().SetGravity(g))
|
|
}
|
|
}, bind.Doc("SetGravity changes the world gravity for the rest of the Play session."), bind.Params("gravity"))
|
|
|
|
bind.Func1(m, "Layer", func(c *bind.Ctx, name string) int {
|
|
s := need(c)
|
|
if s == nil {
|
|
return -1
|
|
}
|
|
i, ok := s.LayerIndex(name)
|
|
if !ok {
|
|
c.Failf("unknown physics layer %q, declare it in Project Settings → Physics", name)
|
|
}
|
|
return i
|
|
}, bind.Doc("Layer returns the index of a named physics layer."), bind.Params("name"))
|
|
|
|
bind.Func1(m, "LayerMask", func(c *bind.Ctx, name string) int {
|
|
s := need(c)
|
|
if s == nil {
|
|
return 0
|
|
}
|
|
i, ok := s.LayerIndex(name)
|
|
if !ok {
|
|
c.Failf("unknown physics layer %q, declare it in Project Settings → Physics", name)
|
|
return 0
|
|
}
|
|
return 1 << i
|
|
}, bind.Doc("LayerMask returns the mask bit of a named layer. Add masks of different layers to combine them."),
|
|
bind.Params("name"), bind.Example("mask := physics.LayerMask(\"World\") + physics.LayerMask(\"Enemies\")"))
|
|
return l
|
|
}
|