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

This commit is contained in:
2026-07-09 08:33:57 +08:00
commit 26ed99fda6
845 changed files with 75419 additions and 0 deletions
+180
View File
@@ -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}
}
+93
View File
@@ -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
}
+51
View File
@@ -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
}
+64
View File
@@ -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
}
+109
View File
@@ -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
}