Major update to implement basic collision

This commit is contained in:
Tom
2020-09-16 20:16:47 -07:00
parent eec9d30795
commit 70bb24a7fb
9 changed files with 501 additions and 54 deletions

128
bot/phy/aabb.go Normal file
View File

@ -0,0 +1,128 @@
package phy
import (
"math"
"github.com/Tnze/go-mc/bot/world"
)
type MinMax struct {
Min, Max float64
}
func (mm MinMax) Extend(delta float64) MinMax {
if delta < 0 {
return MinMax{
Min: mm.Min + delta,
Max: mm.Max,
}
}
return MinMax{
Min: mm.Min,
Max: mm.Max + delta,
}
}
func (mm MinMax) Contract(amt float64) MinMax {
return MinMax{
Min: mm.Min + amt,
Max: mm.Max - amt,
}
}
func (mm MinMax) Expand(amt float64) MinMax {
return MinMax{
Min: mm.Min - amt,
Max: mm.Max + amt,
}
}
func (mm MinMax) Offset(amt float64) MinMax {
return MinMax{
Min: mm.Min + amt,
Max: mm.Max + amt,
}
}
// AABB implements Axis Aligned Bounding Box operations.
type AABB struct {
X, Y, Z MinMax
Block world.BlockStatus
}
func (bb AABB) Extend(dx, dy, dz float64) AABB {
return AABB{
X: bb.X.Extend(dx),
Y: bb.Y.Extend(dx),
Z: bb.Z.Extend(dx),
Block: bb.Block,
}
}
func (bb AABB) Contract(x, y, z float64) AABB {
return AABB{
X: bb.X.Contract(x),
Y: bb.Y.Contract(y),
Z: bb.Z.Contract(z),
Block: bb.Block,
}
}
func (bb AABB) Expand(x, y, z float64) AABB {
return AABB{
X: bb.X.Expand(x),
Y: bb.Y.Expand(y),
Z: bb.Z.Expand(z),
Block: bb.Block,
}
}
func (bb AABB) Offset(x, y, z float64) AABB {
return AABB{
X: bb.X.Offset(x),
Y: bb.Y.Offset(y),
Z: bb.Z.Offset(z),
Block: bb.Block,
}
}
func (bb AABB) XOffset(o AABB, xOffset float64) float64 {
if o.Y.Max > bb.Y.Min && o.Y.Min < bb.Y.Max && o.Z.Max > bb.Z.Min && o.Z.Min < bb.Z.Max {
if xOffset > 0.0 && o.X.Max <= bb.X.Min {
xOffset = math.Min(bb.X.Min-o.X.Max, xOffset)
} else if xOffset < 0.0 && o.X.Min >= bb.X.Max {
xOffset = math.Max(bb.X.Max-o.X.Min, xOffset)
}
}
return xOffset
}
func (bb AABB) YOffset(o AABB, yOffset float64) float64 {
if o.X.Max > bb.X.Min && o.X.Min < bb.X.Max && o.Z.Max > bb.Z.Min && o.Z.Min < bb.Z.Max {
if yOffset > 0.0 && o.Y.Max <= bb.Y.Min {
yOffset = math.Min(bb.Y.Min-o.Y.Max, yOffset)
} else if yOffset < 0.0 && o.Y.Min >= bb.Y.Max {
yOffset = math.Max(bb.Y.Max-o.Y.Min, yOffset)
}
}
return yOffset
}
func (bb AABB) ZOffset(o AABB, zOffset float64) float64 {
if o.X.Max > bb.X.Min && o.X.Min < bb.X.Max && o.Y.Max > bb.Y.Min && o.Y.Min < bb.Y.Max {
if zOffset > 0.0 && o.Z.Max <= bb.Z.Min {
zOffset = math.Min(bb.Z.Min-o.Z.Max, zOffset)
} else if zOffset < 0.0 && o.Z.Min >= bb.Z.Max {
zOffset = math.Max(bb.Z.Max-o.Z.Min, zOffset)
}
}
return zOffset
}
func (bb AABB) Intersects(o AABB) bool {
return true &&
bb.X.Min < o.X.Max && bb.X.Max > o.X.Min &&
bb.Y.Min < o.Y.Max && bb.Y.Max > o.Y.Min &&
bb.Z.Min < o.Z.Max && bb.Z.Max > o.Z.Min
}

145
bot/phy/phy.go Normal file
View File

@ -0,0 +1,145 @@
// Package phy implements a minimal physics simulation necessary for realistic
// bot behavior.
package phy
import (
"math"
"github.com/Tnze/go-mc/bot/world"
"github.com/Tnze/go-mc/bot/world/entity/player"
)
const (
playerWidth = 0.6
playerHeight = 1.8
resetVel = 0.003
gravity = 0.08
)
// World represents a provider of information about the surrounding world.
type World interface {
GetBlockStatus(x, y, z int) world.BlockStatus
}
// Surrounds represents the blocks surrounding the player (Y, Z, X).
type Surrounds []AABB
// Point represents a point in 3D space.
type Point struct {
X, Y, Z float64
}
// State tracks physics state.
type State struct {
// player state.
Pos Point
Vel Point
Yaw, Pitch float64
// player state flags.
onGround bool
collision struct {
vertical bool
horizontal bool
}
Run bool
}
func (s *State) ServerPositionUpdate(player player.Pos, w World) error {
s.Pos = Point{X: player.X, Y: player.Y, Z: player.Z}
s.Yaw, s.Pitch = float64(player.Yaw), float64(player.Pitch)
s.Vel = Point{}
s.onGround, s.collision.vertical, s.collision.horizontal = false, false, false
s.Run = true
return nil
}
func abs(i1, i2 int) int {
if i1 < i2 {
return i2 - i1
}
return i1 - i2
}
func (s *State) surroundings(query AABB, w World) Surrounds {
minY, maxY := int(math.Floor(query.Y.Min))-1, int(math.Floor(query.Y.Max))+1
minZ, maxZ := int(math.Floor(query.Z.Min)), int(math.Floor(query.Z.Max))+1
minX, maxX := int(math.Floor(query.X.Min)), int(math.Floor(query.X.Max))+1
out := Surrounds(make([]AABB, 0, abs(maxY, minY)*abs(maxZ, minZ)*abs(maxX, minX)))
for y := minY; y < maxY; y++ {
for z := minZ; z < maxZ; z++ {
for x := minX; x < maxX; x++ {
if block := w.GetBlockStatus(x, y, z); block > 0 {
out = append(out, AABB{X: MinMax{Max: 1}, Y: MinMax{Max: 1}, Z: MinMax{Max: 1}, Block: block}.Offset(float64(x), float64(y), float64(z)))
}
}
}
}
return out
}
func (s *State) Tick(w World) error {
if !s.Run {
return nil
}
if math.Abs(s.Vel.X) < resetVel {
s.Vel.X = 0
}
if math.Abs(s.Vel.Y) < resetVel {
s.Vel.Y = 0
}
if math.Abs(s.Vel.Z) < resetVel {
s.Vel.Z = 0
}
s.Vel.Y -= gravity
// Apply collision.
var (
player = s.BB()
query = player.Extend(s.Vel.X, s.Vel.Y, s.Vel.Z)
surroundings = s.surroundings(query, w)
newVel = s.Vel
)
for _, b := range surroundings {
newVel.Y = b.YOffset(player, newVel.Y)
}
player = player.Offset(0, newVel.Y, 0)
for _, b := range surroundings {
newVel.X = b.XOffset(player, newVel.X)
}
player = player.Offset(newVel.X, 0, 0)
for _, b := range surroundings {
newVel.Z = b.ZOffset(player, newVel.Z)
}
player = player.Offset(0, 0, newVel.Z)
// Update flags.
s.Pos.X = player.X.Min + playerWidth/2
s.Pos.Y = player.Y.Min
s.Pos.Z = player.Z.Min + playerWidth/2
s.collision.horizontal = newVel.X != s.Vel.X || newVel.Z != s.Vel.Z
s.collision.vertical = newVel.Y != s.Vel.Y
s.onGround = s.collision.vertical && s.Vel.Y < 0
s.Vel = newVel
return nil
}
func (s *State) BB() AABB {
return AABB{
X: MinMax{Min: -playerWidth / 2, Max: playerWidth / 2},
Y: MinMax{Max: playerHeight},
Z: MinMax{Min: -playerWidth / 2, Max: playerWidth / 2},
}.Offset(s.Pos.X, s.Pos.Y, s.Pos.Z)
}
func (s *State) Position() player.Pos {
return player.Pos{
X: s.Pos.X, Y: s.Pos.Y, Z: s.Pos.Z,
Yaw: float32(s.Yaw), Pitch: float32(s.Pitch),
OnGround: s.onGround,
}
}