split into multiple assemblies
This commit is contained in:
11
Assets/Portal/Runtime/HandPortalDriver.cs
Normal file
11
Assets/Portal/Runtime/HandPortalDriver.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace EscapeRoomEngine.Portal.Runtime
|
||||
{
|
||||
public class HandPortalDriver : PortalDriver
|
||||
{
|
||||
private new void Start()
|
||||
{
|
||||
traveller = transform.parent.parent.parent; // get the hand portal offset
|
||||
base.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Portal/Runtime/HandPortalDriver.cs.meta
Normal file
11
Assets/Portal/Runtime/HandPortalDriver.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee06bb2aa499a594c9761498039d73a7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
13
Assets/Portal/Runtime/PlayerCamera.cs
Normal file
13
Assets/Portal/Runtime/PlayerCamera.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace EscapeRoomEngine.Portal.Runtime
|
||||
{
|
||||
public class PlayerCamera : MonoBehaviour
|
||||
{
|
||||
public new Camera camera;
|
||||
[SerializeField] private Transform leftEye, rightEye;
|
||||
|
||||
public Transform GetEyeTransform(Camera.StereoscopicEye eye) =>
|
||||
eye == Camera.StereoscopicEye.Left ? leftEye : rightEye;
|
||||
}
|
||||
}
|
||||
11
Assets/Portal/Runtime/PlayerCamera.cs.meta
Normal file
11
Assets/Portal/Runtime/PlayerCamera.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b2ceb62217b416743baa8beee7109cfb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
18
Assets/Portal/Runtime/Portal.asmdef
Normal file
18
Assets/Portal/Runtime/Portal.asmdef
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "Portal",
|
||||
"rootNamespace": "EscapeRoomEngine",
|
||||
"references": [
|
||||
"GUID:75469ad4d38634e559750d17036d5f7c",
|
||||
"GUID:15fc0a57446b3144c949da3e2b9737a9",
|
||||
"GUID:fe685ec1767f73d42b749ea8045bfe43"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
3
Assets/Portal/Runtime/Portal.asmdef.meta
Normal file
3
Assets/Portal/Runtime/Portal.asmdef.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ba4c7dba98ca4c31818cc46276b5dea1
|
||||
timeCreated: 1668943888
|
||||
82
Assets/Portal/Runtime/Portal.cs
Normal file
82
Assets/Portal/Runtime/Portal.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace EscapeRoomEngine.Portal.Runtime
|
||||
{
|
||||
[RequireComponent(typeof(Collider))]
|
||||
public class Portal : MonoBehaviour
|
||||
{
|
||||
public static readonly Matrix4x4 HalfRotation = Matrix4x4.Rotate(Quaternion.Euler(0, 180, 0));
|
||||
|
||||
/// <summary>
|
||||
/// The portal that is connected with this one.
|
||||
/// </summary>
|
||||
public Portal linkedPortal;
|
||||
/// <summary>
|
||||
/// The camera that will draw the view for this portal.
|
||||
/// </summary>
|
||||
public PortalCamera portalCamera;
|
||||
|
||||
private readonly List<PortalDriver> _closePortalDrivers = new();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// check whether the other portal is set up
|
||||
if (!linkedPortal || linkedPortal.linkedPortal != this) throw new Exception("Other portal not set up correctly.");
|
||||
|
||||
// check whether the collider is set up correctly
|
||||
if (!GetComponent<Collider>().isTrigger) throw new Exception("Collider must be a trigger.");
|
||||
}
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
for (var i = 0; i < _closePortalDrivers.Count; i++)
|
||||
{
|
||||
var portalDriver = _closePortalDrivers[i];
|
||||
if (portalDriver.entrySide < 0 && CalculateSide(portalDriver.transform) >= 0) // must have entered from the front and exited the back
|
||||
{
|
||||
StopTrackingDriver(portalDriver);
|
||||
linkedPortal.StartTrackingDriver(portalDriver, -1);
|
||||
portalDriver.Teleport(this, linkedPortal);
|
||||
i--; // decrease the loop counter because the list is one element smaller now
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void StartTrackingDriver(PortalDriver portalDriver, int entrySide)
|
||||
{
|
||||
_closePortalDrivers.Add(portalDriver);
|
||||
portalDriver.EnableClone(linkedPortal);
|
||||
portalDriver.entrySide = entrySide;
|
||||
}
|
||||
|
||||
private void StopTrackingDriver(PortalDriver portalDriver)
|
||||
{
|
||||
_closePortalDrivers.Remove(portalDriver);
|
||||
portalDriver.DisableClone(linkedPortal);
|
||||
}
|
||||
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
var portalDriver = other.GetComponent<PortalDriver>();
|
||||
if (portalDriver && !_closePortalDrivers.Contains(portalDriver))
|
||||
{
|
||||
StartTrackingDriver(portalDriver, CalculateSide(portalDriver.transform));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTriggerExit(Collider other)
|
||||
{
|
||||
var portalDriver = other.GetComponent<PortalDriver>();
|
||||
if (portalDriver)
|
||||
StopTrackingDriver(portalDriver);
|
||||
}
|
||||
|
||||
private int CalculateSide(Transform portalDriverTransform)
|
||||
{
|
||||
var t = transform;
|
||||
return Math.Sign(Vector3.Dot(t.forward, portalDriverTransform.position - t.position));
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Portal/Runtime/Portal.cs.meta
Normal file
11
Assets/Portal/Runtime/Portal.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ae59cddbf8fa37549bb38b1039feeb34
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
109
Assets/Portal/Runtime/PortalCamera.cs
Normal file
109
Assets/Portal/Runtime/PortalCamera.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
using UnityEngine.XR;
|
||||
|
||||
namespace EscapeRoomEngine.Portal.Runtime
|
||||
{
|
||||
[RequireComponent(typeof(Camera))]
|
||||
public class PortalCamera : MonoBehaviour
|
||||
{
|
||||
private static readonly Camera.StereoscopicEye[] Eyes =
|
||||
{ Camera.StereoscopicEye.Left, Camera.StereoscopicEye.Right };
|
||||
private static readonly Dictionary<Camera.StereoscopicEye, int> EyeTextureNames = new()
|
||||
{
|
||||
{ Camera.StereoscopicEye.Left, Shader.PropertyToID("_LeftTex") },
|
||||
{ Camera.StereoscopicEye.Right, Shader.PropertyToID("_RightTex") }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The minimum near clip plane distance to be used when calculating the oblique clip plane.
|
||||
/// </summary>
|
||||
public float minNearClipPlane = 0.0001f;
|
||||
/// <summary>
|
||||
/// The portal this camera renders through.
|
||||
/// </summary>
|
||||
[SerializeField] private Portal portal;
|
||||
/// <summary>
|
||||
/// The mesh where the rendered texture will be drawn on.
|
||||
/// </summary>
|
||||
[SerializeField] private MeshRenderer screen;
|
||||
|
||||
private PlayerCamera _playerCamera;
|
||||
private Camera _camera;
|
||||
private readonly Dictionary<Camera.StereoscopicEye, RenderTexture> _textures = new();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// get player camera
|
||||
if (Camera.main != null) _playerCamera = Camera.main.GetComponent<PlayerCamera>();
|
||||
else throw new Exception("Main camera has no player camera script set up.");
|
||||
|
||||
// get portal camera
|
||||
_camera = GetComponent<Camera>();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
RenderPipelineManager.beginCameraRendering += Render;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
RenderPipelineManager.beginCameraRendering -= Render;
|
||||
}
|
||||
|
||||
private void Render(ScriptableRenderContext scriptableRenderContext, Camera _)
|
||||
{
|
||||
// check whether the portal plane is visible from the player camera
|
||||
var frustumPlanes = GeometryUtility.CalculateFrustumPlanes(_playerCamera.camera);
|
||||
if (!GeometryUtility.TestPlanesAABB(frustumPlanes, portal.linkedPortal.portalCamera.screen.bounds))
|
||||
// don't render this portal if it is not visible
|
||||
return;
|
||||
|
||||
var t = portal.transform;
|
||||
|
||||
screen.enabled = false;
|
||||
|
||||
foreach (var eye in Eyes)
|
||||
{
|
||||
// create portal texture if it doesn't exist yet
|
||||
if (!_textures.ContainsKey(eye))
|
||||
{
|
||||
if (XRSettings.eyeTextureWidth > 0 && XRSettings.eyeTextureHeight > 0)
|
||||
{
|
||||
_textures.Add(eye,
|
||||
new RenderTexture(XRSettings.eyeTextureWidth, XRSettings.eyeTextureHeight, 24));
|
||||
portal.linkedPortal.portalCamera.screen.material.SetTexture(EyeTextureNames[eye], _textures[eye]);
|
||||
}
|
||||
else // no texture was created so nothing should be rendered
|
||||
continue;
|
||||
}
|
||||
|
||||
// position portal camera
|
||||
var m = t.localToWorldMatrix * Portal.HalfRotation * portal.linkedPortal.transform.worldToLocalMatrix *
|
||||
_playerCamera.GetEyeTransform(eye).localToWorldMatrix;
|
||||
transform.SetPositionAndRotation(m.GetPosition(), m.rotation);
|
||||
_camera.projectionMatrix = _playerCamera.camera.GetStereoProjectionMatrix(eye);
|
||||
|
||||
// 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 = _camera.worldToCameraMatrix.inverse.transpose *
|
||||
new Vector4(n.x, n.y, n.z, portalPlane.distance); // vector format clip plane in camera space
|
||||
if (-portalPlane.GetDistanceToPoint(transform.position) >= minNearClipPlane)
|
||||
// only adjust the near clip plane if it doesn't intersect with the camera
|
||||
_camera.projectionMatrix = _camera.CalculateObliqueMatrix(clipPlane);
|
||||
|
||||
// render portal view
|
||||
_camera.targetTexture = _textures[eye];
|
||||
UniversalRenderPipeline.RenderSingleCamera(scriptableRenderContext, _camera);
|
||||
}
|
||||
|
||||
screen.enabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Portal/Runtime/PortalCamera.cs.meta
Normal file
11
Assets/Portal/Runtime/PortalCamera.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ad60e3973ab83f3468637a06970d7f1f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
86
Assets/Portal/Runtime/PortalDriver.cs
Normal file
86
Assets/Portal/Runtime/PortalDriver.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace EscapeRoomEngine.Portal.Runtime
|
||||
{
|
||||
[RequireComponent(typeof(Collider), typeof(Rigidbody))]
|
||||
public class PortalDriver : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// The object that will be transported through the portal. Usually either this object or a parent offset object.
|
||||
/// If left empty, it will default to this object.
|
||||
/// </summary>
|
||||
public Transform traveller;
|
||||
/// <summary>
|
||||
/// Whether this portal driver has a clone mirroring it at other portals. Disable this for the player.
|
||||
/// </summary>
|
||||
public bool hasClone = true;
|
||||
|
||||
/// <summary>
|
||||
/// The side of the portal this became tracked on.
|
||||
/// </summary>
|
||||
[HideInInspector] public int entrySide;
|
||||
/// <summary>
|
||||
/// The clone that is used to mirror this portal driver at another portal.
|
||||
/// </summary>
|
||||
[HideInInspector] public PortalDriverClone clone;
|
||||
|
||||
private Collider _collider;
|
||||
private Rigidbody _rigidbody;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// check whether the collider is set up correctly
|
||||
_collider = GetComponent<Collider>();
|
||||
if (_collider.isTrigger) throw new Exception("Collider must not be a trigger.");
|
||||
|
||||
// check whether the traveller is set
|
||||
if (!traveller) traveller = transform;
|
||||
|
||||
// get the rigidbody if there is one
|
||||
_rigidbody = GetComponent<Rigidbody>();
|
||||
}
|
||||
|
||||
protected void Start()
|
||||
{
|
||||
if (hasClone)
|
||||
clone = PortalDriverClone.Create(this);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (hasClone && clone.gameObject.activeSelf)
|
||||
clone.UpdatePosition(transform);
|
||||
}
|
||||
|
||||
public void EnableClone(Portal at)
|
||||
{
|
||||
if (hasClone)
|
||||
{
|
||||
clone.portal = at;
|
||||
clone.gameObject.SetActive(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void DisableClone(Portal at)
|
||||
{
|
||||
if (hasClone && at.Equals(clone.portal)) // don't disable clones that are already at a different portal
|
||||
{
|
||||
clone.portal = null;
|
||||
clone.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void Teleport(Portal from, Portal to)
|
||||
{
|
||||
var m = to.transform.localToWorldMatrix * Portal.HalfRotation * from.transform.worldToLocalMatrix *
|
||||
traveller.localToWorldMatrix;
|
||||
traveller.SetPositionAndRotation(m.GetPosition(), m.rotation);
|
||||
if (_rigidbody)
|
||||
{
|
||||
_rigidbody.velocity = to.transform.TransformDirection(
|
||||
Portal.HalfRotation.rotation * from.transform.InverseTransformDirection(_rigidbody.velocity));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Portal/Runtime/PortalDriver.cs.meta
Normal file
11
Assets/Portal/Runtime/PortalDriver.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1bacec3b1d81cef43972d5dfc26a467b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
44
Assets/Portal/Runtime/PortalDriverClone.cs
Normal file
44
Assets/Portal/Runtime/PortalDriverClone.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem.XR;
|
||||
using UnityEngine.XR.Interaction.Toolkit;
|
||||
|
||||
namespace EscapeRoomEngine.Portal.Runtime
|
||||
{
|
||||
public class PortalDriverClone : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// The portal where this clone is mirroring the portal driver.
|
||||
/// </summary>
|
||||
[HideInInspector] public Portal portal;
|
||||
|
||||
public static PortalDriverClone Create(PortalDriver portalDriver)
|
||||
{
|
||||
// copy the portal driver object
|
||||
var clone = Instantiate(portalDriver).gameObject;
|
||||
|
||||
// destroy all unnecessary components
|
||||
Destroy(clone.GetComponent<PortalDriver>());
|
||||
foreach (var xrGrabInteractable in clone.GetComponentsInChildren<XRGrabInteractable>())
|
||||
Destroy(xrGrabInteractable);
|
||||
foreach (var rigidbody in clone.GetComponentsInChildren<Rigidbody>())
|
||||
Destroy(rigidbody);
|
||||
foreach (var trackedPoseDriver in clone.GetComponentsInChildren<TrackedPoseDriver>())
|
||||
Destroy(trackedPoseDriver);
|
||||
|
||||
// add a clone component
|
||||
clone.AddComponent<PortalDriverClone>();
|
||||
|
||||
// set up the clone
|
||||
var portalDriverClone = clone.GetComponent<PortalDriverClone>();
|
||||
portalDriverClone.gameObject.SetActive(false);
|
||||
return portalDriverClone;
|
||||
}
|
||||
|
||||
public void UpdatePosition(Transform cloning)
|
||||
{
|
||||
var m = portal.transform.localToWorldMatrix * Portal.HalfRotation *
|
||||
portal.linkedPortal.transform.worldToLocalMatrix * cloning.localToWorldMatrix;
|
||||
transform.SetPositionAndRotation(m.GetPosition(), m.rotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Portal/Runtime/PortalDriverClone.cs.meta
Normal file
11
Assets/Portal/Runtime/PortalDriverClone.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 088fae7d750b41e4eb8d333853ffa41a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user