public static unsafe bool Hit(this BvhNode n, Ray r, float tMin, float tMax, #if FULL_DIAGNOSTICS ref AccumulateJob.Diagnostics diagnostics, #endif out HitRecord rec) { rec = default; if (!n.Bounds.Hit(r, tMin, tMax)) { return(false); } #if FULL_DIAGNOSTICS diagnostics.BoundsHitCount++; #endif if (n.IsLeaf) { #if FULL_DIAGNOSTICS diagnostics.CandidateCount++; #endif return(n.Content.Hit(r, tMin, tMax, out rec)); } bool hitLeft = n.Left->Hit(r, tMin, tMax, out HitRecord leftRecord); bool hitRight = n.Right->Hit(r, tMin, tMax, out HitRecord rightRecord); if (!hitLeft && !hitRight) { return(false); } if (hitLeft && hitRight) { rec = leftRecord.Distance < rightRecord.Distance ? leftRecord : rightRecord; return(true); } if (hitLeft) { rec = leftRecord; return(true); } rec = rightRecord; return(true); }
public static unsafe bool Hit(this BvhNode n, NativeArray <Entity> entities, Ray r, float tMin, float tMax, ref Random rng, AccumulateJob.WorkingArea wa, #if FULL_DIAGNOSTICS ref Diagnostics diagnostics, #endif out HitRecord rec) { int candidateCount = 0, nodeStackHeight = 1; BvhNode **nodeStackTail = wa.Nodes; Entity * candidateListTail = wa.Entities - 1, candidateListHead = wa.Entities; float3 rayInvDirection = rcp(r.Direction); *nodeStackTail = &n; while (nodeStackHeight > 0) { BvhNode *nodePtr = *nodeStackTail--; nodeStackHeight--; if (!nodePtr->Bounds.Hit(r.Origin, rayInvDirection, tMin, tMax)) { continue; } #if FULL_DIAGNOSTICS diagnostics.BoundsHitCount++; #endif if (nodePtr->IsLeaf) { *++candidateListTail = entities[nodePtr->EntityId]; candidateCount++; } else { *++nodeStackTail = nodePtr->Left; *++nodeStackTail = nodePtr->Right; nodeStackHeight += 2; } } #if FULL_DIAGNOSTICS diagnostics.CandidateCount = candidateCount; #endif if (candidateCount == 0) { rec = default; return(false); } #if BVH_SIMD // TODO: this is fully broken // skip SIMD codepath if there's only one if (candidateCount == 1) { return(candidateListHead->Hit(r, tMin, tMax, out rec)); } var simdSpheresHead = (Sphere4 *)wa.Vectors; int simdBlockCount = (int)ceil(candidateCount / 4.0f); float4 a = dot(r.Direction, r.Direction); int4 curId = int4(0, 1, 2, 3), hitId = -1; float4 hitT = tMax; Sphere4 *blockCursor = simdSpheresHead; Entity * candidateCursor = candidateListHead; int candidateIndex = 0; for (int i = 0; i < simdBlockCount; i++) { for (int j = 0; j < 4; j++) { if (candidateIndex < candidateCount) { Sphere *sphereData = candidateCursor->AsSphere; float3 center = sphereData->Center(r.Time); blockCursor->CenterX[j] = center.x; blockCursor->CenterY[j] = center.y; blockCursor->CenterZ[j] = center.z; blockCursor->SquaredRadius[j] = sphereData->SquaredRadius; ++candidateCursor; ++candidateIndex; } else { blockCursor->CenterX[j] = float.MaxValue; blockCursor->CenterY[j] = float.MaxValue; blockCursor->CenterZ[j] = float.MaxValue; blockCursor->SquaredRadius[j] = 0; } } float4 ocX = r.Origin.x - blockCursor->CenterX, ocY = r.Origin.y - blockCursor->CenterY, ocZ = r.Origin.z - blockCursor->CenterZ; float4 b = ocX * r.Direction.x + ocY * r.Direction.y + ocZ * r.Direction.z; float4 c = ocX * ocX + ocY * ocY + ocZ * ocZ - blockCursor->SquaredRadius; float4 discriminant = b * b - a * c; bool4 discriminantTest = discriminant > 0; if (any(discriminantTest)) { float4 sqrtDiscriminant = sqrt(discriminant); float4 t0 = (-b - sqrtDiscriminant) / a; float4 t1 = (-b + sqrtDiscriminant) / a; float4 t = select(t1, t0, t0 > tMin); bool4 mask = discriminantTest & t > tMin & t < hitT; hitId = select(hitId, curId, mask); hitT = select(hitT, t, mask); } curId += 4; ++blockCursor; } if (all(hitId == -1)) { rec = default; return(false); } float minDistance = cmin(hitT); int laneMask = bitmask(hitT == minDistance); int firstLane = tzcnt(laneMask); int closestId = hitId[firstLane]; Sphere *closestSphere = candidateListHead[closestId].AsSphere; float3 point = r.GetPoint(minDistance); rec = new HitRecord(minDistance, point, (point - closestSphere->Center(r.Time)) / closestSphere->Radius, closestSphere->Material); return(true); #else // iterative candidate tests (non-SIMD) bool anyHit = candidateListHead->Hit(r, tMin, tMax, ref rng, out rec); for (int i = 1; i < candidateCount; i++) { bool thisHit = candidateListHead[i].Hit(r, tMin, tMax, ref rng, out HitRecord thisRec); if (thisHit && (!anyHit || thisRec.Distance < rec.Distance)) { anyHit = true; rec = thisRec; } } if (anyHit) { return(true); } rec = default; return(false); #endif }