
namespace PlanarReflections5 {

    /*
    * PIDI - Planar Reflections™ 5 - Copyright© 2017-2024
    * PIDI - Planar Reflections is a trademark and copyrighted property of Jorge Pinal Negrete.

    * You cannot sell, redistribute, share nor make public this code, modified or not, in part nor in whole, through any
    * means on any platform except with the purpose of contacting the developers to request support and only when taking
    * all pertinent measures to avoid its release to the public and / or any unrelated third parties.
    * Modifications are allowed only for internal use within the limits of your Unity based projects and cannot be shared,
    * published, redistributed nor made available to any third parties unrelated to Irreverent Software by any means.
    *
    * For more information, contact us at support@irreverent-software.com
    *
    */

    using UnityEngine.Rendering;
    using UnityEngine.Rendering.Universal;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;

    [System.Serializable]
    public class PlanarReflectionSettings {

        public float nearClipPlane = 0.03f;

        public float farClipPlane = 1000;

        public LayerMask reflectLayers = 1;

        public int mainRenderPassIndex = 0;

        [Range( 0, 1 )] public float customLODBias = 1.0f;

        public int maxLODLevel;

        public bool renderShadows = true;

        public bool usePostFX = true;

        public LayerMask postFXVolumeMask;

        public bool accurateMatrix = true;

        public string camerasPrefix;

        public bool screenBasedResolution = true;

        public Vector2 explicitResolution = new Vector2( 512, 512 );

        [Range( 0.1f, 2.0f )] public float outputResolutionMultiplier = 1.0f;

        [Range( 0, 60 )] public int reflectionFramerate = 0;

        public bool useMipMaps = true;

        public bool useAntialiasing = false;

        public bool clearToColor = false;

        public Color backgroundColor = Color.blue;

        public bool renderDepth = false;

        public bool renderFog;

        public int fogRendererIndex = 1;

        public bool forceHDR = true;

        public bool updateOnCastOnly = true;

        public bool framerateByDistance = true;

        public float framerateThreshold = 20;

        public Material customSkybox;

#if UPDATE_PLANAR3

        #region LEGACY_API
        /// <summary>
        /// This is a legacy method and will be deprecated soon. Please use nearClipPlane instead.
        /// <br>Controls the near clip plane value of the virtual camera that renders the reflection. When using accurate matrices it is ignored and the reflective surface's plane is used instead.</br>
        /// </summary>
        public float nearClipDistance { get { return nearClipPlane; } set { nearClipPlane = value; } }

        public float farClipDistance { get { return farClipPlane; } set { farClipPlane = value; } }

        public bool customShadowDistance { get { return renderShadows; } }

        public float shadowDistance { get { return 50f; } }

        public int targetFramerate { get { return reflectionFramerate; } set { reflectionFramerate = value; } }

        public bool useDepth { get { return renderDepth; } set { renderDepth = value; } }

        public string camerasPrefix { get { return camerasPrefix; } set { camerasPrefix = value; } }

        public bool trackCamerasWithTag { get { return !string.IsNullOrEmpty( camerasPrefix ); } }

        public PlanarReflections3.ReflectionClipMode reflectionClipMode {
            get { return accurateMatrix ? PlanarReflections3.ReflectionClipMode.AccurateClipping : PlanarReflections3.ReflectionClipMode.SimpleApproximation; }
            set { accurateMatrix = value == PlanarReflections3.ReflectionClipMode.AccurateClipping; }
        }

        public PlanarReflections3.ResolutionMode resolutionMode {
            get { return screenBasedResolution ? PlanarReflections3.ResolutionMode.ScreenBased : PlanarReflections3.ResolutionMode.ExplicitValue; }
            set { screenBasedResolution = value == PlanarReflections3.ResolutionMode.ScreenBased; }
        }

        public bool useCustomClearFlags { get { return true; } }

        public int clearFlags { get { return clearToColor ? 1 : 0; } set { clearToColor = value == 1; } }

        public bool forceFloatOutput { get { return forceHDR; } set { forceHDR = value; } }

        #endregion
#endif

#if UPDATE_PLANAR3

        public static implicit operator PlanarReflectionSettings( PlanarReflections3.ReflectionSettings source ) {

            var newSettings = new PlanarReflectionSettings();

            newSettings.nearClipPlane = source.nearClipDistance;
            newSettings.farClipPlane = source.farClipDistance;
            newSettings.reflectLayers = source.reflectLayers;
            newSettings.renderShadows = !source.customShadowDistance || source.shadowDistance > 0.1f;
            newSettings.reflectionFramerate = source.targetFramerate;
            newSettings.usePostFX = source.usePostFX;
            newSettings.explicitResolution = source.explicitResolution;
            newSettings.renderDepth = source.useDepth;
            newSettings.useMipMaps = source.useMipMaps;
            newSettings.useAntialiasing = source.useAntialiasing;
            newSettings.clearToColor = source.useCustomClearFlags && source.clearFlags == 1;
            newSettings.camerasPrefix = source.trackCamerasWithTag ? source.camerasPrefix : "";
            newSettings.outputResolutionMultiplier = source.resolutionDownscale;
            newSettings.forceHDR = source.forceFloatOutput;
            newSettings.backgroundColor = source.backgroundColor;
            newSettings.accurateMatrix = source.reflectionClipMode == PlanarReflections3.ReflectionClipMode.AccurateClipping;
            newSettings.screenBasedResolution = source.resolutionMode == PlanarReflections3.ResolutionMode.ScreenBased;

            return newSettings;

        }

#endif


    }

    [System.Serializable]
    public class ReflectionData {
        protected Camera _reflectionCam;
        public Camera ReflectionCamera { get { return _reflectionCam; } }

        public UniversalAdditionalCameraData UniversalData;

        public RenderTexture _reflectionTex;

        public RenderTexture _reflectionDepth;

        public RenderTexture _reflectionFog;

        public Vector2Int screenRes;

        public int recursionLevel = 0;

        public float timer;


        public void ForceSetCamera( Camera cam ) {
            _reflectionCam = cam;
            UniversalData = _reflectionCam.GetUniversalAdditionalCameraData();
        }

        public ReflectionData( Camera cam, PlanarReflectionSettings settings ) {
            _reflectionCam = cam;
            _reflectionTex = RenderTexture.GetTemporary( 1, 1 );
            _reflectionDepth = RenderTexture.GetTemporary( 1, 1 );
            _reflectionFog = RenderTexture.GetTemporary( 1, 1 );
            screenRes = new Vector2Int( Screen.width, Screen.height );

            UniversalData = _reflectionCam.GetUniversalAdditionalCameraData();

            RegenerateTextures( settings );

        }

        public void RegenerateTextures( PlanarReflectionSettings settings ) {

            RenderTexture.ReleaseTemporary( _reflectionTex );
            RenderTexture.ReleaseTemporary( _reflectionDepth );
            RenderTexture.ReleaseTemporary( _reflectionFog );

            var rd = new RenderTextureDescriptor( Mathf.RoundToInt( settings.outputResolutionMultiplier * ( settings.screenBasedResolution ? Screen.width : settings.explicitResolution.x ) ), Mathf.RoundToInt( settings.outputResolutionMultiplier * ( settings.screenBasedResolution ? Screen.height : settings.explicitResolution.y ) ) );
            rd.useMipMap = settings.useMipMaps;
            rd.msaaSamples = 1;
            rd.colorFormat = settings.forceHDR ? RenderTextureFormat.DefaultHDR : RenderTextureFormat.Default;
            rd.depthBufferBits = 16;
            rd.autoGenerateMips = true;
            rd.volumeDepth = 1;
            rd.vrUsage = VRTextureUsage.None;
            rd.dimension = UnityEngine.Rendering.TextureDimension.Tex2D;
            rd.mipCount = 6;
            _reflectionTex = RenderTexture.GetTemporary( rd );
            _reflectionTex.filterMode = FilterMode.Bilinear;
            if ( settings.renderDepth ) {
                rd.colorFormat = RenderTextureFormat.Depth;
                _reflectionDepth = RenderTexture.GetTemporary( rd );
                _reflectionDepth.filterMode = FilterMode.Bilinear;
                _reflectionDepth.name = "_REFDEPTHP4";
            }
            else {
                RenderTexture.ReleaseTemporary( _reflectionDepth );
            }

            if ( settings.renderFog ) {
                rd.colorFormat = RenderTextureFormat.Default;
                _reflectionFog = RenderTexture.GetTemporary( rd );
                _reflectionFog.filterMode = FilterMode.Bilinear;
                _reflectionFog.name = "_REFFOGP4";
            }
            else {
                RenderTexture.ReleaseTemporary( _reflectionFog );
            }
            screenRes = new Vector2Int( Screen.width, Screen.height );
            _reflectionTex.name = "_REFTEXP4";

        }





    }

    [HelpURL( "https://irreverent-software.com/docs/pidi-planar-reflections-5/getting-started/installation/" )]
    [ExecuteAlways]
    public class PlanarReflectionRenderer : MonoBehaviour {


#if UNITY_2023_3_OR_NEWER
        protected UniversalRenderPipeline.SingleCameraRequest rq = new UniversalRenderPipeline.SingleCameraRequest();
#endif
        Camera[] _allCameras = new Camera[0];




#if UNITY_EDITOR

        public bool showAdvancedSettings;

        public Mesh defaultReflectorMesh;

        public Material defaultReflectorMaterial;

        public bool showPreviewReflector;

        Vector3 _sceneViewOriginalPos = new Vector3( Mathf.Infinity, 0, 0 );

        public string Version { get { return "5.1.5"; } }

#endif

        [SerializeField] protected PlanarReflectionSettings _settings = new PlanarReflectionSettings();

        public PlanarReflectionSettings Settings { get { return _settings; } }

        public RenderTexture externalReflectionTex;

        public RenderTexture externalReflectionDepth;

        protected Dictionary<Camera, ReflectionData> _reflectionData = new Dictionary<Camera, ReflectionData>();

        private List<Camera> _reflectionCameras = new List<Camera>( new Camera[1] );


        private Camera[] updateCams = new Camera[0];

#if UNITY_EDITOR

        protected ReflectionData _sceneReflection;

        private MaterialPropertyBlock _sceneReflectorMatBlock;

#endif


        public Texture GetReflection( Camera cam ) {

            if ( cam.cameraType == CameraType.SceneView ) {
#if UNITY_EDITOR
                if ( externalReflectionTex ) {
                    return _sceneReflection != null && _sceneReflection._reflectionTex != null ? (Texture)externalReflectionTex : Texture2D.blackTexture;
                }
                return _sceneReflection != null && _sceneReflection._reflectionTex != null ? (Texture)_sceneReflection._reflectionTex : Texture2D.blackTexture;
#else
                return Texture2D.blackTexture;
#endif
            }
            else {

                if ( externalReflectionTex ) {
                    return _reflectionData.ContainsKey( cam ) ? (Texture)externalReflectionTex : Texture2D.blackTexture;
                }

                if ( _reflectionData.ContainsKey( cam ) ) {
                    return _reflectionData[cam]._reflectionTex;
                }
                else {
                    return Texture2D.blackTexture;
                }
            }


        }


        public Texture GetReflectionDepth( Camera cam ) {

            if ( cam.cameraType == CameraType.SceneView ) {
#if UNITY_EDITOR
                return _sceneReflection != null && _sceneReflection._reflectionDepth != null ? (Texture)_sceneReflection._reflectionDepth : Texture2D.whiteTexture;
#else
                return Texture2D.blackTexture;
#endif
            }
            else {
                if ( _reflectionData.ContainsKey( cam ) ) {
                    return _reflectionData[cam]._reflectionDepth ? (Texture)_reflectionData[cam]._reflectionDepth : Texture2D.whiteTexture;
                }
                else {
                    return Texture2D.blackTexture;
                }
            }


        }


        public Texture GetReflectionFog( Camera cam ) {

            if ( cam.cameraType == CameraType.SceneView ) {
#if UNITY_EDITOR
                return _sceneReflection != null && _sceneReflection._reflectionFog != null ? (Texture)_sceneReflection._reflectionFog : Texture2D.blackTexture;
#else
                return Texture2D.blackTexture;
#endif
            }
            else {
                if ( _reflectionData.ContainsKey( cam ) ) {
                    return _reflectionData[cam]._reflectionFog;
                }
                else {
                    return Texture2D.blackTexture;
                }
            }


        }



#if UNITY_EDITOR
        [UnityEditor.MenuItem( "GameObject/Effects/Planar Reflections 5/Create Reflections Renderer", priority = -99 )]
        public static void CreateReflectionsRendererObject() {

            var reflector = new GameObject( "Reflection Renderer", typeof( PlanarReflectionRenderer ) );
            reflector.transform.position = Vector3.zero;
            reflector.transform.rotation = Quaternion.identity;

        }

        [UnityEditor.Callbacks.DidReloadScripts]
        private static void OnScriptsReloaded() {
            var cams = Resources.FindObjectsOfTypeAll<Camera>();

            foreach ( Camera cam in cams ) {
                if ( cam.name.Contains( "_URPReflectionCamera" ) ) {
#if !UNITY_2018_3_OR_NEWER
                    DestroyImmediate( cam.targetTexture );
#else
                    RenderTexture.ReleaseTemporary( cam.targetTexture );
#endif
                    cam.targetTexture = null;
                    DestroyImmediate( cam.gameObject );
                }
            }
        }

#endif




        public void OnEnable() {

            _reflectionCameras = new List<Camera>( new Camera[1] );

#if UNITY_EDITOR


#if UNITY_2018_1_OR_NEWER
            UnityEditor.EditorApplication.QueuePlayerLoopUpdate();
            UnityEditor.SceneView.RepaintAll();
#endif


#endif


#if UPDATE_PLANAR3
            if ( GetComponent<PlanarReflections3.PlanarReflectionsRenderer>() ) {
                _settings = GetComponent<PlanarReflections3.PlanarReflectionsRenderer>().Settings;
            }
#endif


            //Camera.onPreCull += RenderReflection;


#if UNITY_2023_3_OR_NEWER
            RenderPipelineManager.beginContextRendering -= RenderAllCameras;
            RenderPipelineManager.beginContextRendering += RenderAllCameras;
#endif

#if !UNITY_2023_3_OR_NEWER
            RenderPipelineManager.beginFrameRendering += RenderAllCameras;
#else
#if UNITY_EDITOR && !UNITY_2023_3_OR_NEWER
            UnityEditor.EditorApplication.update += UpdateSceneReflections;
#endif
            RenderPipelineManager.beginCameraRendering -= DrawPreview;
            RenderPipelineManager.beginCameraRendering += DrawPreview;

#endif

        }

        public void OnDisable() {

#if UNITY_EDITOR
            UnityEditor.Undo.undoRedoPerformed -= ApplySettings;

#endif

#if UNITY_2023_3_OR_NEWER
            RenderPipelineManager.beginContextRendering -= RenderAllCameras;
#endif

#if !UNITY_2023_3_OR_NEWER
            UnityEngine.Rendering.RenderPipelineManager.beginFrameRendering -= RenderAllCameras;
#else
            RenderPipelineManager.beginCameraRendering -= DrawPreview;

#if UNITY_EDITOR && !UNITY_2023_3_OR_NEWER
            UnityEditor.EditorApplication.update -= UpdateSceneReflections;
#endif

#endif

            foreach ( KeyValuePair<Camera, ReflectionData> pair in _reflectionData ) {
                RenderTexture.ReleaseTemporary( pair.Value._reflectionTex );
                RenderTexture.ReleaseTemporary( pair.Value._reflectionDepth );
                RenderTexture.ReleaseTemporary( pair.Value._reflectionFog );
            }

            var cams = _reflectionCameras.ToArray();

            for ( int i = 0; i < cams.Length; i++ ) {
                if ( cams[i] ) {
                    DestroyImmediate( cams[i].gameObject );
                }
            }

            _reflectionCameras.Clear();
            _reflectionCameras.Add( null );
            _reflectionData.Clear();
#if UNITY_EDITOR
            _sceneReflection = default;
#endif

        }


        public void ApplySettings() {

            foreach ( KeyValuePair<Camera, ReflectionData> pair in _reflectionData ) {
                _reflectionData[pair.Key].RegenerateTextures( _settings );
            }

#if UNITY_EDITOR
            if ( _sceneReflection != null )
                _sceneReflection.RegenerateTextures( _settings );
#endif

        }

#if UNITY_2023_3_OR_NEWER
        void RenderAllCameras( ScriptableRenderContext context, List<Camera> cams ) {
            for ( int i = 0; i < cams.Count; i++ ) {
                RenderURPReflection( context, cams[i] );
            }

        }
#else
        void RenderAllCameras(  ScriptableRenderContext context, Camera[] cams ) {
        
            for (int i = 0; i < cams.Length; i++ ) {
                RenderURPReflection( context, cams[i] );
            }

        }
#endif


        void DrawPreview( ScriptableRenderContext context, Camera cam ) {
#if UNITY_EDITOR
            if ( cam.cameraType == CameraType.SceneView && showPreviewReflector ) {
                DrawReflectorMesh( cam, _sceneReflection );
            }
#endif
        }



        public void SyncToCamera( Camera refCamera, Camera cam ) {

            refCamera.CopyFrom( cam );
            refCamera.depth = cam.depth + 99;
            refCamera.allowHDR = cam.allowHDR;
            refCamera.allowMSAA = false;
            refCamera.useOcclusionCulling = false;
            refCamera.cullingMask = _settings.reflectLayers;
            refCamera.clearFlags = _settings.clearToColor ? CameraClearFlags.SolidColor : CameraClearFlags.Skybox;
            refCamera.backgroundColor = _settings.backgroundColor;
            refCamera.cameraType = CameraType.Game;
            refCamera.renderingPath = cam.renderingPath;

            refCamera.useOcclusionCulling = false;

            refCamera.targetTexture = externalReflectionTex ? externalReflectionTex : _reflectionData[cam]._reflectionTex;




            var uData = refCamera.GetUniversalAdditionalCameraData();


            uData.renderType = CameraRenderType.Base;
            Vector3 worldSpaceViewDir = cam.transform.forward;
            Vector3 worldSpaceViewUp = cam.transform.up;
            Vector3 worldSpaceCamPos = cam.transform.position;

            Vector3 planeSpaceViewDir = transform.InverseTransformDirection( worldSpaceViewDir );
            Vector3 planeSpaceViewUp = transform.InverseTransformDirection( worldSpaceViewUp );
            Vector3 planeSpaceCamPos = transform.InverseTransformPoint( worldSpaceCamPos );

            planeSpaceViewDir.y *= -1.0f;
            planeSpaceViewUp.y *= -1.0f;
            planeSpaceCamPos.y *= -1.0f;

            worldSpaceViewDir = transform.TransformDirection( planeSpaceViewDir );
            worldSpaceViewUp = transform.TransformDirection( planeSpaceViewUp );
            worldSpaceCamPos = transform.TransformPoint( planeSpaceCamPos );

            refCamera.transform.position = worldSpaceCamPos;
            refCamera.transform.LookAt( worldSpaceCamPos + worldSpaceViewDir, worldSpaceViewUp );

            refCamera.nearClipPlane = _settings.nearClipPlane;
            refCamera.farClipPlane = _settings.farClipPlane;

            refCamera.rect = new Rect( 0, 0, 1, 1 );

            refCamera.aspect = cam.aspect;


            if ( _settings.accurateMatrix ) {
                refCamera.projectionMatrix = refCamera.CalculateObliqueMatrix( CameraSpacePlane( refCamera, transform.position, transform.up ) );
            }

        }



        public void RenderURPReflection( ScriptableRenderContext context, Camera cam ) {

            if ( !string.IsNullOrEmpty( _settings.camerasPrefix ) ) {
                if ( cam.cameraType != CameraType.SceneView && !cam.name.Contains( _settings.camerasPrefix ) ) {
                    return;
                }
            }


            if ( !_reflectionData.ContainsKey( cam )
#if UNITY_EDITOR
                ||
                _sceneReflection == null ||
                !_sceneReflection.ReflectionCamera
#endif
                ) {

#if UNITY_EDITOR

                if ( cam.cameraType == CameraType.SceneView ) {

                    if ( _sceneReflection == null ) {
                        var rCam = new GameObject( "_URPSceneReflectionCamera", typeof( Camera ), typeof( UniversalAdditionalCameraData ) );

                        _reflectionCameras[0] = rCam.GetComponent<Camera>();

                        rCam.hideFlags = HideFlags.HideAndDontSave;
                        rCam.GetComponent<Camera>().enabled = false;
                        rCam.GetComponent<Camera>().cameraType = CameraType.Game;



                        _sceneReflection = new ReflectionData( _reflectionCameras[0], _settings );
                    }
                    else {
                        if ( !_sceneReflection.ReflectionCamera ) {
                            var rCam = new GameObject( "_URPSceneReflectionCamera", typeof( Camera ), typeof( UniversalAdditionalCameraData ) );

                            _reflectionCameras[0] = rCam.GetComponent<Camera>();



                            rCam.hideFlags = HideFlags.HideAndDontSave;
                            rCam.GetComponent<Camera>().enabled = false;
                            rCam.GetComponent<Camera>().cameraType = CameraType.Game;
                        }

                        _sceneReflection.ForceSetCamera( _reflectionCameras[0] );
                    }

                    //var rCam = new GameObject( "_URPReflectionCamera", typeof( Camera ), typeof( Skybox ), typeof(UniversalAdditionalCameraData) );


                }
                else

#endif
                if ( cam.cameraType == CameraType.Game && !_reflectionData.ContainsKey( cam ) && !cam.gameObject.name.Contains( "ReflectionCamera" ) && cam.gameObject.hideFlags == HideFlags.None ) {
                    var rCam = new GameObject( "_URPGameReflectionCamera", typeof( Camera ), typeof( UniversalAdditionalCameraData ) );

                    _reflectionCameras.Add( rCam.GetComponent<Camera>() );

                    rCam.hideFlags = HideFlags.HideAndDontSave;
                    rCam.GetComponent<Camera>().enabled = false;
                    rCam.GetComponent<Camera>().cameraType = CameraType.Game;

                    _reflectionData.Add( cam, new ReflectionData( rCam.GetComponent<Camera>(), _settings ) );
                }


#if UNITY_EDITOR
                if ( cam.cameraType == CameraType.SceneView ) {
                    if ( _sceneReflection == null ) {
                        _sceneReflection = new ReflectionData( _reflectionCameras[0], _settings );
                    }
                    else {
                        _sceneReflection.ForceSetCamera( _reflectionCameras[0] );
                    }

                }
                else
#endif
                if ( cam.cameraType == CameraType.Game && !_reflectionData.ContainsKey( cam ) && !cam.gameObject.name.Contains( "ReflectionCamera" ) && cam.gameObject.hideFlags == HideFlags.None ) {
                    _reflectionData.Add( cam, new ReflectionData( _reflectionCameras[0], _settings ) );
                }
                else if ( cam.gameObject.hideFlags != HideFlags.None ) {
                    return;
                }
            }


            Plane reflectionPlane = new Plane( transform.up, transform.position );

            if ( Mathf.Abs( Vector3.Dot( transform.up, cam.transform.forward ) ) < 0.01f && ( cam.orthographic || reflectionPlane.GetDistanceToPoint( cam.transform.position ) < 0.025f ) ) {
                return;
            }

#if UNITY_EDITOR

            var isSceneCam = cam.cameraType == CameraType.SceneView;


            if ( !_reflectionData.ContainsKey( cam ) && !isSceneCam ) {
                return;
            }


            if ( isSceneCam ) {
                if ( Screen.width != _sceneReflection.screenRes.x || Screen.height != _sceneReflection.screenRes.y ) {
                    _sceneReflection.RegenerateTextures( _settings );
                }
            }
            else {
                if ( Screen.width != _reflectionData[cam].screenRes.x || Screen.height != _reflectionData[cam].screenRes.y ) {
                    _reflectionData[cam].RegenerateTextures( _settings );
                }
            }



            var currentData = isSceneCam ? _sceneReflection : _reflectionData[cam];
#else
            if ( Screen.width != _reflectionData[cam].screenRes.x || Screen.height != _reflectionData[cam].screenRes.y ) {
                _reflectionData[cam].RegenerateTextures( _settings );
            }

            var currentData = _reflectionData[cam];
#endif
            var refCamera = currentData.ReflectionCamera;

            refCamera.CopyFrom( cam );
            refCamera.depth = cam.depth + 99;
            refCamera.allowHDR = cam.allowHDR;
            refCamera.allowMSAA = false;
            refCamera.useOcclusionCulling = false;
            refCamera.cullingMask = _settings.reflectLayers;
            refCamera.clearFlags = _settings.clearToColor ? CameraClearFlags.SolidColor : CameraClearFlags.Skybox;
            refCamera.backgroundColor = _settings.backgroundColor;
            refCamera.cameraType = CameraType.Game;
            refCamera.renderingPath = cam.renderingPath;

            refCamera.useOcclusionCulling = false;

            refCamera.targetTexture = externalReflectionTex ? externalReflectionTex : currentData._reflectionTex;


            var uData = refCamera.GetUniversalAdditionalCameraData();

            uData.renderType = CameraRenderType.Base;
            Vector3 worldSpaceViewDir = cam.transform.forward;
            Vector3 worldSpaceViewUp = cam.transform.up;
            Vector3 worldSpaceCamPos = cam.transform.position;

            Vector3 planeSpaceViewDir = transform.InverseTransformDirection( worldSpaceViewDir );
            Vector3 planeSpaceViewUp = transform.InverseTransformDirection( worldSpaceViewUp );
            Vector3 planeSpaceCamPos = transform.InverseTransformPoint( worldSpaceCamPos );

            planeSpaceViewDir.y *= -1.0f;
            planeSpaceViewUp.y *= -1.0f;
            planeSpaceCamPos.y *= -1.0f;

            worldSpaceViewDir = transform.TransformDirection( planeSpaceViewDir );
            worldSpaceViewUp = transform.TransformDirection( planeSpaceViewUp );
            worldSpaceCamPos = transform.TransformPoint( planeSpaceCamPos );

            refCamera.transform.position = worldSpaceCamPos;
            refCamera.transform.LookAt( worldSpaceCamPos + worldSpaceViewDir, worldSpaceViewUp );

            refCamera.nearClipPlane = _settings.nearClipPlane;
            refCamera.farClipPlane = _settings.farClipPlane;

            refCamera.rect = new Rect( 0, 0, 1, 1 );

            refCamera.aspect = cam.aspect;

            uData.SetRenderer( _settings.mainRenderPassIndex );

            if ( _settings.accurateMatrix ) {
                refCamera.projectionMatrix = refCamera.CalculateObliqueMatrix( CameraSpacePlane( refCamera, transform.position, transform.up ) );
            }


            var tempLOD = QualitySettings.lodBias;
            var maxLod = QualitySettings.maximumLODLevel;

            QualitySettings.lodBias *= _settings.customLODBias;
            QualitySettings.maximumLODLevel = _settings.maxLODLevel;


            uData.renderShadows = _settings.renderShadows;
            uData.volumeLayerMask = _settings.postFXVolumeMask;
            uData.antialiasing = _settings.useAntialiasing ? AntialiasingMode.FastApproximateAntialiasing : AntialiasingMode.None;

            if (
#if UNITY_EDITOR
            !Application.isPlaying ||
#endif
            ( Time.realtimeSinceStartup > currentData.timer) ) {


                if ( _settings.renderFog
#if UNITY_EDITOR
            && !isSceneCam
#endif
                    ) {
                    refCamera.targetTexture = currentData._reflectionFog;
                    refCamera.depthTextureMode = DepthTextureMode.None;
                    uData.renderPostProcessing = false;

                    uData.SetRenderer( _settings.fogRendererIndex );
#if UNITY_2023_3_OR_NEWER
                    rq.destination = refCamera.targetTexture;
                    var clearF = refCamera.clearFlags;
                    var clearCol = refCamera.backgroundColor;
                    refCamera.clearFlags = CameraClearFlags.Color;
                    refCamera.backgroundColor = Color.clear;
                    refCamera.targetTexture = null;
                    UniversalRenderPipeline.SubmitRenderRequest( refCamera, rq );
                    refCamera.targetTexture = rq.destination;
                    refCamera.clearFlags = clearF;
                    refCamera.backgroundColor = clearCol;
#else
                    var clearF = refCamera.clearFlags;
                    var clearCol = refCamera.backgroundColor;
                    refCamera.clearFlags = CameraClearFlags.Color;
                    refCamera.backgroundColor = Color.clear;
                    UniversalRenderPipeline.RenderSingleCamera( context, refCamera );
                    refCamera.clearFlags = clearF;
                    refCamera.backgroundColor = clearCol;
#endif
                    uData.SetRenderer( 0 );
                }


                if ( _settings.renderDepth
#if UNITY_EDITOR
            && !isSceneCam
#endif
                    ) {
                    refCamera.targetTexture = currentData._reflectionDepth;
                    refCamera.depthTextureMode = DepthTextureMode.Depth;

                    uData.renderPostProcessing = false;

                    uData.requiresColorOption = CameraOverrideOption.Off;
                    uData.renderShadows = false;


#if UNITY_2023_3_OR_NEWER
                    rq.destination = refCamera.targetTexture;
                    refCamera.targetTexture = null;
                    UniversalRenderPipeline.SubmitRenderRequest( refCamera, rq );
                    refCamera.targetTexture = rq.destination;
#else
                    UniversalRenderPipeline.RenderSingleCamera( context, refCamera );
#endif

#if UNITY_EDITOR
                    if ( isSceneCam ) {
                        uData.renderPostProcessing = false;
                    }
                    else {
#endif

                        uData.renderPostProcessing = _settings.usePostFX;
#if UNITY_EDITOR
                    }
#endif
                    uData.volumeLayerMask = _settings.postFXVolumeMask;
                }

                refCamera.targetTexture = currentData._reflectionTex;

#if UNITY_EDITOR
                if ( isSceneCam ) {
                    uData.renderPostProcessing = false;
                }
                else {
#endif
                    uData.renderPostProcessing = _settings.usePostFX;
#if UNITY_EDITOR
                }
#endif
                uData.volumeLayerMask = _settings.postFXVolumeMask;

                uData.requiresDepthOption = CameraOverrideOption.Off;
                uData.requiresColorOption = CameraOverrideOption.Off;


                if ( _settings.usePostFX
#if UNITY_EDITOR
                    && !isSceneCam
#endif
                    ) {
                    refCamera.enabled = true;
                }
                else
                {
                    refCamera.enabled = false;

#if UNITY_2023_3_OR_NEWER
                    rq.destination = refCamera.targetTexture;
                    refCamera.depthTextureMode = DepthTextureMode.None;
                    refCamera.targetTexture = null;
#if UNITY_EDITOR
                    if ( isSceneCam ) {
                        refCamera.cameraType = CameraType.Game;
                    }
#endif
                    UniversalRenderPipeline.SubmitRenderRequest( refCamera, rq );
#if UNITY_EDITOR
                    if ( isSceneCam ) {
                        refCamera.cameraType = CameraType.SceneView;
                    }
#endif

                    refCamera.targetTexture = rq.destination;
#else
                    UniversalRenderPipeline.RenderSingleCamera( context, refCamera );
#endif
                }
            }

            QualitySettings.maximumLODLevel = maxLod;
            QualitySettings.lodBias = tempLOD;



            if (
#if UNITY_EDITOR
                Application.isPlaying &&
#endif
                Time.realtimeSinceStartup > currentData.timer && _settings.reflectionFramerate > 0 ) {
                currentData.timer = Time.realtimeSinceStartup + ( 1.0f / _settings.reflectionFramerate );
            }

#if UNITY_EDITOR && !UNITY_2024_3_OR_NEWER
            if ( cam.cameraType == CameraType.SceneView && showPreviewReflector )
                DrawReflectorMesh( cam, currentData );
#endif
        }



        private void Update() {

            if ( _allCameras.Length != Camera.allCamerasCount ) {
                _allCameras = new Camera[Camera.allCamerasCount];
                Camera.GetAllCameras( _allCameras );
            }

        }


        private void LateUpdate() {

            for ( int i = 0; i < _allCameras.Length; i++ ) {
                if ( _settings.usePostFX ) {
                    if ( _reflectionData.ContainsKey( _allCameras[i] ) ) {
                        SyncToCamera( _reflectionData[_allCameras[i]].ReflectionCamera, _allCameras[i] );
                    }
                }
            }

        }


        private Vector4 CameraSpacePlane( Camera forCamera, Vector3 planeCenter, Vector3 planeNormal ) {
            Vector3 offsetPos = planeCenter;
            Matrix4x4 mtx = forCamera.worldToCameraMatrix;
            Vector3 cPos = mtx.MultiplyPoint( offsetPos );
            Vector3 cNormal = mtx.MultiplyVector( planeNormal ).normalized * 1;
            return new Vector4( cNormal.x, cNormal.y, cNormal.z, -Vector3.Dot( cPos, cNormal ) );
        }


#if UNITY_EDITOR
        private void DrawReflectorMesh( Camera sceneCam, ReflectionData data ) {

            if ( !sceneCam || data == null )
                return;

            var matrix = new Matrix4x4();
            matrix.SetTRS( transform.position, transform.rotation, Vector3.one * 10 );

            if ( _sceneReflectorMatBlock == null ) {
                _sceneReflectorMatBlock = new MaterialPropertyBlock();
            }

            _sceneReflectorMatBlock.SetTexture( "_ReflectionTex", data._reflectionTex ? (Texture)data._reflectionTex : (Texture)Texture2D.blackTexture );
            Graphics.DrawMesh( defaultReflectorMesh, matrix, defaultReflectorMaterial, 0, sceneCam, 0, _sceneReflectorMatBlock );

        }

        public void OnDrawGizmos() {

            Gizmos.matrix = Matrix4x4.TRS( transform.position, transform.rotation, Vector3.one );
            Gizmos.color = Color.clear;
            Gizmos.DrawCube( Vector3.zero, new Vector3( 1, 0.01f, 1 ) * 10 );
            Gizmos.color = Color.cyan;
            Gizmos.DrawWireCube( Vector3.zero, new Vector3( 1, 0, 1 ) * 10 );
            Gizmos.matrix = Matrix4x4.TRS( Vector3.zero, Quaternion.identity, Vector3.one );

        }


        public void OnDrawGizmosSelected() {

        }


#endif


    }

}