Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion Sources/UntoldEngine/Physics/PhysicsQuery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,19 @@ public enum PhysicsQuery {
// farther candidates even though its reported hit distance is 0 — so
// inside-origin candidates are always examined and the scan never
// breaks out of the sorted order early.
//
// The query itself is asked for the unbounded distance, not the
// caller's maxDistance: its own internal cull compares maxDistance
// against that same entry-or-exit value, so an inside-origin box
// whose EXIT point lies beyond a tight maxDistance would otherwise be
// dropped before the narrow phase ever sees it — even though its true
// hit distance (0, at the ray origin) is well within range. The real
// cap is enforced below, against the recomputed entry distance.
var best: PhysicsRayHit?
for (entity, sortedDistance) in OctreeSystem.shared.query(
rayOrigin: ray.origin,
rayDirection: direction,
maxDistance: maxDistance
maxDistance: fallbackUnboundedDistance
) {
guard passesFilter(entity, filter),
let bounds = OctreeSystem.shared.getBounds(for: entity)
Expand Down
23 changes: 23 additions & 0 deletions Tests/UntoldEngineTests/PhysicsQueryTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,29 @@ final class PhysicsQueryTests: XCTestCase {
XCTAssertEqual(hit.normal, -direction)
}

func testInsideBoxHitSurvivesTightMaxDistance() throws {
// Regression: the broad-phase cull inside OctreeSystem.query compares
// maxDistance against the sorted distance, which for an inside-origin
// box is its far EXIT distance, not the true hit distance (0). Passing
// the caller's maxDistance straight through to that query would drop
// this box before the narrow phase ever saw it, even though the real
// hit (distance 0, at the ray origin) is well inside a tight budget.
let bigBox = makeObstacle(
center: simd_float3(0, 0, -50),
halfExtents: simd_float3(50, 50, 100)
)

let direction = simd_float3(0, 0, -1)
let hit = try XCTUnwrap(PhysicsQuery.raycast(
PhysicsRay(origin: .zero, direction: direction, maxDistance: 5.0)
))

XCTAssertEqual(hit.entity, bigBox)
XCTAssertEqual(hit.distance, 0.0)
XCTAssertEqual(hit.position, .zero)
XCTAssertEqual(hit.normal, -direction)
}

func testFallbackMissReturnsNil() {
_ = makeObstacle(center: simd_float3(0.0, 10.0, -5.0))

Expand Down
Loading