Files
XRLib/Assets/Runtime Transform Gizmos/Scripts/Runtime Package Common/Scene/SceneTree.cs
2025-06-19 19:24:30 +09:00

239 lines
10 KiB
C#

using UnityEngine;
using System.Collections.Generic;
namespace RTG
{
public class SceneTree
{
private static float _nonMeshObjectSize = 1e-4f;
private SphereTree<GameObject> _objectTree = new SphereTree<GameObject>();
private List<SphereTreeNodeRayHit<GameObject>> _nodeHitBuffer = new List<SphereTreeNodeRayHit<GameObject>>();
private List<SphereTreeNode<GameObject>> _nodeBuffer = new List<SphereTreeNode<GameObject>>();
private Dictionary<GameObject, SphereTreeNode<GameObject>> _objectToNode = new Dictionary<GameObject, SphereTreeNode<GameObject>>();
public GameObjectRayHit RaycastMeshObject(Ray ray, GameObject gameObject)
{
Mesh objectMesh = gameObject.GetMesh();
RTMesh rtMesh = RTMeshDb.Get.GetRTMesh(objectMesh);
if (rtMesh != null)
{
MeshRayHit meshRayHit = rtMesh.Raycast(ray, gameObject.transform.localToWorldMatrix);
if (meshRayHit != null) return new GameObjectRayHit(ray, gameObject, meshRayHit);
}
else
{
// If no RTMesh instance is available, we will cast a ray against
// the object's MeshCollider as a last resort. This is actually useful
// when dealing with static mesh objects. These objects' meshes have
// their 'isReadable' flag set to false and can not be used to create
// an RTMesh instance. Thus a mesh collider is the next best choice.
MeshCollider meshCollider = gameObject.GetComponent<MeshCollider>();
if (meshCollider != null)
{
RaycastHit rayHit;
if (meshCollider.Raycast(ray, out rayHit, float.MaxValue)) return new GameObjectRayHit(ray, rayHit);
}
}
return null;
}
public GameObjectRayHit RaycastSpriteObject(Ray ray, GameObject gameObject)
{
float t;
OBB worldOBB = ObjectBounds.CalcSpriteWorldOBB(gameObject);
if (!worldOBB.IsValid) return null;
if (BoxMath.Raycast(ray, out t, worldOBB.Center, worldOBB.Size, worldOBB.Rotation))
return new GameObjectRayHit(ray, gameObject, worldOBB.GetPointFaceNormal(ray.GetPoint(t)), t);
return null;
}
public bool RaycastAll(Ray ray, SceneRaycastPrecision raycastPresicion, List<GameObjectRayHit> hits)
{
hits.Clear();
if (!_objectTree.RaycastAll(ray, _nodeHitBuffer)) return false;
var boundsQConfig = new ObjectBounds.QueryConfig();
boundsQConfig.ObjectTypes = GameObjectTypeHelper.AllCombined;
boundsQConfig.NoVolumeSize = Vector3Ex.FromValue(_nonMeshObjectSize);
Vector3 camLook = RTFocusCamera.Get.Look;
if (raycastPresicion == SceneRaycastPrecision.BestFit)
{
foreach (var nodeHit in _nodeHitBuffer)
{
GameObject sceneObject = nodeHit.HitNode.Data;
if (sceneObject == null || !sceneObject.activeInHierarchy) continue;
Renderer renderer = sceneObject.GetComponent<Renderer>();
if (renderer != null && !renderer.isVisible) continue;
GameObjectType objectType = sceneObject.GetGameObjectType();
if (objectType == GameObjectType.Mesh)
{
GameObjectRayHit objectHit = RaycastMeshObject(ray, sceneObject);
if (objectHit != null) hits.Add(objectHit);
}
else
if (objectType == GameObjectType.Terrain)
{
TerrainCollider terrainCollider = sceneObject.GetComponent<TerrainCollider>();
if(terrainCollider != null)
{
RaycastHit hitInfo;
if (terrainCollider.Raycast(ray, out hitInfo, float.MaxValue)) hits.Add(new GameObjectRayHit(ray, hitInfo));
}
}
else
if(objectType == GameObjectType.Sprite)
{
GameObjectRayHit objectHit = RaycastSpriteObject(ray, sceneObject);
if (objectHit != null) hits.Add(objectHit);
}
else
{
OBB worldOBB = ObjectBounds.CalcWorldOBB(sceneObject, boundsQConfig);
if (worldOBB.IsValid)
{
float t;
if (BoxMath.Raycast(ray, out t, worldOBB.Center, worldOBB.Size, worldOBB.Rotation))
{
var faceDesc = BoxMath.GetFaceClosestToPoint(ray.GetPoint(t), worldOBB.Center, worldOBB.Size, worldOBB.Rotation, camLook);
var hit = new GameObjectRayHit(ray, sceneObject, faceDesc.Plane.normal, t);
hits.Add(hit);
}
}
}
}
}
else
if (raycastPresicion == SceneRaycastPrecision.Box)
{
foreach (var nodeHit in _nodeHitBuffer)
{
GameObject sceneObject = nodeHit.HitNode.Data;
if (sceneObject == null || !sceneObject.activeInHierarchy) continue;
Renderer renderer = sceneObject.GetComponent<Renderer>();
if (renderer != null && !renderer.isVisible) continue;
OBB worldOBB = ObjectBounds.CalcWorldOBB(sceneObject, boundsQConfig);
if (worldOBB.IsValid)
{
float t;
if (BoxMath.Raycast(ray, out t, worldOBB.Center, worldOBB.Size, worldOBB.Rotation))
{
var faceDesc = BoxMath.GetFaceClosestToPoint(ray.GetPoint(t), worldOBB.Center, worldOBB.Size, worldOBB.Rotation, camLook);
var hit = new GameObjectRayHit(ray, sceneObject, faceDesc.Plane.normal, t);
hits.Add(hit);
}
}
}
}
return hits.Count != 0;
}
public bool OverlapBox(OBB obb, List<GameObject> gameObjects)
{
gameObjects.Clear();
if (!_objectTree.OverlapBox(obb, _nodeBuffer)) return false;
var boundsQConfig = new ObjectBounds.QueryConfig();
boundsQConfig.ObjectTypes = GameObjectTypeHelper.AllCombined;
boundsQConfig.NoVolumeSize = Vector3Ex.FromValue(_nonMeshObjectSize);
foreach (SphereTreeNode<GameObject> node in _nodeBuffer)
{
GameObject sceneObject = (GameObject)node.Data;
if (sceneObject == null || !sceneObject.activeInHierarchy) continue;
OBB worldOBB = ObjectBounds.CalcWorldOBB(sceneObject, boundsQConfig);
if (obb.IntersectsOBB(worldOBB)) gameObjects.Add(sceneObject);
}
return gameObjects.Count != 0;
}
public bool IsObjectRegistered(GameObject gameObject)
{
return _objectToNode.ContainsKey(gameObject);
}
public bool RegisterObject(GameObject gameObject)
{
if (!CanRegisterObject(gameObject)) return false;
var boundsQConfig = new ObjectBounds.QueryConfig();
boundsQConfig.ObjectTypes = GameObjectTypeHelper.AllCombined;
boundsQConfig.NoVolumeSize = Vector3Ex.FromValue(_nonMeshObjectSize);
AABB worldAABB = ObjectBounds.CalcWorldAABB(gameObject, boundsQConfig);
Sphere worldSphere = new Sphere(worldAABB);
SphereTreeNode<GameObject> objectNode = _objectTree.AddNode(gameObject, worldSphere);
_objectToNode.Add(gameObject, objectNode);
RTFocusCamera.Get.SetObjectVisibilityDirty();
return true;
}
public bool UnregisterObject(GameObject gameObject)
{
if (!IsObjectRegistered(gameObject)) return false;
_objectTree.RemoveNode(_objectToNode[gameObject]);
_objectToNode.Remove(gameObject);
RTFocusCamera.Get.SetObjectVisibilityDirty();
return true;
}
public void OnObjectTransformChanged(Transform objectTransform)
{
var boundsQConfig = new ObjectBounds.QueryConfig();
boundsQConfig.ObjectTypes = GameObjectTypeHelper.AllCombined;
boundsQConfig.NoVolumeSize = Vector3Ex.FromValue(_nonMeshObjectSize);
AABB worldAABB = ObjectBounds.CalcWorldAABB(objectTransform.gameObject, boundsQConfig);
Sphere worldSphere = new Sphere(worldAABB);
SphereTreeNode<GameObject> objectNode = _objectToNode[objectTransform.gameObject];
objectNode.Sphere = worldSphere;
_objectTree.OnNodeSphereUpdated(objectNode);
RTFocusCamera.Get.SetObjectVisibilityDirty();
}
public void RemoveNodesWithNullObjects()
{
var newObjectToNodeDictionary = new Dictionary<GameObject, SphereTreeNode<GameObject>>();
foreach (var pair in _objectToNode)
{
if (pair.Key == null) _objectTree.RemoveNode(pair.Value);
else newObjectToNodeDictionary.Add(pair.Key, pair.Value);
}
_objectToNode.Clear();
_objectToNode = newObjectToNodeDictionary;
}
public void DebugDraw()
{
_objectTree.DebugDraw();
}
private bool CanRegisterObject(GameObject gameObject)
{
if (gameObject == null || IsObjectRegistered(gameObject)) return false;
if (gameObject.IsRTGAppObject()) return false;
if (gameObject.GetComponent<RectTransform>() != null) return false;
return true;
}
}
}