using UnityEngine; namespace Unheimlich.Utility { /// /// Utility functions for portal camera rendering and visibility calculations. /// Used to optimize portal rendering by detecting visibility and overlaps. /// public static class PortalCameraUtility { private static readonly Vector3[] _cubeCornerOffsets = { new Vector3(1, 1, 1), new Vector3(-1, 1, 1), new Vector3(-1, -1, 1), new Vector3(-1, -1, -1), new Vector3(-1, 1, -1), new Vector3(1, -1, -1), new Vector3(1, 1, -1), new Vector3(1, -1, 1), }; /// /// Tests if a renderer is visible from the camera's perspective. /// Uses frustum culling to determine visibility. /// public static bool VisibleFromCamera(Renderer renderer, Camera camera) { Plane[] frustumPlanes = GeometryUtility.CalculateFrustumPlanes(camera); return GeometryUtility.TestPlanesAABB(frustumPlanes, renderer.bounds); } /// /// Checks if two objects' screen-space bounding boxes overlap when viewed from a camera. /// Used to determine if one portal is being viewed through another portal (recursion). /// Returns true if the far object is visible through/behind the near object. /// public static bool BoundsOverlap(MeshFilter nearObject, MeshFilter farObject, Camera camera) { var near = GetScreenRectFromBounds(nearObject, camera); var far = GetScreenRectFromBounds(farObject, camera); if (far.zMax > near.zMin) { if (far.xMax < near.xMin || far.xMin > near.xMax) return false; if (far.yMax < near.yMin || far.yMin > near.yMax) return false; return true; } return false; } /// /// Converts a mesh's 3D bounding box to screen-space coordinates (viewport space). /// Handles corner cases where parts of the mesh are behind the camera. /// Returns a MinMax3D struct containing the min/max viewport coordinates (x, y) and depth (z). /// public static MinMax3D GetScreenRectFromBounds(MeshFilter renderer, Camera mainCamera) { MinMax3D minMax = new MinMax3D(float.MaxValue, float.MinValue); var localBounds = renderer.sharedMesh.bounds; bool anyPointIsInFrontOfCamera = false; for (int i = 0; i < 8; i++) { Vector3 localSpaceCorner = localBounds.center + Vector3.Scale(localBounds.extents, _cubeCornerOffsets[i]); Vector3 worldSpaceCorner = renderer.transform.TransformPoint(localSpaceCorner); Vector3 viewportSpaceCorner = mainCamera.WorldToViewportPoint(worldSpaceCorner); if (viewportSpaceCorner.z > 0) { anyPointIsInFrontOfCamera = true; } else { viewportSpaceCorner.x = (viewportSpaceCorner.x <= 0.5f) ? 1 : 0; viewportSpaceCorner.y = (viewportSpaceCorner.y <= 0.5f) ? 1 : 0; } minMax.AddPoint(viewportSpaceCorner); } if (!anyPointIsInFrontOfCamera) { return new MinMax3D(); } return minMax; } /// /// Represents a 3D axis-aligned bounding box in viewport/screen space. /// X and Y are in viewport coordinates [0,1], Z is depth from camera. /// public struct MinMax3D { public float xMin; public float xMax; public float yMin; public float yMax; public float zMin; public float zMax; public MinMax3D(float min, float max) { this.xMin = min; this.xMax = max; this.yMin = min; this.yMax = max; this.zMin = min; this.zMax = max; } public void AddPoint(Vector3 point) { xMin = Mathf.Min(xMin, point.x); xMax = Mathf.Max(xMax, point.x); yMin = Mathf.Min(yMin, point.y); yMax = Mathf.Max(yMax, point.y); zMin = Mathf.Min(zMin, point.z); zMax = Mathf.Max(zMax, point.z); } } } }