using System;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
namespace Escape_Room_Engine.Portal
{
public class Portal : MonoBehaviour
{
private static readonly Matrix4x4 HalfRotation = Matrix4x4.Rotate(Quaternion.Euler(0, 180, 0));
///
/// The portal that is connected with this one.
///
public Portal other;
///
/// The main camera to take the position for the portal camera from.
///
[SerializeField] private Camera playerCamera;
///
/// The camera that will draw the view for this portal.
///
[SerializeField] private Camera portalCamera;
///
/// The quad where the rendered texture will be drawn on.
///
[SerializeField] private MeshRenderer portalQuad;
private RenderTexture _texture;
private void Awake()
{
// check whether the other portal is set up
if (!other || other.other != this) throw new Exception("Other Portal not set up correctly.");
// check whether the player camera is set up
if (!playerCamera) throw new Exception("No camera initialised.");
// create render texture
if (!_texture) _texture = new RenderTexture(Screen.width, Screen.height, 24, RenderTextureFormat.ARGB32);
}
private void Start()
{
// assign render texture
portalCamera.targetTexture = _texture;
other.portalQuad.material.mainTexture = _texture;
}
private void OnEnable()
{
RenderPipelineManager.beginCameraRendering += Render;
}
private void OnDisable()
{
RenderPipelineManager.beginCameraRendering -= Render;
}
private void Render(ScriptableRenderContext scriptableRenderContext, Camera camera)
{
var t = transform;
// position portal camera
var portalCameraMatrix = t.localToWorldMatrix * HalfRotation * other.transform.worldToLocalMatrix *
playerCamera.transform.localToWorldMatrix;
portalCamera.transform.SetPositionAndRotation(portalCameraMatrix.GetPosition(), portalCameraMatrix.rotation);
// set camera clip plane to portal (otherwise the wall behind the portal would be rendered)
// calculating the clip plane: https://computergraphics.stackexchange.com/a/1506
var n = -t.forward; // clip plane normal
var portalPlane = new Plane(n, t.position); // clip plane in world space
var clipPlane = portalCamera.worldToCameraMatrix.inverse.transpose *
new Vector4(n.x, n.y, n.z, portalPlane.distance); // vector format clip plane in camera space
portalCamera.projectionMatrix = playerCamera.CalculateObliqueMatrix(clipPlane);
// render portal view
UniversalRenderPipeline.RenderSingleCamera(scriptableRenderContext, portalCamera);
}
}
}