Files
Ipseity-Demo/Assets/Unheimlich_POC/Scripts/Utility/PortalCameraUtility.cs
T

129 lines
4.6 KiB
C#

using UnityEngine;
namespace Unheimlich.Utility
{
/// <summary>
/// Utility functions for portal camera rendering and visibility calculations.
/// Used to optimize portal rendering by detecting visibility and overlaps.
/// </summary>
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),
};
/// <summary>
/// Tests if a renderer is visible from the camera's perspective.
/// Uses frustum culling to determine visibility.
/// </summary>
public static bool VisibleFromCamera(Renderer renderer, Camera camera)
{
Plane[] frustumPlanes = GeometryUtility.CalculateFrustumPlanes(camera);
return GeometryUtility.TestPlanesAABB(frustumPlanes, renderer.bounds);
}
/// <summary>
/// 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.
/// </summary>
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;
}
/// <summary>
/// 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).
/// </summary>
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;
}
/// <summary>
/// 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.
/// </summary>
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);
}
}
}
}