private static List <ScanResultLOD> GetCenterPointList( Transform transform, GameObjectShape shape) { // Single ScanResultLOD for lowest LOD. var result = ScanResultLOD.CreateList(1); Matrix4x4 matrix = transform.worldToLocalMatrix; bool flatten = shape.Flatten; foreach (Volume vol in s_Volumes) { Vector3 localPoint = matrix.MultiplyPoint3x4(vol.Bounds.center); if (flatten) { localPoint.y = 0; } result[0].LocalPoints.Add(localPoint); } return(result); }
/// <summary> /// Performs a scan over a set of colliders and returns /// lists of enclosed points for different levels of detail. /// </summary> /// <param name="scene"><see cref="PhysicsScene"/> for raycasts and overlap checks</param> /// <param name="transform"><see cref="DetectableGameObject"/> transform</param> /// <param name="colliders">List of colliders</param> /// <param name="shape"><see cref="GameObjectShape"/></param> /// <param name="result">List of <see cref="ScanResultLOD"/> instances (output)</param> /// <returns>The highest LOD for the given scan settings and object size</returns> public static int Scan( PhysicsScene scene, Transform transform, IList <Collider> colliders, GameObjectShape shape, out List <ScanResultLOD> result) { s_GlobalMaxLOD = 0; CheckPhysicsBufferSize(colliders); CreateVolumes(colliders, shape); result = shape.ScanLOD > 0 ? GetLODPointLists(scene, transform, shape) : GetCenterPointList(transform, shape); // GlobalMaxLOD corresponds to the result list's // length unless the 'flatten' option is enabled: // GlobalMaxLOD = result.Length - 1 // // If the points are flattened for 2D, the result // only contains a single point list, because dynamic // LODs are unnecessary for 2D detection. // However, GlobalMaxLOD still refers to the number // of LODs that *would* be used given the current // Scan LOD without flattening. The reason for this // is that we need the max LOD value for showing // the correct inspector settings, even if the scan // result contains only one point list. // // GlobalMaxLOD is also used for clamping the // Scan LOD setting, because we have a minimum // length limit when splitting volumes into cells. // Therefore it's not possible to apply high Scan // LODs to small colliders/volumes. return(s_GlobalMaxLOD); }
private static List <ScanResultLOD> GetLODPointLists( PhysicsScene scene, Transform transform, GameObjectShape shape) { SplitVolumesIntoCells(shape.ScanLOD); int mask = 1 << transform.gameObject.layer; bool flatten = shape.Flatten; var result = ScanResultLOD.CreateList(flatten ? 1 : s_GlobalMaxLOD + 1); foreach (Volume vol in s_Volumes) { ToggleActiveColliders(vol); bool isConcave = vol.IsConcave; Vector3 cellSize = vol.CellSize; Vector3Int gridSize = vol.GridSize; Vector3 offset = vol.Bounds.min + cellSize * 0.5f; Vector3 boundsCenter = vol.Bounds.center; float boundsMagnitude = vol.Bounds.size.magnitude; int nx = gridSize.x - 1; int ny = gridSize.y - 1; int nz = gridSize.z - 1; // Find cells occupied by colliders. Clear(gridSize); for (int x = 0; x <= nx; x++) { for (int y = 0; y <= ny; y++) { for (int z = 0; z <= nz; z++) { Vector3 worldPoint = Vector3.Scale( new Vector3(x, y, z), cellSize) + offset; bool isInside = isConcave ? IsInsideCollider( scene, mask, worldPoint, vol.Bounds) : IsInsideCollider( scene, mask, worldPoint, vol.Colliders); s_HollowGrid[x, y, z] = isInside; s_FilledGrid[x, y, z] = isInside; } } } // Reduce cell count, hollow out filled grid. for (int x = 1; x < nx; x++) { for (int y = 1; y < ny; y++) { for (int z = 1; z < nz; z++) { if (s_FilledGrid[x, y, z]) { s_HollowGrid[x, y, z] &= !(s_FilledGrid[x - 1, y, z] & s_FilledGrid[x + 1, y, z] & s_FilledGrid[x, y - 1, z] & s_FilledGrid[x, y + 1, z] & s_FilledGrid[x, y, z - 1] & s_FilledGrid[x, y, z + 1]); } } } } // Downsample cells for generating LODs // and store points in corresponding groups. int level = flatten ? 0 : vol.MaxLOD; float yFlat = transform.position.y; for (int x = 0; x <= nx; x++) { for (int y = 0; y <= ny; y++) { for (int z = 0; z <= nz; z++) { if (s_HollowGrid[x, y, z]) { Cell cell = new Cell(level, x, y, z); Vector3 worldPoint = cell.Scale(cellSize) + offset; if (flatten) { cell.y = 0; worldPoint.y = yFlat; } // Highest LOD. AddPoint(cell, worldPoint); for (int i = level; i > 0; i--) { cell = cell.Downsample(); AddPoint(cell, worldPoint); } } } } } // Write centroids to result. Matrix4x4 matrix = transform.worldToLocalMatrix; float projection = shape.Projection; bool project = projection > 0; foreach (var kvp in s_PointGroupsByCell) { // KeyValuePair, Key: Cell, Value: PointGroup. int i = kvp.Key.level; Vector3 worldPoint = kvp.Value.Centroid; if (project && i > 0) { // Project outward from volume bounds center, // unless lowest LOD or flattened points -> i == 0. // NOTE Doesn't work well with compound or concave colliders. Vector3 normal = (worldPoint - boundsCenter).normalized; if (scene.Raycast(boundsCenter + normal * boundsMagnitude, -normal, out RaycastHit hit, boundsMagnitude, mask)) { worldPoint = Vector3.Lerp(worldPoint, hit.point, projection); } } // World -> local. result[i].LocalPoints.Add(matrix.MultiplyPoint3x4(worldPoint)); } } ToggleActiveColliders(); return(result); }
private static void CreateVolumes( IList <Collider> colliders, GameObjectShape shape) { s_Volumes.Clear(); if (shape.Merge) { // Merge all. var volume = new Volume() { Colliders = new List <Collider>(colliders) }; foreach (var collider in colliders) { bool isConcave = collider is MeshCollider && !((MeshCollider)collider).convex; volume.IsConcave = volume.IsConcave || isConcave; volume.Bounds.Encapsulate(collider.bounds); } s_Volumes.Add(volume); } else { var byColliderType = new List <Volume>[] { new List <Volume>(), new List <Volume>() }; foreach (var collider in colliders) { bool isConcave = collider is MeshCollider && !((MeshCollider)collider).convex; byColliderType[isConcave ? 1 : 0].Add(new Volume() { Colliders = new List <Collider>() { collider }, Bounds = collider.bounds, IsConcave = isConcave }); } // Merge connected by type, don't mix convex and concave. for (int i = 0; i < 2; i++) { var volumes = byColliderType[i]; for (int j = volumes.Count - 1; j > 0; j--) { for (int k = j - 1; k > -1; k--) { // NOTE Intersects check yields true for colliders // located side by side, e.g. two box colliders of // size 1x1x1, sitting at 0/0/0 and 1/1/1 respectively. if (volumes[j].Bounds.Intersects(volumes[k].Bounds)) { volumes[j].Bounds.Encapsulate(volumes[k].Bounds); volumes[j].Colliders.AddRange(volumes[k].Colliders); volumes.RemoveAt(k); j--; } } } s_Volumes.AddRange(volumes); } } }