github.com/df-mc/dragonfly@v0.9.13/server/entity/movement.go (about) 1 package entity 2 3 import ( 4 "github.com/df-mc/dragonfly/server/block/cube" 5 "github.com/df-mc/dragonfly/server/world" 6 "github.com/go-gl/mathgl/mgl64" 7 "math" 8 ) 9 10 // MovementComputer is used to compute movement of an entity. When constructed, the Gravity of the entity 11 // the movement is computed for must be passed. 12 type MovementComputer struct { 13 Gravity, Drag float64 14 DragBeforeGravity bool 15 16 onGround bool 17 } 18 19 // Movement represents the movement of a world.Entity as a result of a call to MovementComputer.TickMovement. The 20 // resulting position and velocity can be obtained by calling Position and Velocity. These can be sent to viewers by 21 // calling Send. 22 type Movement struct { 23 v []world.Viewer 24 e world.Entity 25 pos, vel, dpos, dvel mgl64.Vec3 26 rot cube.Rotation 27 onGround bool 28 } 29 30 // Send sends the Movement to any viewers watching the entity at the time of the movement. If the position/velocity 31 // changes were negligible, nothing is sent. 32 func (m *Movement) Send() { 33 posChanged := !m.dpos.ApproxEqualThreshold(zeroVec3, epsilon) 34 velChanged := !m.dvel.ApproxEqualThreshold(zeroVec3, epsilon) 35 36 for _, v := range m.v { 37 if posChanged { 38 v.ViewEntityMovement(m.e, m.pos, m.rot, m.onGround) 39 } 40 if velChanged { 41 v.ViewEntityVelocity(m.e, m.vel) 42 } 43 } 44 } 45 46 // Position returns the position as a result of the Movement as an mgl64.Vec3. 47 func (m *Movement) Position() mgl64.Vec3 { 48 return m.pos 49 } 50 51 // Velocity returns the velocity after the Movement as an mgl64.Vec3. 52 func (m *Movement) Velocity() mgl64.Vec3 { 53 return m.vel 54 } 55 56 // Rotation returns the rotation, yaw and pitch, of the entity after the Movement. 57 func (m *Movement) Rotation() cube.Rotation { 58 return m.rot 59 } 60 61 // TickMovement performs a movement tick on an entity. Velocity is applied and changed according to the values 62 // of its Drag and Gravity. 63 // The new position of the entity after movement is returned. 64 // The resulting Movement can be sent to viewers by calling Movement.Send. 65 func (c *MovementComputer) TickMovement(e world.Entity, pos, vel mgl64.Vec3, rot cube.Rotation) *Movement { 66 w := e.World() 67 viewers := w.Viewers(pos) 68 69 velBefore := vel 70 vel = c.applyHorizontalForces(w, pos, c.applyVerticalForces(vel)) 71 dPos, vel := c.checkCollision(e, pos, vel) 72 73 return &Movement{v: viewers, e: e, 74 pos: pos.Add(dPos), vel: vel, dpos: dPos, dvel: vel.Sub(velBefore), 75 rot: rot, onGround: c.onGround, 76 } 77 } 78 79 // OnGround checks if the entity that this computer calculates is currently on the ground. 80 func (c *MovementComputer) OnGround() bool { 81 return c.onGround 82 } 83 84 // zeroVec3 is a mgl64.Vec3 with zero values. 85 var zeroVec3 mgl64.Vec3 86 87 // epsilon is the epsilon used for thresholds for change used for change in position and velocity. 88 const epsilon = 0.001 89 90 // applyVerticalForces applies gravity and drag on the Y axis, based on the Gravity and Drag values set. 91 func (c *MovementComputer) applyVerticalForces(vel mgl64.Vec3) mgl64.Vec3 { 92 if c.DragBeforeGravity { 93 vel[1] *= 1 - c.Drag 94 } 95 vel[1] -= c.Gravity 96 if !c.DragBeforeGravity { 97 vel[1] *= 1 - c.Drag 98 } 99 return vel 100 } 101 102 // applyHorizontalForces applies friction to the velocity based on the Drag value, reducing it on the X and Z axes. 103 func (c *MovementComputer) applyHorizontalForces(w *world.World, pos, vel mgl64.Vec3) mgl64.Vec3 { 104 friction := 1 - c.Drag 105 if c.onGround { 106 if f, ok := w.Block(cube.PosFromVec3(pos).Side(cube.FaceDown)).(interface { 107 Friction() float64 108 }); ok { 109 friction *= f.Friction() 110 } else { 111 friction *= 0.6 112 } 113 } 114 vel[0] *= friction 115 vel[2] *= friction 116 return vel 117 } 118 119 // checkCollision handles the collision of the entity with blocks, adapting the velocity of the entity if it 120 // happens to collide with a block. 121 // The final velocity and the Vec3 that the entity should move is returned. 122 func (c *MovementComputer) checkCollision(e world.Entity, pos, vel mgl64.Vec3) (mgl64.Vec3, mgl64.Vec3) { 123 // TODO: Implement collision with other entities. 124 deltaX, deltaY, deltaZ := vel[0], vel[1], vel[2] 125 126 // Entities only ever have a single bounding box. 127 entityBBox := e.Type().BBox(e).Translate(pos) 128 blocks := blockBBoxsAround(e, entityBBox.Extend(vel)) 129 130 if !mgl64.FloatEqualThreshold(deltaY, 0, epsilon) { 131 // First we move the entity BBox on the Y axis. 132 for _, blockBBox := range blocks { 133 deltaY = entityBBox.YOffset(blockBBox, deltaY) 134 } 135 entityBBox = entityBBox.Translate(mgl64.Vec3{0, deltaY}) 136 } 137 if !mgl64.FloatEqualThreshold(deltaX, 0, epsilon) { 138 // Then on the X axis. 139 for _, blockBBox := range blocks { 140 deltaX = entityBBox.XOffset(blockBBox, deltaX) 141 } 142 entityBBox = entityBBox.Translate(mgl64.Vec3{deltaX}) 143 } 144 if !mgl64.FloatEqualThreshold(deltaZ, 0, epsilon) { 145 // And finally on the Z axis. 146 for _, blockBBox := range blocks { 147 deltaZ = entityBBox.ZOffset(blockBBox, deltaZ) 148 } 149 } 150 if !mgl64.FloatEqual(vel[1], 0) { 151 // The Y velocity of the entity is currently not 0, meaning it is moving either up or down. We can 152 // then assume the entity is not currently on the ground. 153 c.onGround = false 154 } 155 if !mgl64.FloatEqual(deltaX, vel[0]) { 156 vel[0] = 0 157 } 158 if !mgl64.FloatEqual(deltaY, vel[1]) { 159 // The entity either hit the ground or hit the ceiling. 160 if vel[1] < 0 { 161 // The entity was going down, so we can assume it is now on the ground. 162 c.onGround = true 163 } 164 vel[1] = 0 165 } 166 if !mgl64.FloatEqual(deltaZ, vel[2]) { 167 vel[2] = 0 168 } 169 return mgl64.Vec3{deltaX, deltaY, deltaZ}, vel 170 } 171 172 // blockBBoxsAround returns all blocks around the entity passed, using the BBox passed to make a prediction of 173 // what blocks need to have their BBox returned. 174 func blockBBoxsAround(e world.Entity, box cube.BBox) []cube.BBox { 175 w := e.World() 176 grown := box.Grow(0.25) 177 min, max := grown.Min(), grown.Max() 178 minX, minY, minZ := int(math.Floor(min[0])), int(math.Floor(min[1])), int(math.Floor(min[2])) 179 maxX, maxY, maxZ := int(math.Ceil(max[0])), int(math.Ceil(max[1])), int(math.Ceil(max[2])) 180 181 // A prediction of one BBox per block, plus an additional 2, in case 182 blockBBoxs := make([]cube.BBox, 0, (maxX-minX)*(maxY-minY)*(maxZ-minZ)+2) 183 for y := minY; y <= maxY; y++ { 184 for x := minX; x <= maxX; x++ { 185 for z := minZ; z <= maxZ; z++ { 186 pos := cube.Pos{x, y, z} 187 boxes := w.Block(pos).Model().BBox(pos, w) 188 for _, box := range boxes { 189 blockBBoxs = append(blockBBoxs, box.Translate(mgl64.Vec3{float64(x), float64(y), float64(z)})) 190 } 191 } 192 } 193 } 194 return blockBBoxs 195 }