223 lines
7.7 KiB
C#
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;
|
|
}
|
|
} |