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} }