#if UNITY_EDITOR using UnityEngine; using UnityEditor; using System.Collections.Generic; using System; namespace GSpawn { public class SceneObjectTree { [NonSerialized] private List> _overlappedNodes = new List>(); [NonSerialized] private List> _nodeHitBuffer = new List>(); [NonSerialized] private List _fullOverlapCorners = new List(); [NonSerialized] private HashSet _prefabInstanceRoots = new HashSet(); [NonSerialized] private ObjectBounds.QueryConfig _nodeBoundsQConfig; [NonSerialized] private ObjectBounds.QueryConfig _objectBoundsQConfig; [NonSerialized] private GameObjectType _recognizedObjectTypes = GameObjectType.Mesh | GameObjectType.Sprite | GameObjectType.Terrain | GameObjectType.Light | GameObjectType.ParticleSystem | GameObjectType.Camera | GameObjectType.Empty; [NonSerialized] private BinarySphereTree _tree = new BinarySphereTree(); [NonSerialized] private Dictionary> _nodeMap = new Dictionary>(); public SceneObjectTree() { _nodeBoundsQConfig = new ObjectBounds.QueryConfig(); _nodeBoundsQConfig.volumelessSize = Vector3.one * 0.1f; _nodeBoundsQConfig.objectTypes = _recognizedObjectTypes; _nodeBoundsQConfig.includeInactive = true; _nodeBoundsQConfig.includeInvisible = true; _objectBoundsQConfig = new ObjectBounds.QueryConfig(); _objectBoundsQConfig.volumelessSize = Vector3.one; _objectBoundsQConfig.objectTypes = _recognizedObjectTypes; _tree.initialize(0.0f); } public void clear() { _tree.clear(); _nodeMap.Clear(); } public bool overlapBox(OBB box, ObjectOverlapFilter overlapFilter, ObjectOverlapConfig overlapConfig) { if (!_tree.overlapBox(box, _overlappedNodes)) return false; if (overlapConfig.requireFullOverlap) { foreach (var node in _overlappedNodes) { GameObject gameObject = node.data; if (gameObject == null || !gameObject.activeInHierarchy || LayerEx.isLayerHidden(gameObject.layer) || SceneVisibilityManager.instance.IsHidden(gameObject, false) || (overlapFilter != null && !overlapFilter.filterObject(gameObject))) continue; OBB worldOBB = ObjectBounds.calcWorldOBB(gameObject, _objectBoundsQConfig); if (worldOBB.isValid) { worldOBB.calcCorners(_fullOverlapCorners, false); if (box.containsPoints(_fullOverlapCorners)) { if (overlapConfig.prefabMode != ObjectOverlapPrefabMode.None) { GameObject prefabRoot = gameObject.getOutermostPrefabInstanceRoot(); if (prefabRoot != null || overlapConfig.prefabMode != ObjectOverlapPrefabMode.OnlyPrefabInstanceRoot) return true; } else return true; } } } } else { foreach (var node in _overlappedNodes) { GameObject gameObject = node.data; if (gameObject == null || !gameObject.activeInHierarchy || LayerEx.isLayerHidden(gameObject.layer) || SceneVisibilityManager.instance.IsHidden(gameObject, false) || (overlapFilter != null && !overlapFilter.filterObject(gameObject))) continue; OBB worldOBB = ObjectBounds.calcWorldOBB(gameObject, _objectBoundsQConfig); if (worldOBB.isValid && worldOBB.intersectsOBB(box)) { if (overlapConfig.prefabMode != ObjectOverlapPrefabMode.None) { GameObject prefabRoot = gameObject.getOutermostPrefabInstanceRoot(); if (prefabRoot != null || overlapConfig.prefabMode != ObjectOverlapPrefabMode.OnlyPrefabInstanceRoot) return true; } else return true; } } } return false; } public bool overlapBox(OBB box, ObjectOverlapFilter overlapFilter, ObjectOverlapConfig overlapConfig, List overlappedObjects) { overlappedObjects.Clear(); if (!_tree.overlapBox(box, _overlappedNodes)) return false; _prefabInstanceRoots.Clear(); if (overlapConfig.requireFullOverlap) { foreach (var node in _overlappedNodes) { GameObject gameObject = node.data; // Note: This should not be necessary, but seems to happen when holding down ALT key (e.g. during object segments selection). if (gameObject == null) continue; if (!gameObject.activeInHierarchy || LayerEx.isLayerHidden(gameObject.layer) || SceneVisibilityManager.instance.IsHidden(gameObject, false) || (overlapFilter != null && !overlapFilter.filterObject(gameObject))) continue; OBB worldOBB = ObjectBounds.calcWorldOBB(gameObject, _objectBoundsQConfig); if (worldOBB.isValid) { worldOBB.calcCorners(_fullOverlapCorners, false); if (box.containsPoints(_fullOverlapCorners)) { if (overlapConfig.prefabMode != ObjectOverlapPrefabMode.None) { GameObject prefabRoot = gameObject.getOutermostPrefabInstanceRoot(); if (prefabRoot != null) { if (!_prefabInstanceRoots.Contains(prefabRoot)) { _prefabInstanceRoots.Add(prefabRoot); overlappedObjects.Add(prefabRoot); } } else if (overlapConfig.prefabMode != ObjectOverlapPrefabMode.OnlyPrefabInstanceRoot) { overlappedObjects.Add(gameObject); } } else overlappedObjects.Add(gameObject); } } } } else { foreach (var node in _overlappedNodes) { GameObject gameObject = node.data; // Note: This should not be necessary, but seems to happen when holding down ALT key (e.g. during object path selection). if (gameObject == null) continue; if (!gameObject.activeInHierarchy || LayerEx.isLayerHidden(gameObject.layer) || SceneVisibilityManager.instance.IsHidden(gameObject, false) || (overlapFilter != null && !overlapFilter.filterObject(gameObject))) continue; OBB worldOBB = ObjectBounds.calcWorldOBB(gameObject, _objectBoundsQConfig); if (worldOBB.isValid && worldOBB.intersectsOBB(box)) { if (overlapConfig.prefabMode != ObjectOverlapPrefabMode.None) { GameObject prefabRoot = gameObject.getOutermostPrefabInstanceRoot(); if (prefabRoot != null) { if (!_prefabInstanceRoots.Contains(prefabRoot)) { _prefabInstanceRoots.Add(prefabRoot); overlappedObjects.Add(prefabRoot); } } else if (overlapConfig.prefabMode != ObjectOverlapPrefabMode.OnlyPrefabInstanceRoot) overlappedObjects.Add(gameObject); } else overlappedObjects.Add(gameObject); } } } return overlappedObjects.Count != 0; } public bool raycastAll(Ray ray, SceneRaycastFilter raycastFilter, ObjectRaycastConfig raycastConfig, bool sort, List objectHits) { objectHits.Clear(); if (!_tree.raycastAll(ray, _nodeHitBuffer, false)) return false; OBB worldOBB = new OBB(); if (raycastConfig.raycastPrecision == ObjectRaycastPrecision.BestFit) { foreach (var hit in _nodeHitBuffer) { GameObject gameObject = hit.hitNode.data; if (gameObject == null) continue; // Note: This should not be necessary, but seems to happen with prefab drag and drop in scene view. if (!gameObject.activeInHierarchy || LayerEx.isLayerHidden(gameObject.layer) || SceneVisibilityManager.instance.IsHidden(gameObject, false) || (raycastFilter != null && !raycastFilter.filterObject(gameObject))) continue; // ---------------------------------- Mesh ---------------------------------- // Mesh mesh = gameObject.getMesh(); if (mesh != null) { if (gameObject.isMeshOrSkinnedMeshRendererEnabled()) { PluginMesh pluginMesh = PluginMeshDb.instance.getPluginMesh(mesh); MeshRayHit meshRayHit; if (pluginMesh.raycastClosest(ray, gameObject.transform, raycastConfig.meshConfig, out meshRayHit)) objectHits.Add(new ObjectRayHit(ray, gameObject, meshRayHit)); } continue; } // ---------------------------------- Terrain ---------------------------------- // TerrainCollider terrainCollider = gameObject.getTerrainCollider(); if (terrainCollider != null) { if (gameObject.isTerrainEnabled()) { RaycastHit raycastHit; if (terrainCollider.Raycast(ray, out raycastHit, float.MaxValue)) { Vector3 hitNormal = raycastHit.normal; if (raycastConfig.terrainConfig.useInterpolatedNormal) hitNormal = terrainCollider.gameObject.getTerrain().getInterpolatedNormal(raycastHit.point); objectHits.Add(new ObjectRayHit(ray, gameObject, hitNormal, raycastHit.distance)); } } continue; } // ---------------------------------- Sprite ---------------------------------- // Sprite sprite = gameObject.getSprite(); if (sprite != null) { if (gameObject.isSpriteRendererEnabled()) { worldOBB = ObjectBounds.calcSpriteWorldOBB(gameObject); if (worldOBB.isValid) { float t; if (worldOBB.raycast(ray, out t)) { Vector3 hitPt = ray.GetPoint(t); Box3DFace faceClosestToPt = Box3D.findFaceClosestToPoint(hitPt, worldOBB.center, worldOBB.size, worldOBB.rotation); Vector3 faceNormal = Box3D.calcFaceNormal(worldOBB.center, worldOBB.size, worldOBB.rotation, faceClosestToPt); objectHits.Add(new ObjectRayHit(ray, gameObject, faceNormal, t)); } } } continue; } // ---------------------------------- Misc ---------------------------------- // worldOBB = ObjectBounds.calcWorldOBB(gameObject, _objectBoundsQConfig); if (worldOBB.isValid) { float t; if (worldOBB.raycast(ray, out t)) { Vector3 hitPt = ray.GetPoint(t); Box3DFace faceClosestToPt = Box3D.findFaceClosestToPoint(hitPt, worldOBB.center, worldOBB.size, worldOBB.rotation); Vector3 faceNormal = Box3D.calcFaceNormal(worldOBB.center, worldOBB.size, worldOBB.rotation, faceClosestToPt); objectHits.Add(new ObjectRayHit(ray, gameObject, faceNormal, t)); } } } } else if (raycastConfig.raycastPrecision == ObjectRaycastPrecision.Box) { foreach (var hit in _nodeHitBuffer) { GameObject gameObject = hit.hitNode.data; if (!gameObject.activeInHierarchy || LayerEx.isLayerHidden(gameObject.layer) || SceneVisibilityManager.instance.IsHidden(gameObject, false) || !raycastFilter.filterObject(gameObject)) continue; worldOBB = ObjectBounds.calcWorldOBB(gameObject, _objectBoundsQConfig); if (worldOBB.isValid) { float t; if (worldOBB.raycast(ray, out t)) { Vector3 hitPt = ray.GetPoint(t); Box3DFace faceClosestToPt = Box3D.findFaceClosestToPoint(hitPt, worldOBB.center, worldOBB.size, worldOBB.rotation); Vector3 faceNormal = Box3D.calcFaceNormal(worldOBB.center, worldOBB.size, worldOBB.rotation, faceClosestToPt); objectHits.Add(new ObjectRayHit(ray, gameObject, faceNormal, t)); } } } } if (sort) ObjectRayHit.sortByHitDistance(objectHits); return objectHits.Count != 0; } public void registerGameObjects(IEnumerable gameObjects) { foreach (var gameObject in gameObjects) registerGameObject(gameObject); } public void registerGameObject(GameObject gameObject) { if (canRegisterGameObject(gameObject)) { AABB objectAABB = ObjectBounds.calcWorldAABB(gameObject, _nodeBoundsQConfig); if (objectAABB.isValid) { Sphere sphere = objectAABB.getEnclosingSphere(); var node = _tree.createLeafNode(sphere.center, sphere.radius, gameObject); _nodeMap.Add(gameObject, node); } } } public void unregisterGameObjects(IEnumerable gameObjects) { foreach (var gameObject in gameObjects) unregisterGameObject(gameObject); } public void unregisterGameObject(GameObject gameObject) { BinarySphereTreeNode objectNode = null; if (_nodeMap.TryGetValue(gameObject, out objectNode)) { _nodeMap.Remove(gameObject); _tree.eraseLeafNode(objectNode); } } public void onGameObjectTransformChanged(GameObject gameObject) { if (!_nodeMap.ContainsKey(gameObject)) return; AABB objectAABB = ObjectBounds.calcWorldAABB(gameObject, _nodeBoundsQConfig); if (objectAABB.isValid) { Sphere sphere = objectAABB.getEnclosingSphere(); var node = _nodeMap[gameObject]; _tree.updateLeafNodeSphere(node, sphere.center, sphere.radius); } } public void handleNullRefs() { foreach (var pair in _nodeMap) { if (pair.Key == null && pair.Value.parent != null) _tree.eraseLeafNode(pair.Value); } var newMap = new Dictionary>(); foreach (var pair in _nodeMap) if (pair.Key != null) newMap.Add(pair.Key, pair.Value); _nodeMap.Clear(); _nodeMap = newMap; } private bool canRegisterGameObject(GameObject gameObject) { if (gameObject.couldBePooled()) return false; if (_nodeMap.ContainsKey(gameObject)) return false; GameObjectType objectType = GameObjectDataDb.instance.getGameObjectType(gameObject); if ((objectType & _recognizedObjectTypes) != 0) return true; return false; } } } #endif