215 lines
5.3 KiB
Go
215 lines
5.3 KiB
Go
package physics2d
|
|
|
|
import (
|
|
"strconv"
|
|
"time"
|
|
|
|
"aego/core/mathx"
|
|
"aego/demos"
|
|
"aego/demos/internal/app"
|
|
"aego/engine"
|
|
"aego/modules/physics/world"
|
|
"aego/platform/input"
|
|
"aego/render"
|
|
renderhost "aego/render/host"
|
|
"aego/render/view"
|
|
"aego/ui/core"
|
|
"aego/ui/widgets"
|
|
)
|
|
|
|
func init() {
|
|
demos.Register("physics2d", func() demos.Demo { return New(false) })
|
|
demos.Register("physics2d-headless", func() demos.Demo { return New(true) })
|
|
}
|
|
|
|
var presets = [...]world.DebugFlags{
|
|
world.DrawShapes | world.DrawJoints,
|
|
world.DrawShapes | world.DrawJoints | world.DrawContacts,
|
|
world.DrawShapes | world.DrawJoints | world.DrawAABBs,
|
|
world.DrawShapes | world.DrawJoints | world.DrawIslands | world.DrawVelocities,
|
|
world.DrawAll,
|
|
}
|
|
|
|
type Demo struct {
|
|
app.Base
|
|
headless bool
|
|
mod *renderhost.Module
|
|
w *world.World
|
|
cam view.Camera2D
|
|
preset int
|
|
paused bool
|
|
stepOnce bool
|
|
mouse mathx.Vec2
|
|
stepTime time.Duration
|
|
steps int
|
|
failure string
|
|
}
|
|
|
|
func New(headless bool) *Demo {
|
|
return &Demo{headless: headless, cam: view.Camera2D{Zoom: 20, Position: mathx.Vec2{X: -5, Y: -12}}}
|
|
}
|
|
|
|
func (d *Demo) Name() string {
|
|
if d.headless {
|
|
return "physics2d-headless"
|
|
}
|
|
return "physics2d"
|
|
}
|
|
|
|
func (d *Demo) Configure(c *engine.Config) {
|
|
if !d.headless {
|
|
return
|
|
}
|
|
c.Headless = true
|
|
if c.HeadlessFrameTime <= 0 {
|
|
c.HeadlessFrameTime = 1.0 / 60
|
|
}
|
|
}
|
|
|
|
func (d *Demo) Module() *renderhost.Module {
|
|
if d.mod == nil {
|
|
d.mod = renderhost.New(render.Options{MaxDebugVerts: 1 << 18})
|
|
}
|
|
return d.mod
|
|
}
|
|
|
|
func (d *Demo) Init(e *engine.Engine) error {
|
|
d.Attach(e, d.Module())
|
|
return d.reset()
|
|
}
|
|
|
|
func (d *Demo) reset() error {
|
|
w, err := buildScene()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
d.w, d.steps, d.failure = w, 0, ""
|
|
return nil
|
|
}
|
|
|
|
func (d *Demo) FixedUpdate(dt float64) {
|
|
if d.paused && !d.stepOnce {
|
|
return
|
|
}
|
|
start := time.Now()
|
|
d.w.Step(float32(dt))
|
|
d.stepTime = time.Since(start)
|
|
d.steps++
|
|
d.stepOnce = false
|
|
}
|
|
|
|
func (d *Demo) Update(dt float64) {
|
|
d.ToggleOverlay()
|
|
in := d.Engine.Input()
|
|
switch {
|
|
case in.KeyPressed(input.KeyEscape):
|
|
d.Engine.Quit()
|
|
case in.KeyPressed(input.KeyD):
|
|
d.preset = (d.preset + 1) % len(presets)
|
|
case in.KeyPressed(input.KeyR):
|
|
if err := d.reset(); err != nil {
|
|
d.failure = err.Error()
|
|
}
|
|
case in.KeyPressed(input.KeyP):
|
|
d.paused = !d.paused
|
|
case in.KeyPressed(input.KeySpace) && d.paused:
|
|
d.stepOnce = true
|
|
}
|
|
if w := in.Wheel().Y; w != 0 && !d.UI.WantsMouse() {
|
|
d.cam.Zoom = mathx.Clamp(d.cam.Zoom*(1+w*0.1), 4, 200)
|
|
}
|
|
speed := float32(dt) * 400 / d.cam.Zoom
|
|
if in.KeyDown(input.KeyLeft) {
|
|
d.cam.Position.X -= speed
|
|
}
|
|
if in.KeyDown(input.KeyRight) {
|
|
d.cam.Position.X += speed
|
|
}
|
|
if in.KeyDown(input.KeyUp) {
|
|
d.cam.Position.Y -= speed
|
|
}
|
|
if in.KeyDown(input.KeyDown) {
|
|
d.cam.Position.Y += speed
|
|
}
|
|
if d.UI.WantsMouse() {
|
|
return
|
|
}
|
|
if in.ButtonPressed(input.MouseLeft) {
|
|
d.kick()
|
|
}
|
|
if in.ButtonPressed(input.MouseRight) {
|
|
d.spawn()
|
|
}
|
|
}
|
|
|
|
func (d *Demo) kick() {
|
|
sh, ok := d.w.ShapeAt(d.mouse, world.DefaultQueryFilter())
|
|
if !ok {
|
|
return
|
|
}
|
|
b, _ := d.w.ShapeBody(sh)
|
|
if t, _ := d.w.BodyType(b); t != world.Dynamic {
|
|
return
|
|
}
|
|
mass, _, _, _ := d.w.BodyMass(b)
|
|
_ = d.w.ApplyImpulseAtPoint(b, mathx.Vec2{Y: -8 * mass}, d.mouse)
|
|
}
|
|
|
|
func (d *Demo) spawn() {
|
|
def := world.DefaultBodyDef()
|
|
def.Position = d.mouse
|
|
b, err := d.w.CreateBody(def)
|
|
if err == nil {
|
|
_, err = d.w.AddShape(b, world.DefaultShapeDef(world.Box{Half: mathx.Vec2{X: 0.4, Y: 0.4}}))
|
|
}
|
|
if err != nil {
|
|
d.failure = err.Error()
|
|
}
|
|
}
|
|
|
|
func (d *Demo) Render(float64) {
|
|
v := d.WorldView()
|
|
v.Camera = d.cam
|
|
f := d.Renderer.View(v)
|
|
dbg := f.Debug()
|
|
d.w.DebugDraw(dbg, presets[d.preset])
|
|
d.mouse = f.View().ScreenToWorld(d.Engine.Input().MousePos().Scale(d.UI.Scale()))
|
|
origin := mathx.Vec2{X: d.cam.Position.X, Y: d.cam.Position.Y - 18}
|
|
dir := d.mouse.Sub(origin)
|
|
if hit, ok := d.w.Raycast(origin, dir, 200, world.DefaultQueryFilter()); ok {
|
|
dbg.Line(origin, hit.Point, mathx.Color{R: 1, G: 0.3, B: 0.3, A: 1})
|
|
dbg.Line(hit.Point, hit.Point.Add(hit.Normal.Scale(1.5)), mathx.Color{R: 0.3, G: 1, B: 0.4, A: 1})
|
|
dbg.Cross(hit.Point, 0.4, mathx.Color{R: 1, G: 1, B: 1, A: 1})
|
|
} else {
|
|
dbg.Line(origin, origin.Add(dir.Normalized().Scale(200)), mathx.Color{R: 0.5, G: 0.5, B: 0.5, A: 0.6})
|
|
}
|
|
f.End()
|
|
d.BeginUI()
|
|
d.DrawOverlay(d.overlay)
|
|
d.FlushUI()
|
|
}
|
|
|
|
func (d *Demo) overlay(c *core.Context) {
|
|
s := d.w.Stats()
|
|
widgets.LabelDim(c, "step "+strconv.FormatFloat(float64(d.stepTime.Microseconds())/1000, 'f', 2, 64)+" ms #"+strconv.Itoa(d.steps))
|
|
widgets.LabelDim(c, "bodies "+strconv.Itoa(s.Bodies)+" active "+strconv.Itoa(s.Active)+" sleeping "+strconv.Itoa(s.Sleeping))
|
|
widgets.LabelDim(c, "pairs "+strconv.Itoa(s.TouchingPairs)+" points "+strconv.Itoa(s.ContactPoints)+" islands "+strconv.Itoa(s.Islands))
|
|
widgets.LabelDim(c, "LMB kick RMB box D layers P pause Space step R reset")
|
|
if d.paused {
|
|
widgets.LabelDim(c, "paused")
|
|
}
|
|
if d.failure != "" {
|
|
widgets.LabelDim(c, d.failure)
|
|
}
|
|
}
|
|
|
|
func (d *Demo) ReportMetrics(dst []demos.Metric) []demos.Metric {
|
|
s := d.w.Stats()
|
|
return append(dst,
|
|
demos.Metric{Name: "physics.step_ms", Value: float64(d.stepTime.Microseconds()) / 1000},
|
|
demos.Metric{Name: "physics.active", Value: float64(s.Active)},
|
|
demos.Metric{Name: "physics.pairs", Value: float64(s.TouchingPairs)},
|
|
demos.Metric{Name: "physics.islands", Value: float64(s.Islands)},
|
|
)
|
|
}
|