2023-05-08 15:56:10 +03:00

223 lines
7.7 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Unity.XR.CoreUtils;
using UnityEngine;
using UnityEngine.XR;
public class Portal : MonoBehaviour
{
public bool allowRender;
private bool _canRender;
private bool _shouldRender = true;
public Portal targetPortal;
public Transform normalVisible;
public Transform normalInvisible;
public Camera portalLCamera;
public Camera portalRCamera;
public Renderer viewThroughRenderer;
private RenderTexture _viewThroughRenderTextureL;
private RenderTexture _viewThroughRenderTextureR;
private Material _viewThroughMaterial;
private Camera _mainCamera;
private Vector4 _vectorPlane;
public bool _shouldTeleport;
private bool ShouldRender(Plane[] cameraPlanes) =>
viewThroughRenderer.isVisible &&
GeometryUtility.TestPlanesAABB(cameraPlanes,
viewThroughRenderer.bounds);
private void Start()
{
// Generate bounding plane
var plane = new Plane(normalVisible.forward, transform.position + normalVisible.forward * -0.01f);
_vectorPlane = new Vector4(plane.normal.x, plane.normal.y, plane.normal.z, plane.distance);
if (allowRender)
{
float scaleX = transform.localScale.x;
float scaleY = transform.localScale.y;
// Create render texture
_viewThroughRenderTextureL =
new RenderTexture((1440), (1600), 24);
_viewThroughRenderTextureL.Create();
_viewThroughRenderTextureR =
new RenderTexture((1440), (1600), 24);
_viewThroughRenderTextureR.Create();
// Assign render texture to portal camera
portalLCamera.targetTexture = _viewThroughRenderTextureL;
portalRCamera.targetTexture = _viewThroughRenderTextureR;
// Assign render texture to portal material (cloned)
_viewThroughMaterial = viewThroughRenderer.material;
_viewThroughMaterial.SetTexture("_TexL", _viewThroughRenderTextureL);
_viewThroughMaterial.SetTexture("_TexR", _viewThroughRenderTextureR);
// Cache the main camera
_mainCamera = Camera.main;
Application.onBeforeRender += OnBeforeRender;
_shouldTeleport = true;
_canRender = true;
}
else
{
portalLCamera.gameObject.SetActive(false);
portalRCamera.gameObject.SetActive(false);
viewThroughRenderer.enabled = false;
}
}
private void OnBeforeRender()
{
if (!_canRender) return;
var cameraPlanes = GeometryUtility.CalculateFrustumPlanes(_mainCamera);
if (!ShouldRender(cameraPlanes))
{
if (!_shouldRender) return;
Debug.Log("Disabling render for " + transform.name);
viewThroughRenderer.material = new Material(Shader.Find("Unlit/Color"));
portalLCamera.enabled = false;
portalRCamera.enabled = false;
_shouldRender = false;
return;
}
if (!_shouldRender)
{
Debug.Log("Enabling render for " + transform.name);
viewThroughRenderer.material = _viewThroughMaterial;
portalLCamera.enabled = true;
portalRCamera.enabled = true;
_shouldRender = true;
}
UpdateCamera(portalLCamera, XRNode.LeftEye);
UpdateCamera(portalRCamera, XRNode.RightEye);
}
private void UpdateCamera(Camera portalCamera, XRNode eye)
{
// Calculate portal camera position and rotation
var virtualPosition = TransformPositionBetweenPortals(this, targetPortal, GetEyeWorldPosition(eye));
var virtualRotation = TransformRotationBetweenPortals(this, targetPortal, GetEyeRotation(eye));
// Position camera
portalCamera.transform.SetPositionAndRotation(virtualPosition, virtualRotation);
// Calculate projection matrix
var clipThroughSpace = Matrix4x4.Transpose(Matrix4x4.Inverse(portalCamera.worldToCameraMatrix)) *
targetPortal._vectorPlane;
// Set portal camera projection matrix to clip walls between target portal and portal camera
// Inherits main camera near/far clip plane and FOV settings
portalCamera.projectionMatrix =
CalculateObliqueMatrix(
_mainCamera.GetStereoProjectionMatrix(eye == XRNode.LeftEye
? Camera.StereoscopicEye.Left
: Camera.StereoscopicEye.Right), clipThroughSpace);
}
private void OnDestroy()
{
if (!_canRender) return;
Application.onBeforeRender -= OnBeforeRender;
// Release render texture from GPU
_viewThroughRenderTextureL.Release();
_viewThroughRenderTextureR.Release();
// Destroy cloned material and render texture
Destroy(_viewThroughMaterial);
Destroy(_viewThroughRenderTextureL);
Destroy(_viewThroughRenderTextureR);
}
private static Vector3 TransformPositionBetweenPortals(Portal sender, Portal target, Vector3 position)
{
return
target.normalInvisible.TransformPoint(
sender.normalVisible.InverseTransformPoint(position));
}
private static Quaternion TransformRotationBetweenPortals(Portal sender, Portal target, Quaternion rotation)
{
return
target.normalInvisible.rotation *
Quaternion.Inverse(sender.normalVisible.rotation) *
rotation;
}
private Vector3 GetEyeWorldPosition(XRNode eye)
{
if (!XRSettings.enabled) return _mainCamera.transform.position;
InputDevice device = InputDevices.GetDeviceAtXRNode(eye);
if (!device.isValid) return default;
float cameraSeparation = _mainCamera.stereoSeparation;
return _mainCamera.transform.position + _mainCamera.transform.right *
(eye == XRNode.LeftEye ? -cameraSeparation : cameraSeparation) / 2;
}
private Quaternion GetEyeRotation(XRNode _)
{
return _mainCamera.transform.rotation;
}
static Matrix4x4 CalculateObliqueMatrix(Matrix4x4 projection, Vector4 clipPlane)
{
Matrix4x4 obliqueMatrix = projection;
Vector4 q = projection.inverse * new Vector4(
Math.Sign(clipPlane.x),
Math.Sign(clipPlane.y),
1.0f,
1.0f
);
Vector4 c = clipPlane * (2.0F / (Vector4.Dot(clipPlane, q)));
obliqueMatrix[2] = c.x - projection[3];
obliqueMatrix[6] = c.y - projection[7];
obliqueMatrix[10] = c.z - projection[11];
obliqueMatrix[14] = c.w - projection[15];
return obliqueMatrix;
}
private void OnTriggerEnter(Collider other)
{
if (!_canRender || !_shouldTeleport) return;
if (!other.CompareTag("Player")) return;
Debug.Log(transform.name + " player entered and should teleport");
targetPortal._shouldTeleport = false;
// Move player to target portal collider relative position
other.transform.position = TransformPositionBetweenPortals(this, targetPortal, other.transform.position);
other.transform.rotation = TransformRotationBetweenPortals(this, targetPortal, other.transform.rotation);
}
private void OnTriggerExit(Collider other)
{
if (!_canRender || !_shouldTeleport || IsInvoking(nameof(AllowTeleport))) return;
if (!other.CompareTag("Player")) return;
Debug.Log(transform.name + " player exited");
Invoke(nameof(AllowTeleport), 1f);
}
private void AllowTeleport()
{
_shouldTeleport = true;
}
}