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  }