ConcealmentMeter: add per-collider AABB broadphase in the voxel loop
Cache each environment candidate's world AABB once per evaluation and gate the expensive per-voxel tests (Collider.ClosestPoint, ContainsWorld, concave raycast) behind a cheap bounds.Contains prefilter. Collapses the hot loop from O(voxel x colliders) worth of ClosestPoint/raycast calls to O(voxel x few) for dense collider clusters (VHACD hulls, furniture), removing the framedrop when overlapping many colliders. Conservative AABB test never drops a real hit, so EmbeddedPercent is identical, just cheaper. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
50781e32d2
commit
f4d31b0c24
1 file changed
+26
-4
@@ -88,6 +88,15 @@ public class ConcealmentMeter : MonoBehaviour
|
||||
private readonly List<MeshCollider> _concave = new List<MeshCollider>();
|
||||
private readonly List<ConcealmentVolume> _volumes = new List<ConcealmentVolume>();
|
||||
|
||||
// World AABBs parallel to _convex/_concave/_volumes, fetched ONCE per eval. The per-voxel hot loop
|
||||
// rejects a candidate with a cheap Bounds.Contains before the expensive ClosestPoint / raycast /
|
||||
// InverseTransformPoint. A point inside a collider is always inside its world AABB, so this is a
|
||||
// conservative filter -> never a false negative, only fewer expensive tests. This is what keeps cost
|
||||
// ~O(voxels) instead of O(voxels x colliders) when the body overlaps many colliders (e.g. VHACD hulls).
|
||||
private readonly List<Bounds> _convexBounds = new List<Bounds>();
|
||||
private readonly List<Bounds> _concaveBounds = new List<Bounds>();
|
||||
private readonly List<Bounds> _volumeBounds = new List<Bounds>();
|
||||
|
||||
private readonly List<Vector3> _gizPos = new List<Vector3>();
|
||||
private readonly List<bool> _gizEmbedded = new List<bool>();
|
||||
private float _gizVoxelWorld;
|
||||
@@ -273,6 +282,9 @@ public class ConcealmentMeter : MonoBehaviour
|
||||
_terrains.Clear();
|
||||
_concave.Clear();
|
||||
_volumes.Clear();
|
||||
_convexBounds.Clear();
|
||||
_concaveBounds.Clear();
|
||||
_volumeBounds.Clear();
|
||||
|
||||
// Baked concealment volumes are authoritative for the (open-shell) furniture they cover;
|
||||
// gathered from the registry independent of colliders so they need no specific collider type.
|
||||
@@ -282,7 +294,8 @@ public class ConcealmentMeter : MonoBehaviour
|
||||
ConcealmentVolume v = vols[i];
|
||||
if (!v || !v.HasBake) continue;
|
||||
if (characterRoot && (v.transform == characterRoot || v.transform.IsChildOf(characterRoot))) continue;
|
||||
if (v.WorldBounds.Intersects(wb)) _volumes.Add(v);
|
||||
Bounds vb = v.WorldBounds; // getter recomputes a matrix transform -> take it once, reuse as the hot-loop filter
|
||||
if (vb.Intersects(wb)) { _volumes.Add(v); _volumeBounds.Add(vb); }
|
||||
}
|
||||
|
||||
int n = Physics.OverlapBoxNonAlloc(wb.center, wb.extents, _overlap, Quaternion.identity,
|
||||
@@ -311,11 +324,15 @@ public class ConcealmentMeter : MonoBehaviour
|
||||
// A baked ConcealmentVolume or ConcealmentColliderBaker supersedes raycast parity for this object.
|
||||
if (includeConcaveMeshColliders && !mc.GetComponentInParent<ConcealmentVolume>()
|
||||
&& !mc.GetComponentInParent<ConcealmentColliderBaker>())
|
||||
{
|
||||
_concave.Add(mc);
|
||||
_concaveBounds.Add(mc.bounds);
|
||||
}
|
||||
continue; // concave can't use ClosestPoint
|
||||
}
|
||||
|
||||
_convex.Add(col); // Box / Sphere / Capsule / convex MeshCollider
|
||||
_convexBounds.Add(col.bounds);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,12 +340,16 @@ public class ConcealmentMeter : MonoBehaviour
|
||||
{
|
||||
for (int i = 0; i < _volumes.Count; i++)
|
||||
{
|
||||
if (_volumes[i].ContainsWorld(p)) return true;
|
||||
// AABB reject first: skips the ContainsWorld InverseTransformPoint matrix mult for far voxels.
|
||||
if (_volumeBounds[i].Contains(p) && _volumes[i].ContainsWorld(p)) return true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < _convex.Count; i++)
|
||||
{
|
||||
if ((_convex[i].ClosestPoint(p) - p).sqrMagnitude <= InsideEpsSqr) return true;
|
||||
// AABB reject first: ClosestPoint (esp. on convex MeshColliders / VHACD hulls) is the dominant
|
||||
// per-voxel cost; a point outside the collider's world AABB cannot be inside it.
|
||||
if (_convexBounds[i].Contains(p) &&
|
||||
(_convex[i].ClosestPoint(p) - p).sqrMagnitude <= InsideEpsSqr) return true;
|
||||
}
|
||||
|
||||
if (includeTerrain)
|
||||
@@ -348,7 +369,8 @@ public class ConcealmentMeter : MonoBehaviour
|
||||
{
|
||||
for (int i = 0; i < _concave.Count; i++)
|
||||
{
|
||||
if (InsideConcave(_concave[i], p)) return true;
|
||||
// AABB reject first: InsideConcave fires a raycast loop up to 1000 units per voxel -> gate hard.
|
||||
if (_concaveBounds[i].Contains(p) && InsideConcave(_concave[i], p)) return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user