using OctoCore; using UnityEngine; using UnityEngine.Rendering.Universal; namespace Unheimlich.NonEuclidean { public class NonEuclideanManager : MonoBehaviour { public static NonEuclideanManager Instance { get; private set; } [System.Serializable] private class Configuration { public NonEuclideanRoom StartRoom; public LayerMask InFrontOfPortalLayer = 1 << 0; public LayerMask BehindPortalLayer = 1 << 20; } [System.Serializable] private class References { public NonEuclideanRoom[] Rooms; } [System.Serializable] private class State { public NonEuclideanRoom ActiveRoom; public NonEuclideanPortal ActivePortal; } [SerializeField] private Configuration _configuration; [SerializeField] private References _references; [SerializeField, OctoReadOnly] private State _state; private int _bacwardLayer; private int _forwardLayer; private Camera _camera; public int BackwardLayer => _bacwardLayer; public int ForwardLayer => _forwardLayer; public NonEuclideanRoom ActiveRoom => _state.ActiveRoom; public Camera Camera => _camera; public NonEuclideanPortal ActivePortal => _state.ActivePortal; void OnEnable() { if (!Instance) Instance = this; } void OnDisable() { if (Instance == this) Instance = null; } void Start() { _camera = Camera.main; if (_camera == null) { Debug.LogWarning("No MainCamera found"); return; } _bacwardLayer = GetFirstLayerFromMask(_configuration.InFrontOfPortalLayer); _forwardLayer = GetFirstLayerFromMask(_configuration.BehindPortalLayer); SetActiveRoom(_configuration.StartRoom); } void Update() { EvaluatePortals(); } void EvaluatePortals() { bool checkPortals = true; if (_state.ActivePortal) checkPortals = !_state.ActivePortal.IsPortalPassed(); if (checkPortals) { NonEuclideanPortal shouldBeActivatedPortal = _state.ActivePortal; float minDist = float.MaxValue; foreach (NonEuclideanPortal portal in ActiveRoom.Portals) { // Is camera behind portal if (portal.transform.InverseTransformPoint(_camera.transform.position).z < 0) { Vector3 portalDir = (transform.position - _camera.transform.position).normalized; Vector3 viewDir = _camera.transform.forward; float angle = Vector3.Angle(viewDir, portalDir); // Is camera pointing to portal if (angle < 80) { float dist = Vector3.Distance(portal.transform.position, _camera.transform.position); // Is this portal nearest than other portal if (dist < minDist) { minDist = dist; shouldBeActivatedPortal = portal; } } } } if (_state.ActivePortal != shouldBeActivatedPortal) { if (_state.ActivePortal) _state.ActivePortal.Activate(false); _state.ActivePortal = shouldBeActivatedPortal; if (_state.ActivePortal) _state.ActivePortal.Activate(true); ConfigureRooms(); Debug.Log($"[PortalManager] Portal activated: {_state.ActivePortal.name}"); } } } public void SetActiveRoom(NonEuclideanRoom activeRoom) { if (_state.ActivePortal) { _state.ActivePortal.Activate(false); _state.ActivePortal = null; } _state.ActiveRoom = activeRoom; ConfigureRooms(); } private void ConfigureRooms() { // Set active or not rooms foreach (NonEuclideanRoom room in _references.Rooms) { bool isActiveRoom = room == _state.ActiveRoom; bool isTargetRoom = _state.ActivePortal ? room == _state.ActivePortal.TargetRoom : false; room.gameObject.SetActive(isActiveRoom || isTargetRoom); } // Configure Active & Target Rooms ConfigureRoom(_state.ActiveRoom, false); if (_state.ActivePortal) ConfigureRoom(_state.ActivePortal.TargetRoom, true); } private void ConfigureRoom(NonEuclideanRoom room, bool isBehindPortal) { if (room == null) return; int layer = isBehindPortal ? _forwardLayer : _bacwardLayer; SetLayerAndPropertyRecursive(room.gameObject, layer, isBehindPortal); foreach (Light light in room.GetComponentsInChildren()) // TMP { bool isBehind = layer == NonEuclideanManager.Instance.ForwardLayer; if (!light.TryGetComponent(out UniversalAdditionalLightData urp)) urp = light.gameObject.AddComponent(); urp.renderingLayers = 1u << (isBehind ? 1 : 0); light.cullingMask = 1 << layer; } } private void SetLayerAndPropertyRecursive(GameObject obj, int layer, bool isBehindPortal) { if (obj == null) return; int currentLayer = obj.layer; bool shouldPreserveLayer = currentLayer == LayerMask.NameToLayer("UI") || currentLayer == LayerMask.NameToLayer("DetectiveBoard") || currentLayer == LayerMask.NameToLayer("Interactable") || currentLayer == LayerMask.NameToLayer("Player") || currentLayer == LayerMask.NameToLayer("Words") || currentLayer == LayerMask.NameToLayer("Listenable") || currentLayer == LayerMask.NameToLayer("HandsTouch"); if (!shouldPreserveLayer) { obj.layer = layer; } Collider collider = obj.GetComponent(); if (collider) { collider.enabled = !isBehindPortal; } Renderer renderer = obj.GetComponent(); if (renderer && !(renderer is CanvasRenderer)) { /*MaterialPropertyBlock block = new MaterialPropertyBlock(); renderer.GetPropertyBlock(block); block.SetFloat(IsBehindPortalID, isBehindPortal ? 1 : 0); renderer.SetPropertyBlock(block);*/ if (isBehindPortal) renderer.renderingLayerMask = 1u << 1; // Light Layer 1 else renderer.renderingLayerMask = 1u << 0; // Default } foreach (Transform child in obj.transform) SetLayerAndPropertyRecursive(child.gameObject, layer, isBehindPortal); } private int GetFirstLayerFromMask(LayerMask mask) { for (int i = 0; i < 32; i++) { if ((mask & (1 << i)) != 0) return i; } return 0; } } }