up3
Build and deploy / Build (push) Has been cancelled
Build and deploy / Update Contributors (push) Has been cancelled
Build and deploy / Deploy (push) Has been cancelled
Build and deploy / Build (push) Has been cancelled
Build and deploy / Update Contributors (push) Has been cancelled
Build and deploy / Deploy (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
package cube
|
||||
|
||||
import "github.com/go-gl/mathgl/mgl64"
|
||||
|
||||
// Axis represents the axis that a block, such as a log, may be directed in.
|
||||
type Axis int
|
||||
|
||||
const (
|
||||
// Y represents the vertical Y axis.
|
||||
Y Axis = iota
|
||||
// Z represents the horizontal Z axis.
|
||||
Z
|
||||
// X represents the horizontal X axis.
|
||||
X
|
||||
)
|
||||
|
||||
// String converts an Axis into either x, y or z, depending on which axis it is.
|
||||
func (a Axis) String() string {
|
||||
switch a {
|
||||
case X:
|
||||
return "x"
|
||||
case Y:
|
||||
return "y"
|
||||
default:
|
||||
return "z"
|
||||
}
|
||||
}
|
||||
|
||||
// RotateLeft rotates an Axis from X to Z or from Z to X.
|
||||
func (a Axis) RotateLeft() Axis {
|
||||
switch a {
|
||||
case X:
|
||||
return Z
|
||||
case Z:
|
||||
return X
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// RotateRight rotates an Axis from X to Z or from Z to X.
|
||||
func (a Axis) RotateRight() Axis {
|
||||
// No difference in rotating left or right for an Axis.
|
||||
return a.RotateLeft()
|
||||
}
|
||||
|
||||
// Vec3 returns a unit Vec3 of either (1, 0, 0), (0, 1, 0) or (0, 0, 1),
|
||||
// depending on the Axis.
|
||||
func (a Axis) Vec3() mgl64.Vec3 {
|
||||
switch a {
|
||||
case X:
|
||||
return mgl64.Vec3{1, 0, 0}
|
||||
case Y:
|
||||
return mgl64.Vec3{0, 1, 0}
|
||||
default:
|
||||
return mgl64.Vec3{0, 0, 1}
|
||||
}
|
||||
}
|
||||
|
||||
// Axes return all possible axes. (x, y, z)
|
||||
func Axes() []Axis {
|
||||
return []Axis{X, Y, Z}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
package cube
|
||||
|
||||
import (
|
||||
"github.com/go-gl/mathgl/mgl64"
|
||||
)
|
||||
|
||||
// BBox represents an Axis Aligned Bounding Box in a 3D space. It is defined as
|
||||
// two Vec3s, of which one is the minimum and one is the maximum.
|
||||
type BBox struct {
|
||||
min, max mgl64.Vec3
|
||||
}
|
||||
|
||||
// Box creates a new axis aligned bounding box with the minimum and maximum
|
||||
// coordinates provided. The returned box has minimum and maximum coordinates
|
||||
// swapped if necessary so that it is well-formed.
|
||||
func Box(x0, y0, z0, x1, y1, z1 float64) BBox {
|
||||
if x0 > x1 {
|
||||
x0, x1 = x1, x0
|
||||
}
|
||||
if y0 > y1 {
|
||||
y0, y1 = y1, y0
|
||||
}
|
||||
if z0 > z1 {
|
||||
z0, z1 = z1, z0
|
||||
}
|
||||
return BBox{min: mgl64.Vec3{x0, y0, z0}, max: mgl64.Vec3{x1, y1, z1}}
|
||||
}
|
||||
|
||||
// Grow grows the bounding box in all directions by x and returns the new
|
||||
// bounding box.
|
||||
func (box BBox) Grow(x float64) BBox {
|
||||
add := mgl64.Vec3{x, x, x}
|
||||
return BBox{min: box.min.Sub(add), max: box.max.Add(add)}
|
||||
}
|
||||
|
||||
// GrowVec3 grows the BBox on all axes as represented by the Vec3 passed. The
|
||||
// vec values are subtracted from the minimum values of the BBox and added to
|
||||
// the maximum values of the BBox.
|
||||
func (box BBox) GrowVec3(vec mgl64.Vec3) BBox {
|
||||
return BBox{min: box.min.Sub(vec), max: box.max.Add(vec)}
|
||||
}
|
||||
|
||||
// Min returns the minimum coordinate of the bounding box.
|
||||
func (box BBox) Min() mgl64.Vec3 {
|
||||
return box.min
|
||||
}
|
||||
|
||||
// Max returns the maximum coordinate of the bounding box.
|
||||
func (box BBox) Max() mgl64.Vec3 {
|
||||
return box.max
|
||||
}
|
||||
|
||||
// Width returns the width of the BBox.
|
||||
func (box BBox) Width() float64 {
|
||||
return box.max[0] - box.min[0]
|
||||
}
|
||||
|
||||
// Length returns the length of the BBox.
|
||||
func (box BBox) Length() float64 {
|
||||
return box.max[2] - box.min[2]
|
||||
}
|
||||
|
||||
// Height returns the height of the BBox.
|
||||
func (box BBox) Height() float64 {
|
||||
return box.max[1] - box.min[1]
|
||||
}
|
||||
|
||||
// Extend expands the BBox on all axes as represented by the Vec3 passed.
|
||||
// Negative coordinates result in an expansion towards the negative axis, and
|
||||
// vice versa for positive coordinates.
|
||||
func (box BBox) Extend(vec mgl64.Vec3) BBox {
|
||||
if vec[0] < 0 {
|
||||
box.min[0] += vec[0]
|
||||
} else if vec[0] > 0 {
|
||||
box.max[0] += vec[0]
|
||||
}
|
||||
if vec[1] < 0 {
|
||||
box.min[1] += vec[1]
|
||||
} else if vec[1] > 0 {
|
||||
box.max[1] += vec[1]
|
||||
}
|
||||
if vec[2] < 0 {
|
||||
box.min[2] += vec[2]
|
||||
} else if vec[2] > 0 {
|
||||
box.max[2] += vec[2]
|
||||
}
|
||||
return box
|
||||
}
|
||||
|
||||
// ExtendTowards extends the bounding box by x in a given direction.
|
||||
func (box BBox) ExtendTowards(f Face, x float64) BBox {
|
||||
switch f {
|
||||
case FaceDown:
|
||||
box.min[1] -= x
|
||||
case FaceUp:
|
||||
box.max[1] += x
|
||||
case FaceNorth:
|
||||
box.min[2] -= x
|
||||
case FaceSouth:
|
||||
box.max[2] += x
|
||||
case FaceWest:
|
||||
box.min[0] -= x
|
||||
case FaceEast:
|
||||
box.max[0] += x
|
||||
}
|
||||
return box
|
||||
}
|
||||
|
||||
// Stretch stretches the bounding box by x in a given axis.
|
||||
func (box BBox) Stretch(a Axis, x float64) BBox {
|
||||
switch a {
|
||||
case Y:
|
||||
box.min[1] -= x
|
||||
box.max[1] += x
|
||||
case Z:
|
||||
box.min[2] -= x
|
||||
box.max[2] += x
|
||||
case X:
|
||||
box.min[0] -= x
|
||||
box.max[0] += x
|
||||
}
|
||||
return box
|
||||
}
|
||||
|
||||
// Translate moves the entire BBox with the Vec3 given. The (minimum and
|
||||
// maximum) x, y and z coordinates are moved by those in the Vec3 passed.
|
||||
func (box BBox) Translate(vec mgl64.Vec3) BBox {
|
||||
return BBox{min: box.min.Add(vec), max: box.max.Add(vec)}
|
||||
}
|
||||
|
||||
// TranslateTowards moves the entire BBox by x in the direction of a Face f.
|
||||
func (box BBox) TranslateTowards(f Face, x float64) BBox {
|
||||
switch f {
|
||||
case FaceDown:
|
||||
return box.Translate(mgl64.Vec3{0, -x, 0})
|
||||
case FaceUp:
|
||||
return box.Translate(mgl64.Vec3{0, x, 0})
|
||||
case FaceNorth:
|
||||
return box.Translate(mgl64.Vec3{0, 0, -x})
|
||||
case FaceSouth:
|
||||
return box.Translate(mgl64.Vec3{0, 0, x})
|
||||
case FaceWest:
|
||||
return box.Translate(mgl64.Vec3{-x, 0, 0})
|
||||
case FaceEast:
|
||||
return box.Translate(mgl64.Vec3{x, 0, 0})
|
||||
}
|
||||
return box
|
||||
}
|
||||
|
||||
// IntersectsWith checks if the BBox intersects with another BBox.
|
||||
func (box BBox) IntersectsWith(other BBox) bool {
|
||||
return box.intersectsWith(other, 1e-5)
|
||||
}
|
||||
|
||||
// intersectsWith checks if the BBox intersects with another BBox using a
|
||||
// specific epsilon.
|
||||
func (box BBox) intersectsWith(other BBox, epsilon float64) bool {
|
||||
if other.max[0]-box.min[0] > epsilon && box.max[0]-other.min[0] > epsilon {
|
||||
if other.max[1]-box.min[1] > epsilon && box.max[1]-other.min[1] > epsilon {
|
||||
return other.max[2]-box.min[2] > epsilon && box.max[2]-other.min[2] > epsilon
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AnyIntersections checks if any of boxes intersect with search.
|
||||
func AnyIntersections(boxes []BBox, search BBox) bool {
|
||||
for _, box := range boxes {
|
||||
if box.intersectsWith(search, 0) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Vec3Within checks if a BBox has vec within it.
|
||||
func (box BBox) Vec3Within(vec mgl64.Vec3) bool {
|
||||
if vec[0] <= box.min[0] || vec[0] >= box.max[0] {
|
||||
return false
|
||||
}
|
||||
if vec[2] <= box.min[2] || vec[2] >= box.max[2] {
|
||||
return false
|
||||
}
|
||||
return vec[1] > box.min[1] && vec[1] < box.max[1]
|
||||
}
|
||||
|
||||
// Vec3WithinYZ checks if a BBox has vec within its Y and Z bounds.
|
||||
func (box BBox) Vec3WithinYZ(vec mgl64.Vec3) bool {
|
||||
if vec[2] < box.min[2] || vec[2] > box.max[2] {
|
||||
return false
|
||||
}
|
||||
return vec[1] >= box.min[1] && vec[1] <= box.max[1]
|
||||
}
|
||||
|
||||
// Vec3WithinXZ checks if a BBox has vec within its X and Z bounds.
|
||||
func (box BBox) Vec3WithinXZ(vec mgl64.Vec3) bool {
|
||||
if vec[0] < box.min[0] || vec[0] > box.max[0] {
|
||||
return false
|
||||
}
|
||||
return vec[2] >= box.min[2] && vec[2] <= box.max[2]
|
||||
}
|
||||
|
||||
// Vec3WithinXY checks if a BBox has vec within its X and Y bounds.
|
||||
func (box BBox) Vec3WithinXY(vec mgl64.Vec3) bool {
|
||||
if vec[0] < box.min[0] || vec[0] > box.max[0] {
|
||||
return false
|
||||
}
|
||||
return vec[1] >= box.min[1] && vec[1] <= box.max[1]
|
||||
}
|
||||
|
||||
// XOffset calculates the offset on the X axis between two bounding boxes,
|
||||
// returning a delta always smaller than or equal to deltaX if deltaX is bigger
|
||||
// than 0, or always bigger than or equal to deltaX if it is smaller than 0.
|
||||
func (box BBox) XOffset(nearby BBox, deltaX float64) float64 {
|
||||
if box.max[1] <= nearby.min[1] || box.min[1] >= nearby.max[1] || box.max[2] <= nearby.min[2] || box.min[2] >= nearby.max[2] {
|
||||
// Not in the same Y/Z plane.
|
||||
return deltaX
|
||||
}
|
||||
if deltaX > 0 && box.max[0] <= nearby.min[0] {
|
||||
deltaX = min(deltaX, nearby.min[0]-box.max[0])
|
||||
} else if deltaX < 0 && box.min[0] >= nearby.max[0] {
|
||||
deltaX = max(deltaX, nearby.max[0]-box.min[0])
|
||||
}
|
||||
return deltaX
|
||||
}
|
||||
|
||||
// YOffset calculates the offset on the Y axis between two bounding boxes,
|
||||
// returning a delta always smaller than or equal to deltaY if deltaY is bigger
|
||||
// than 0, or always bigger than or equal to deltaY if it is smaller than 0.
|
||||
func (box BBox) YOffset(nearby BBox, deltaY float64) float64 {
|
||||
if box.max[0] <= nearby.min[0] || box.min[0] >= nearby.max[0] || box.max[2] <= nearby.min[2] || box.min[2] >= nearby.max[2] {
|
||||
// Not the same X/Z plane.
|
||||
return deltaY
|
||||
}
|
||||
if deltaY > 0 && box.max[1] <= nearby.min[1] {
|
||||
deltaY = min(deltaY, nearby.min[1]-box.max[1])
|
||||
}
|
||||
if deltaY < 0 && box.min[1] >= nearby.max[1] {
|
||||
deltaY = max(deltaY, nearby.max[1]-box.min[1])
|
||||
}
|
||||
return deltaY
|
||||
}
|
||||
|
||||
// ZOffset calculates the offset on the Z axis between two bounding boxes,
|
||||
// returning a delta always smaller than or equal to deltaZ if deltaZ is bigger
|
||||
// than 0, or always bigger than or equal to deltaZ if it is smaller than 0.
|
||||
func (box BBox) ZOffset(nearby BBox, deltaZ float64) float64 {
|
||||
if box.max[0] <= nearby.min[0] || box.min[0] >= nearby.max[0] || box.max[1] <= nearby.min[1] || box.min[1] >= nearby.max[1] {
|
||||
// Not the same X/Y plane.
|
||||
return deltaZ
|
||||
}
|
||||
if deltaZ > 0 && box.max[2] <= nearby.min[2] {
|
||||
deltaZ = min(deltaZ, nearby.min[2]-box.max[2])
|
||||
}
|
||||
if deltaZ < 0 && box.min[2] >= nearby.max[2] {
|
||||
deltaZ = max(deltaZ, nearby.max[2]-box.min[2])
|
||||
}
|
||||
return deltaZ
|
||||
}
|
||||
|
||||
// Corners returns the positions of all corners of a BBox.
|
||||
func (box BBox) Corners() []mgl64.Vec3 {
|
||||
bbmin, bbmax := box.min, box.max
|
||||
return []mgl64.Vec3{
|
||||
box.min,
|
||||
box.max,
|
||||
{bbmin[0], bbmin[1], bbmax[2]},
|
||||
{bbmin[0], bbmax[1], bbmin[2]},
|
||||
{bbmin[0], bbmax[1], bbmax[2]},
|
||||
{bbmax[0], bbmax[1], bbmin[2]},
|
||||
{bbmax[0], bbmin[1], bbmax[2]},
|
||||
{bbmax[0], bbmin[1], bbmin[2]},
|
||||
}
|
||||
}
|
||||
|
||||
// Mul performs a scalar multiplication of the min and max points of a BBox.
|
||||
func (box BBox) Mul(val float64) BBox {
|
||||
return BBox{min: box.min.Mul(val), max: box.max.Mul(val)}
|
||||
}
|
||||
|
||||
// Volume calculates the volume of a BBox.
|
||||
func (box BBox) Volume() float64 {
|
||||
return box.Height() * box.Length() * box.Width()
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package cube
|
||||
|
||||
// Direction represents a direction towards one of the horizontal axes
|
||||
type Direction int
|
||||
|
||||
const (
|
||||
// North represents the north direction, towards the negative Z.
|
||||
North Direction = iota
|
||||
// South represents the south direction, towards the positive Z.
|
||||
South
|
||||
// West represents the west direction, towards the negative X.
|
||||
West
|
||||
// East represents the east direction, towards the positive X.
|
||||
East
|
||||
)
|
||||
|
||||
// Face converts the direction to a Face and returns it.
|
||||
func (d Direction) Face() Face {
|
||||
return Face(d + 2)
|
||||
}
|
||||
|
||||
// Opposite returns Direction opposite to the current one.
|
||||
func (d Direction) Opposite() Direction {
|
||||
switch d {
|
||||
case North:
|
||||
return South
|
||||
case South:
|
||||
return North
|
||||
case West:
|
||||
return East
|
||||
case East:
|
||||
return West
|
||||
}
|
||||
panic("invalid direction")
|
||||
}
|
||||
|
||||
// RotateRight rotates the direction 90 degrees to the right horizontally
|
||||
// (clockwise) and returns the new direction.
|
||||
func (d Direction) RotateRight() Direction {
|
||||
switch d {
|
||||
case North:
|
||||
return East
|
||||
case East:
|
||||
return South
|
||||
case South:
|
||||
return West
|
||||
case West:
|
||||
return North
|
||||
}
|
||||
panic("invalid direction")
|
||||
}
|
||||
|
||||
// RotateLeft rotates the direction 90 degrees to the left horizontally
|
||||
// (counter-clockwise) and returns the new direction.
|
||||
func (d Direction) RotateLeft() Direction {
|
||||
switch d {
|
||||
case North:
|
||||
return West
|
||||
case East:
|
||||
return North
|
||||
case South:
|
||||
return East
|
||||
case West:
|
||||
return South
|
||||
}
|
||||
panic("invalid direction")
|
||||
}
|
||||
|
||||
// String returns the Direction as a string.
|
||||
func (d Direction) String() string {
|
||||
switch d {
|
||||
case North:
|
||||
return "north"
|
||||
case East:
|
||||
return "east"
|
||||
case South:
|
||||
return "south"
|
||||
case West:
|
||||
return "west"
|
||||
}
|
||||
panic("invalid direction")
|
||||
}
|
||||
|
||||
var directions = [...]Direction{North, East, South, West}
|
||||
|
||||
// Directions returns a list of all directions, going from North to West.
|
||||
func Directions() []Direction {
|
||||
return directions[:]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
// Package cube provides types and functions to handle positions and rotations
|
||||
// of voxel-based objects in a 3D world.
|
||||
package cube
|
||||
@@ -0,0 +1,124 @@
|
||||
package cube
|
||||
|
||||
const (
|
||||
// FaceDown represents the bottom face of a block.
|
||||
FaceDown Face = iota
|
||||
// FaceUp represents the top face of a block.
|
||||
FaceUp
|
||||
// FaceNorth represents the north face of a block.
|
||||
FaceNorth
|
||||
// FaceSouth represents the south face of a block.
|
||||
FaceSouth
|
||||
// FaceWest represents the west face of the block.
|
||||
FaceWest
|
||||
// FaceEast represents the east face of the block.
|
||||
FaceEast
|
||||
)
|
||||
|
||||
// Face represents the face of a block or entity.
|
||||
type Face int
|
||||
|
||||
// Direction converts the Face to a Direction and returns it, assuming the Face
|
||||
// is horizontal and not FaceUp or FaceDown.
|
||||
func (f Face) Direction() Direction {
|
||||
return Direction(f - 2)
|
||||
}
|
||||
|
||||
// Opposite returns the opposite Face. FaceDown will return FaceUp, FaceNorth
|
||||
// will return FaceSouth, FaceWest will return FaceEast, and vice versa.
|
||||
func (f Face) Opposite() Face {
|
||||
switch f {
|
||||
default:
|
||||
return FaceUp
|
||||
case FaceUp:
|
||||
return FaceDown
|
||||
case FaceNorth:
|
||||
return FaceSouth
|
||||
case FaceSouth:
|
||||
return FaceNorth
|
||||
case FaceWest:
|
||||
return FaceEast
|
||||
case FaceEast:
|
||||
return FaceWest
|
||||
}
|
||||
}
|
||||
|
||||
// Axis returns the Axis the face is facing. FaceEast and FaceWest correspond
|
||||
// to the X Axis, FaceNorth and FaceSouth to the Z Axis and FaceUp and FaceDown
|
||||
// to the Y Axis.
|
||||
func (f Face) Axis() Axis {
|
||||
switch f {
|
||||
default:
|
||||
return Y
|
||||
case FaceEast, FaceWest:
|
||||
return X
|
||||
case FaceNorth, FaceSouth:
|
||||
return Z
|
||||
}
|
||||
}
|
||||
|
||||
// RotateRight rotates the face 90 degrees to the right horizontally
|
||||
// (clockwise) and returns the new Face.
|
||||
func (f Face) RotateRight() Face {
|
||||
switch f {
|
||||
case FaceNorth:
|
||||
return FaceEast
|
||||
case FaceEast:
|
||||
return FaceSouth
|
||||
case FaceSouth:
|
||||
return FaceWest
|
||||
case FaceWest:
|
||||
return FaceNorth
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// RotateLeft rotates the face 90 degrees to the left horizontally
|
||||
// (counter-clockwise) and returns the new Face.
|
||||
func (f Face) RotateLeft() Face {
|
||||
switch f {
|
||||
case FaceNorth:
|
||||
return FaceWest
|
||||
case FaceEast:
|
||||
return FaceNorth
|
||||
case FaceSouth:
|
||||
return FaceEast
|
||||
case FaceWest:
|
||||
return FaceSouth
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// String returns the Face as a string.
|
||||
func (f Face) String() string {
|
||||
switch f {
|
||||
case FaceUp:
|
||||
return "up"
|
||||
case FaceDown:
|
||||
return "down"
|
||||
case FaceNorth:
|
||||
return "north"
|
||||
case FaceSouth:
|
||||
return "south"
|
||||
case FaceWest:
|
||||
return "west"
|
||||
case FaceEast:
|
||||
return "east"
|
||||
}
|
||||
panic("invalid face")
|
||||
}
|
||||
|
||||
// Faces returns a list of all faces, starting with down, then up, then north
|
||||
// to west.
|
||||
func Faces() []Face {
|
||||
return faces[:]
|
||||
}
|
||||
|
||||
// HorizontalFaces returns a list of all horizontal faces, from north to west.
|
||||
func HorizontalFaces() []Face {
|
||||
return hFaces[:]
|
||||
}
|
||||
|
||||
var hFaces = [...]Face{FaceNorth, FaceEast, FaceSouth, FaceWest}
|
||||
|
||||
var faces = [...]Face{FaceDown, FaceUp, FaceNorth, FaceEast, FaceSouth, FaceWest}
|
||||
@@ -0,0 +1,33 @@
|
||||
package cube
|
||||
|
||||
import "math"
|
||||
|
||||
// Orientation represents the rotation of a block divided into 16 options.
|
||||
type Orientation int
|
||||
|
||||
// OrientationFromYaw returns an Orientation value that (roughly) matches the
|
||||
// yaw passed.
|
||||
func OrientationFromYaw(yaw float64) Orientation {
|
||||
yaw = math.Mod(yaw, 360)
|
||||
return Orientation(math.Round(yaw / 360 * 16))
|
||||
}
|
||||
|
||||
// Yaw returns the yaw value that matches the orientation.
|
||||
func (o Orientation) Yaw() float64 {
|
||||
return float64(o) / 16 * 360
|
||||
}
|
||||
|
||||
// Opposite returns the opposite orientation value of the Orientation.
|
||||
func (o Orientation) Opposite() Orientation {
|
||||
return OrientationFromYaw(o.Yaw() + 180)
|
||||
}
|
||||
|
||||
// RotateLeft rotates the orientation left by 90 degrees and returns it.
|
||||
func (o Orientation) RotateLeft() Orientation {
|
||||
return OrientationFromYaw(o.Yaw() - 90)
|
||||
}
|
||||
|
||||
// RotateRight rotates the orientation right by 90 degrees and returns it.
|
||||
func (o Orientation) RotateRight() Orientation {
|
||||
return OrientationFromYaw(o.Yaw() + 90)
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
package cube
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"iter"
|
||||
"math"
|
||||
|
||||
"github.com/go-gl/mathgl/mgl64"
|
||||
)
|
||||
|
||||
// Pos holds the position of a block. The position is represented as an array
|
||||
// with an x, y and z value, where the y value is positive.
|
||||
type Pos [3]int
|
||||
|
||||
// String converts the Pos to a string in the format (1,2,3) and returns it.
|
||||
func (p Pos) String() string {
|
||||
return fmt.Sprintf("(%v,%v,%v)", p[0], p[1], p[2])
|
||||
}
|
||||
|
||||
// X returns the X coordinate of the block position.
|
||||
func (p Pos) X() int {
|
||||
return p[0]
|
||||
}
|
||||
|
||||
// Y returns the Y coordinate of the block position.
|
||||
func (p Pos) Y() int {
|
||||
return p[1]
|
||||
}
|
||||
|
||||
// Z returns the Z coordinate of the block position.
|
||||
func (p Pos) Z() int {
|
||||
return p[2]
|
||||
}
|
||||
|
||||
// OutOfBounds checks if the Y value is either bigger than r[1] or smaller than
|
||||
// r[0].
|
||||
func (p Pos) OutOfBounds(r Range) bool {
|
||||
y := p[1]
|
||||
return y > r[1] || y < r[0]
|
||||
}
|
||||
|
||||
// Within reports if the position lies within the inclusive minimum and maximum bounds passed.
|
||||
func (p Pos) Within(min, max Pos) bool {
|
||||
return p[0] >= min[0] && p[0] <= max[0] &&
|
||||
p[1] >= min[1] && p[1] <= max[1] &&
|
||||
p[2] >= min[2] && p[2] <= max[2]
|
||||
}
|
||||
|
||||
// Add adds two positions together and returns a new combined one.
|
||||
func (p Pos) Add(pos Pos) Pos {
|
||||
return Pos{p[0] + pos[0], p[1] + pos[1], p[2] + pos[2]}
|
||||
}
|
||||
|
||||
// Sub subtracts pos from p and returns a new one with the subtracted values.
|
||||
func (p Pos) Sub(pos Pos) Pos {
|
||||
return Pos{p[0] - pos[0], p[1] - pos[1], p[2] - pos[2]}
|
||||
}
|
||||
|
||||
// Vec3 returns a vec3 holding the same coordinates as the block position.
|
||||
func (p Pos) Vec3() mgl64.Vec3 {
|
||||
return mgl64.Vec3{float64(p[0]), float64(p[1]), float64(p[2])}
|
||||
}
|
||||
|
||||
// Vec3Middle returns a Vec3 holding the coordinates of the block position with
|
||||
// 0.5 added on both horizontal axes.
|
||||
func (p Pos) Vec3Middle() mgl64.Vec3 {
|
||||
return mgl64.Vec3{float64(p[0]) + 0.5, float64(p[1]), float64(p[2]) + 0.5}
|
||||
}
|
||||
|
||||
// Vec3Centre returns a Vec3 holding the coordinates of the block position with
|
||||
// 0.5 added on all axes.
|
||||
func (p Pos) Vec3Centre() mgl64.Vec3 {
|
||||
return mgl64.Vec3{float64(p[0]) + 0.5, float64(p[1]) + 0.5, float64(p[2]) + 0.5}
|
||||
}
|
||||
|
||||
// Side returns the position on the side of this block position, at a specific
|
||||
// face.
|
||||
func (p Pos) Side(face Face) Pos {
|
||||
switch face {
|
||||
case FaceUp:
|
||||
p[1]++
|
||||
case FaceDown:
|
||||
p[1]--
|
||||
case FaceNorth:
|
||||
p[2]--
|
||||
case FaceSouth:
|
||||
p[2]++
|
||||
case FaceWest:
|
||||
p[0]--
|
||||
case FaceEast:
|
||||
p[0]++
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// Face returns the face that the other Pos was on compared to the current Pos.
|
||||
// The other Pos is assumed to be a direct neighbour of the current Pos.
|
||||
func (p Pos) Face(other Pos) Face {
|
||||
switch other {
|
||||
case p.Add(Pos{0, 1}):
|
||||
return FaceUp
|
||||
case p.Add(Pos{0, -1}):
|
||||
return FaceDown
|
||||
case p.Add(Pos{0, 0, -1}):
|
||||
return FaceNorth
|
||||
case p.Add(Pos{0, 0, 1}):
|
||||
return FaceSouth
|
||||
case p.Add(Pos{-1, 0, 0}):
|
||||
return FaceWest
|
||||
case p.Add(Pos{1, 0, 0}):
|
||||
return FaceEast
|
||||
}
|
||||
return FaceUp
|
||||
}
|
||||
|
||||
// Neighbours calls the function passed for each of the block position's
|
||||
// neighbours. If the Y value is out of bounds, the function will not be called
|
||||
// for that position.
|
||||
func (p Pos) Neighbours(f func(neighbour Pos), r Range) {
|
||||
if p.OutOfBounds(r) {
|
||||
return
|
||||
}
|
||||
p[0]++
|
||||
f(p)
|
||||
p[0] -= 2
|
||||
f(p)
|
||||
p[0]++
|
||||
p[1]++
|
||||
if p[1] <= r[1] {
|
||||
f(p)
|
||||
}
|
||||
p[1] -= 2
|
||||
if p[1] >= r[0] {
|
||||
f(p)
|
||||
}
|
||||
p[1]++
|
||||
p[2]++
|
||||
f(p)
|
||||
p[2] -= 2
|
||||
f(p)
|
||||
}
|
||||
|
||||
// PosFromVec3 returns a block position by a Vec3, rounding the values down
|
||||
// adequately.
|
||||
func PosFromVec3(vec3 mgl64.Vec3) Pos {
|
||||
return Pos{int(math.Floor(vec3[0])), int(math.Floor(vec3[1])), int(math.Floor(vec3[2]))}
|
||||
}
|
||||
|
||||
// Min returns a new position where each coordinate is the minimum
|
||||
// of input positions p1 and p2.
|
||||
func Min(p1, p2 Pos) Pos {
|
||||
return Pos{min(p1[0], p2[0]), min(p1[1], p2[1]), min(p1[2], p2[2])}
|
||||
}
|
||||
|
||||
// Max returns a new position where each coordinate is the maximum
|
||||
// of input positions p1 and p2.
|
||||
func Max(p1, p2 Pos) Pos {
|
||||
return Pos{max(p1[0], p2[0]), max(p1[1], p2[1]), max(p1[2], p2[2])}
|
||||
}
|
||||
|
||||
// Range3D returns iterator that iterates all points between minimum and maximum of p1 & p2.
|
||||
func Range3D(p1, p2 Pos) iter.Seq[Pos] {
|
||||
max := Max(p1, p2)
|
||||
min := Min(p1, p2)
|
||||
return func(yield func(Pos) bool) {
|
||||
for x := min[0]; x <= max[0]; x++ {
|
||||
for y := min[1]; y <= max[1]; y++ {
|
||||
for z := min[2]; z <= max[2]; z++ {
|
||||
if !yield(Pos{x, y, z}) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cube
|
||||
|
||||
// Range represents the height range of a Dimension in blocks. The first value
|
||||
// of the Range holds the minimum Y value, the second value holds the maximum Y
|
||||
// value.
|
||||
type Range [2]int
|
||||
|
||||
// Min returns the minimum Y value of a Range. It is equivalent to Range[0].
|
||||
func (r Range) Min() int {
|
||||
return r[0]
|
||||
}
|
||||
|
||||
// Max returns the maximum Y value of a Range. It is equivalent to Range[1].
|
||||
func (r Range) Max() int {
|
||||
return r[1]
|
||||
}
|
||||
|
||||
// Height returns the total height of the Range, the difference between Max and
|
||||
// Min.
|
||||
func (r Range) Height() int {
|
||||
return r[1] - r[0]
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package cube
|
||||
|
||||
import (
|
||||
"github.com/go-gl/mathgl/mgl64"
|
||||
"math"
|
||||
)
|
||||
|
||||
// Rotation describes the rotation of an object in the world in degrees. It
|
||||
// holds a yaw (r[0]) and pitch value (r[1]). Yaw is in the range (-180, 180)
|
||||
// while pitch is in the range (-90, 90). A positive pitch implies an entity is
|
||||
// looking downwards, while a negative pitch implies it is looking upwards.
|
||||
type Rotation [2]float64
|
||||
|
||||
// Yaw returns the yaw of r (r[0]).
|
||||
func (r Rotation) Yaw() float64 {
|
||||
return r[0]
|
||||
}
|
||||
|
||||
// Pitch returns the pitch of r (r[1]).
|
||||
func (r Rotation) Pitch() float64 {
|
||||
return r[1]
|
||||
}
|
||||
|
||||
// Elem extracts the elements of the Rotation for direct value assignment.
|
||||
func (r Rotation) Elem() (yaw, pitch float64) {
|
||||
return r[0], r[1]
|
||||
}
|
||||
|
||||
// Add adds the values of two Rotations element-wise and returns a new Rotation.
|
||||
// If the yaw or pitch would otherwise exceed their respective range as
|
||||
// described, they are 'overflown' to the other end of the allowed range.
|
||||
func (r Rotation) Add(r2 Rotation) Rotation {
|
||||
return Rotation{r[0] + r2[0], r[1] + r2[1]}.fix()
|
||||
}
|
||||
|
||||
// Opposite returns the Rotation opposite r, so that
|
||||
// r.Vec3().Add(r.Opposite().Vec3()).Len() is equal to 0.
|
||||
func (r Rotation) Opposite() Rotation {
|
||||
fixed := r.fix()
|
||||
return Rotation{fixed[0] + 180, -fixed[1]}.fix()
|
||||
}
|
||||
|
||||
// Neg returns the negation of the Rotation. It is equivalent to creating a new
|
||||
// Rotation{-r[0], -r[1]}.
|
||||
func (r Rotation) Neg() Rotation {
|
||||
fixed := r.fix()
|
||||
return Rotation{-fixed[0], -fixed[1]}
|
||||
}
|
||||
|
||||
// Direction returns the horizontal Direction that r points towards based on the
|
||||
// yaw of r.
|
||||
func (r Rotation) Direction() Direction {
|
||||
yaw := r.fix().Yaw()
|
||||
switch {
|
||||
case yaw > 45 && yaw <= 135:
|
||||
return West
|
||||
case yaw > -45 && yaw <= 45:
|
||||
return South
|
||||
case yaw > -135 && yaw <= -45:
|
||||
return East
|
||||
case yaw <= -135 || yaw > 135:
|
||||
return North
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Orientation returns an Orientation value that most closely matches the yaw
|
||||
// of r.
|
||||
func (r Rotation) Orientation() Orientation {
|
||||
const step = 360 / 16.0
|
||||
|
||||
yaw := r.fix().Yaw()
|
||||
if yaw < -step/2 {
|
||||
yaw += 360
|
||||
}
|
||||
return Orientation(math.Round(yaw / step))
|
||||
}
|
||||
|
||||
// Vec3 returns the direction vector of r. The length of the mgl64.Vec3 returned
|
||||
// is always 1.
|
||||
func (r Rotation) Vec3() mgl64.Vec3 {
|
||||
yaw, pitch := r.fix().Elem()
|
||||
yawRad, pitchRad := mgl64.DegToRad(yaw), mgl64.DegToRad(pitch)
|
||||
|
||||
m := math.Cos(pitchRad)
|
||||
return mgl64.Vec3{
|
||||
-m * math.Sin(yawRad),
|
||||
-math.Sin(pitchRad),
|
||||
m * math.Cos(yawRad),
|
||||
}
|
||||
}
|
||||
|
||||
// fix 'overflows' the Rotation's values to make sure they are within the range
|
||||
// as described above.
|
||||
func (r Rotation) fix() Rotation {
|
||||
signYaw, signPitch := math.Copysign(180, r[0]), math.Copysign(90, r[1])
|
||||
return Rotation{
|
||||
math.Mod(r[0]+signYaw, 360) - signYaw,
|
||||
math.Mod(r[1]+signPitch, 180) - signPitch,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"github.com/df-mc/dragonfly/server/block/cube"
|
||||
"github.com/go-gl/mathgl/mgl64"
|
||||
"math"
|
||||
)
|
||||
|
||||
// BBoxResult is the result of a basic ray trace collision with a bounding box.
|
||||
type BBoxResult struct {
|
||||
bb cube.BBox
|
||||
pos mgl64.Vec3
|
||||
face cube.Face
|
||||
}
|
||||
|
||||
// BBox ...
|
||||
func (r BBoxResult) BBox() cube.BBox {
|
||||
return r.bb
|
||||
}
|
||||
|
||||
// Position ...
|
||||
func (r BBoxResult) Position() mgl64.Vec3 {
|
||||
return r.pos
|
||||
}
|
||||
|
||||
// Face ...
|
||||
func (r BBoxResult) Face() cube.Face {
|
||||
return r.face
|
||||
}
|
||||
|
||||
// BBoxIntercept performs a ray trace and calculates the point on the BBox's edge nearest to the start position that the ray trace
|
||||
// collided with.
|
||||
// BBoxIntercept returns a BBoxResult with the colliding vector closest to the start position, if no colliding point was found,
|
||||
// a zero BBoxResult is returned and ok is false.
|
||||
func BBoxIntercept(bb cube.BBox, start, end mgl64.Vec3) (result BBoxResult, ok bool) {
|
||||
min, max := bb.Min(), bb.Max()
|
||||
v1 := vec3OnLineWithX(start, end, min[0])
|
||||
v2 := vec3OnLineWithX(start, end, max[0])
|
||||
v3 := vec3OnLineWithY(start, end, min[1])
|
||||
v4 := vec3OnLineWithY(start, end, max[1])
|
||||
v5 := vec3OnLineWithZ(start, end, min[2])
|
||||
v6 := vec3OnLineWithZ(start, end, max[2])
|
||||
|
||||
if v1 != nil && !bb.Vec3WithinYZ(*v1) {
|
||||
v1 = nil
|
||||
}
|
||||
if v2 != nil && !bb.Vec3WithinYZ(*v2) {
|
||||
v2 = nil
|
||||
}
|
||||
if v3 != nil && !bb.Vec3WithinXZ(*v3) {
|
||||
v3 = nil
|
||||
}
|
||||
if v4 != nil && !bb.Vec3WithinXZ(*v4) {
|
||||
v4 = nil
|
||||
}
|
||||
if v5 != nil && !bb.Vec3WithinXY(*v5) {
|
||||
v5 = nil
|
||||
}
|
||||
if v6 != nil && !bb.Vec3WithinXY(*v6) {
|
||||
v6 = nil
|
||||
}
|
||||
|
||||
var (
|
||||
vec *mgl64.Vec3
|
||||
dist = math.MaxFloat64
|
||||
)
|
||||
|
||||
for _, v := range [...]*mgl64.Vec3{v1, v2, v3, v4, v5, v6} {
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if d := start.Sub(*v).LenSqr(); d < dist {
|
||||
vec = v
|
||||
dist = d
|
||||
}
|
||||
}
|
||||
|
||||
if vec == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var f cube.Face
|
||||
switch vec {
|
||||
case v1:
|
||||
f = cube.FaceWest
|
||||
case v2:
|
||||
f = cube.FaceEast
|
||||
case v3:
|
||||
f = cube.FaceDown
|
||||
case v4:
|
||||
f = cube.FaceUp
|
||||
case v5:
|
||||
f = cube.FaceNorth
|
||||
case v6:
|
||||
f = cube.FaceSouth
|
||||
}
|
||||
|
||||
return BBoxResult{bb: bb, pos: *vec, face: f}, true
|
||||
}
|
||||
|
||||
// BBoxIntersects checks if the line segment from start to end intersects the BBox.
|
||||
// Unlike BBoxIntercept, it only reports whether an intersection exists and does not
|
||||
// calculate the closest hit position or face.
|
||||
func BBoxIntersects(bb cube.BBox, start, end mgl64.Vec3) bool {
|
||||
min, max := bb.Min(), bb.Max()
|
||||
dir := end.Sub(start)
|
||||
tMin, tMax := 0.0, 1.0
|
||||
|
||||
for axis := range 3 {
|
||||
if mgl64.FloatEqual(dir[axis], 0) {
|
||||
if start[axis] < min[axis] || start[axis] > max[axis] {
|
||||
return false
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
inv := 1 / dir[axis]
|
||||
t1 := (min[axis] - start[axis]) * inv
|
||||
t2 := (max[axis] - start[axis]) * inv
|
||||
if t1 > t2 {
|
||||
t1, t2 = t2, t1
|
||||
}
|
||||
if t1 > tMin {
|
||||
tMin = t1
|
||||
}
|
||||
if t2 < tMax {
|
||||
tMax = t2
|
||||
}
|
||||
if tMin > tMax {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// vec3OnLineWithX returns an mgl64.Vec3 on the line between mgl64.Vec3 a and b with an X value passed. If no such vec3
|
||||
// could be found, the bool returned is false.
|
||||
func vec3OnLineWithX(a, b mgl64.Vec3, x float64) *mgl64.Vec3 {
|
||||
if mgl64.FloatEqual(b[0], a[0]) {
|
||||
return nil
|
||||
}
|
||||
|
||||
f := (x - a[0]) / (b[0] - a[0])
|
||||
if f < 0 || f > 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &mgl64.Vec3{x, a[1] + (b[1]-a[1])*f, a[2] + (b[2]-a[2])*f}
|
||||
}
|
||||
|
||||
// vec3OnLineWithY returns an mgl64.Vec3 on the line between mgl64.Vec3 a and b with a Y value passed. If no such vec3
|
||||
// could be found, the bool returned is false.
|
||||
func vec3OnLineWithY(a, b mgl64.Vec3, y float64) *mgl64.Vec3 {
|
||||
if mgl64.FloatEqual(a[1], b[1]) {
|
||||
return nil
|
||||
}
|
||||
|
||||
f := (y - a[1]) / (b[1] - a[1])
|
||||
if f < 0 || f > 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &mgl64.Vec3{a[0] + (b[0]-a[0])*f, y, a[2] + (b[2]-a[2])*f}
|
||||
}
|
||||
|
||||
// vec3OnLineWithZ returns an mgl64.Vec3 on the line between mgl64.Vec3 a and b with a Z value passed. If no such vec3
|
||||
// could be found, the bool returned is false.
|
||||
func vec3OnLineWithZ(a, b mgl64.Vec3, z float64) *mgl64.Vec3 {
|
||||
if mgl64.FloatEqual(a[2], b[2]) {
|
||||
return nil
|
||||
}
|
||||
|
||||
f := (z - a[2]) / (b[2] - a[2])
|
||||
if f < 0 || f > 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &mgl64.Vec3{a[0] + (b[0]-a[0])*f, a[1] + (b[1]-a[1])*f, z}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"github.com/df-mc/dragonfly/server/block/cube"
|
||||
"github.com/df-mc/dragonfly/server/block/model"
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
"github.com/go-gl/mathgl/mgl64"
|
||||
"math"
|
||||
)
|
||||
|
||||
// BlockResult is the result of a ray trace collision with a block's model.
|
||||
type BlockResult struct {
|
||||
bb cube.BBox
|
||||
pos mgl64.Vec3
|
||||
face cube.Face
|
||||
|
||||
blockPos cube.Pos
|
||||
}
|
||||
|
||||
// BBox returns the BBox that was collided within the block's model.
|
||||
func (r BlockResult) BBox() cube.BBox {
|
||||
return r.bb
|
||||
}
|
||||
|
||||
// Position ...
|
||||
func (r BlockResult) Position() mgl64.Vec3 {
|
||||
return r.pos
|
||||
}
|
||||
|
||||
// Face returns the hit block face.
|
||||
func (r BlockResult) Face() cube.Face {
|
||||
return r.face
|
||||
}
|
||||
|
||||
// BlockPosition returns the block that was collided with.
|
||||
func (r BlockResult) BlockPosition() cube.Pos {
|
||||
return r.blockPos
|
||||
}
|
||||
|
||||
// BlockIntercept performs a ray trace and calculates the point on the block model's edge nearest to the start position
|
||||
// that the ray collided with.
|
||||
// BlockIntercept returns a BlockResult with the block collided with and with the colliding vector closest to the start position,
|
||||
// if no colliding point was found, a zero BlockResult is returned and ok is false.
|
||||
func BlockIntercept(pos cube.Pos, src world.BlockSource, b world.Block, start, end mgl64.Vec3) (result BlockResult, ok bool) {
|
||||
bbs := b.Model().BBox(pos, src)
|
||||
if len(bbs) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
hit Result
|
||||
dist = math.MaxFloat64
|
||||
)
|
||||
|
||||
for _, bb := range bbs {
|
||||
next, ok := BBoxIntercept(bb.Translate(pos.Vec3()), start, end)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
nextDist := next.Position().Sub(start).LenSqr()
|
||||
if nextDist < dist {
|
||||
hit = next
|
||||
dist = nextDist
|
||||
}
|
||||
}
|
||||
|
||||
if hit == nil {
|
||||
return result, false
|
||||
}
|
||||
|
||||
return BlockResult{bb: hit.BBox(), pos: hit.Position(), face: hit.Face(), blockPos: pos}, true
|
||||
}
|
||||
|
||||
// BlockIntersects checks if the line segment from start to end intersects the block model of b at pos. Unlike
|
||||
// BlockIntercept, it only reports whether an intersection exists and does not calculate the closest hit position, face,
|
||||
// or bounding box.
|
||||
func BlockIntersects(pos cube.Pos, src world.BlockSource, b world.Block, start, end mgl64.Vec3) bool {
|
||||
m := b.Model()
|
||||
switch m.(type) {
|
||||
case model.Empty:
|
||||
return false
|
||||
case model.Solid:
|
||||
return BBoxIntersects(cube.Box(0, 0, 0, 1, 1, 1).Translate(pos.Vec3()), start, end)
|
||||
}
|
||||
|
||||
for _, bb := range m.BBox(pos, src) {
|
||||
if BBoxIntersects(bb.Translate(pos.Vec3()), start, end) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"github.com/df-mc/dragonfly/server/block/cube"
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
"github.com/go-gl/mathgl/mgl64"
|
||||
)
|
||||
|
||||
// EntityResult is the result of a ray trace collision with an entities bounding box.
|
||||
type EntityResult struct {
|
||||
bb cube.BBox
|
||||
pos mgl64.Vec3
|
||||
face cube.Face
|
||||
|
||||
entity world.Entity
|
||||
}
|
||||
|
||||
// BBox returns the entities bounding box that was collided with.
|
||||
func (r EntityResult) BBox() cube.BBox {
|
||||
return r.bb
|
||||
}
|
||||
|
||||
// Position ...
|
||||
func (r EntityResult) Position() mgl64.Vec3 {
|
||||
return r.pos
|
||||
}
|
||||
|
||||
// Face ...
|
||||
func (r EntityResult) Face() cube.Face {
|
||||
return r.face
|
||||
}
|
||||
|
||||
// Entity returns the entity that was collided with.
|
||||
func (r EntityResult) Entity() world.Entity {
|
||||
return r.entity
|
||||
}
|
||||
|
||||
// EntityIntercept performs a ray trace and calculates the point on the entities bounding box's edge nearest to the start position
|
||||
// that the ray collided with.
|
||||
// EntityIntercept returns an EntityResult with the entity collided with and with the colliding vector closest to the start position,
|
||||
// if no colliding point was found, a zero BlockResult is returned ok is false.
|
||||
func EntityIntercept(e world.Entity, start, end mgl64.Vec3) (result EntityResult, ok bool) {
|
||||
bb := e.H().Type().BBox(e).Translate(e.Position()).Grow(0.3)
|
||||
|
||||
r, ok := BBoxIntercept(bb, start, end)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
return EntityResult{bb: bb, pos: r.Position(), face: r.Face(), entity: e}, true
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"github.com/df-mc/dragonfly/server/block/cube"
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
"github.com/go-gl/mathgl/mgl64"
|
||||
"iter"
|
||||
"math"
|
||||
)
|
||||
|
||||
// Result represents the result of a ray trace collision with a bounding box.
|
||||
type Result interface {
|
||||
// BBox returns the bounding box collided with.
|
||||
BBox() cube.BBox
|
||||
// Position returns where the ray first collided with the bounding box.
|
||||
Position() mgl64.Vec3
|
||||
// Face returns the face of the bounding box that was collided on.
|
||||
Face() cube.Face
|
||||
}
|
||||
|
||||
type EntityFilter func(iter.Seq[world.Entity]) iter.Seq[world.Entity]
|
||||
|
||||
// Perform performs a ray trace between start and end, checking if any blocks or entities collided with the
|
||||
// ray. The physics.BBox that's passed is used for checking if any entity within the bounding box collided
|
||||
// with the ray.
|
||||
func Perform(start, end mgl64.Vec3, tx *world.Tx, box cube.BBox, filter EntityFilter) (hit Result, ok bool) {
|
||||
// Check if there's any blocks that we may collide with.
|
||||
TraverseBlocks(start, end, func(pos cube.Pos) (cont bool) {
|
||||
b := tx.Block(pos)
|
||||
|
||||
// Check if we collide with the block's model.
|
||||
if result, ok := BlockIntercept(pos, tx, b, start, end); ok {
|
||||
hit = result
|
||||
end = hit.Position()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
// Now check for any entities that we may collide with.
|
||||
dist := math.MaxFloat64
|
||||
bb := box.Translate(start).Extend(end.Sub(start))
|
||||
entities := tx.EntitiesWithin(bb.Grow(8.0))
|
||||
if filter != nil {
|
||||
entities = filter(entities)
|
||||
}
|
||||
for entity := range entities {
|
||||
if !entity.H().Type().BBox(entity).Translate(entity.Position()).IntersectsWith(bb) {
|
||||
continue
|
||||
}
|
||||
// Check if we collide with the entities bounding box.
|
||||
result, ok := EntityIntercept(entity, start, end)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if distance := start.Sub(result.Position()).LenSqr(); distance < dist {
|
||||
dist = distance
|
||||
hit = result
|
||||
}
|
||||
}
|
||||
|
||||
return hit, hit != nil
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"github.com/df-mc/dragonfly/server/block/cube"
|
||||
"github.com/go-gl/mathgl/mgl64"
|
||||
"math"
|
||||
)
|
||||
|
||||
// TraverseBlocks performs a ray trace between the start and end coordinates.
|
||||
// A function 'f' is passed which is called for each voxel, if f returns false, the function will return.
|
||||
// TraverseBlocks panics if the start and end positions are the same.
|
||||
func TraverseBlocks(start, end mgl64.Vec3, f func(pos cube.Pos) (con bool)) {
|
||||
dir := end.Sub(start)
|
||||
if mgl64.FloatEqual(dir.LenSqr(), 0) {
|
||||
panic("start and end points are the same, giving a zero direction vector")
|
||||
}
|
||||
dir = dir.Normalize()
|
||||
|
||||
b := cube.PosFromVec3(start)
|
||||
|
||||
step := signVec3(dir)
|
||||
stepX, stepY, stepZ := int(step[0]), int(step[1]), int(step[2])
|
||||
max := boundaryVec3(start, dir)
|
||||
|
||||
delta := safeDivideVec3(step, dir)
|
||||
|
||||
r := start.Sub(end).Len()
|
||||
for {
|
||||
if !f(b) {
|
||||
return
|
||||
}
|
||||
|
||||
switch {
|
||||
case max[0] < max[1] && max[0] < max[2]:
|
||||
if max[0] > r {
|
||||
return
|
||||
}
|
||||
b[0] += stepX
|
||||
max[0] += delta[0]
|
||||
case max[1] < max[2]:
|
||||
if max[1] > r {
|
||||
return
|
||||
}
|
||||
b[1] += stepY
|
||||
max[1] += delta[1]
|
||||
default:
|
||||
if max[2] > r {
|
||||
return
|
||||
}
|
||||
b[2] += stepZ
|
||||
max[2] += delta[2]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// safeDivideVec3 ...
|
||||
func safeDivideVec3(dividend, divisor mgl64.Vec3) mgl64.Vec3 {
|
||||
return mgl64.Vec3{
|
||||
safeDivide(dividend[0], divisor[0]),
|
||||
safeDivide(dividend[1], divisor[1]),
|
||||
safeDivide(dividend[2], divisor[2]),
|
||||
}
|
||||
}
|
||||
|
||||
// safeDivide divides the dividend by the divisor, but if the divisor is 0, it returns 0.
|
||||
func safeDivide(dividend, divisor float64) float64 {
|
||||
if divisor == 0.0 {
|
||||
return 0.0
|
||||
}
|
||||
return dividend / divisor
|
||||
}
|
||||
|
||||
// boundaryVec3 ...
|
||||
func boundaryVec3(v1, v2 mgl64.Vec3) mgl64.Vec3 {
|
||||
return mgl64.Vec3{boundary(v1[0], v2[0]), boundary(v1[1], v2[1]), boundary(v1[2], v2[2])}
|
||||
}
|
||||
|
||||
// boundary returns the distance that must be travelled on an axis from the start point with the direction vector
|
||||
// component to cross a block boundary.
|
||||
func boundary(start, dir float64) float64 {
|
||||
if dir == 0.0 {
|
||||
return math.Inf(1)
|
||||
}
|
||||
|
||||
if dir < 0.0 {
|
||||
start, dir = -start, -dir
|
||||
if math.Floor(start) == start {
|
||||
return 0.0
|
||||
}
|
||||
}
|
||||
|
||||
return (1 - (start - math.Floor(start))) / dir
|
||||
}
|
||||
|
||||
// signVec3 ...
|
||||
func signVec3(v1 mgl64.Vec3) mgl64.Vec3 {
|
||||
return mgl64.Vec3{sign(v1[0]), sign(v1[1]), sign(v1[2])}
|
||||
}
|
||||
|
||||
// sign ...
|
||||
func sign(f float64) float64 {
|
||||
switch {
|
||||
case f > 0.0:
|
||||
return 1.0
|
||||
case f < 0.0:
|
||||
return -1.0
|
||||
}
|
||||
return 0.0
|
||||
}
|
||||
Reference in New Issue
Block a user