From c09469a1f240d659786b176c56be4dc75db04102 Mon Sep 17 00:00:00 2001
From: mhby1g21 <mhby1g21@soton.ac.uk>
Date: Sun, 29 Dec 2024 23:28:05 +0000
Subject: [PATCH] working spectator window, but following instead of same as
 main but stabilised

---
 ...plete XR Origin Set Up Variant AVVR.prefab | 240 ++++++++++++++++--
 AVVR/Assets/_Scripts/DisplayManager.cs        |  73 ++++++
 AVVR/Assets/_Scripts/DisplayManager.cs.meta   |  11 +
 AVVR/Assets/_Scripts/SpectatorCamera.cs       | 130 ++++++++++
 AVVR/Assets/_Scripts/SpectatorCamera.cs.meta  |  11 +
 AVVR/Assets/_Scripts/SpectatorWindow.cs       |  92 +++++++
 AVVR/Assets/_Scripts/SpectatorWindow.cs.meta  |  11 +
 7 files changed, 543 insertions(+), 25 deletions(-)
 create mode 100644 AVVR/Assets/_Scripts/DisplayManager.cs
 create mode 100644 AVVR/Assets/_Scripts/DisplayManager.cs.meta
 create mode 100644 AVVR/Assets/_Scripts/SpectatorCamera.cs
 create mode 100644 AVVR/Assets/_Scripts/SpectatorCamera.cs.meta
 create mode 100644 AVVR/Assets/_Scripts/SpectatorWindow.cs
 create mode 100644 AVVR/Assets/_Scripts/SpectatorWindow.cs.meta

diff --git a/AVVR/Assets/Prefabs/Complete XR Origin Set Up Variant AVVR.prefab b/AVVR/Assets/Prefabs/Complete XR Origin Set Up Variant AVVR.prefab
index 25ce1d3..8ff51de 100644
--- a/AVVR/Assets/Prefabs/Complete XR Origin Set Up Variant AVVR.prefab	
+++ b/AVVR/Assets/Prefabs/Complete XR Origin Set Up Variant AVVR.prefab	
@@ -1,5 +1,184 @@
 %YAML 1.1
 %TAG !u! tag:unity3d.com,2011:
+--- !u!1 &3595680582673134672
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 4075259292299637730}
+  - component: {fileID: 5742413650280917386}
+  m_Layer: 0
+  m_Name: DisplayManager
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!4 &4075259292299637730
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 3595680582673134672}
+  serializedVersion: 2
+  m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+  m_LocalPosition: {x: 2.5984073, y: 1.3109349, z: -0.265801}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_ConstrainProportionsScale: 0
+  m_Children: []
+  m_Father: {fileID: 8298045291338814965}
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!114 &5742413650280917386
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 3595680582673134672}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: f86999510c81f1347962db37765a2031, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  xrCamera: {fileID: 7277802327414017196}
+  spectatorCamera: {fileID: 7249712585786564757}
+  useSecondaryWindow: 0
+  windowSize: {x: 1920, y: 1080}
+  windowedMode: 1
+--- !u!1 &5619876154759688209
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 24857229647593761}
+  - component: {fileID: 7249712585786564757}
+  - component: {fileID: 7321445143899016650}
+  - component: {fileID: 3450999096063229191}
+  - component: {fileID: 3027666375171427404}
+  m_Layer: 2
+  m_Name: Spectator Camera
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!4 &24857229647593761
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 5619876154759688209}
+  serializedVersion: 2
+  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+  m_LocalPosition: {x: 0, y: 0, z: 0}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_ConstrainProportionsScale: 0
+  m_Children: []
+  m_Father: {fileID: 8298045291338814965}
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!20 &7249712585786564757
+Camera:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 5619876154759688209}
+  m_Enabled: 1
+  serializedVersion: 2
+  m_ClearFlags: 1
+  m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
+  m_projectionMatrixMode: 1
+  m_GateFitMode: 2
+  m_FOVAxisMode: 0
+  m_Iso: 200
+  m_ShutterSpeed: 0.005
+  m_Aperture: 16
+  m_FocusDistance: 10
+  m_FocalLength: 50
+  m_BladeCount: 5
+  m_Curvature: {x: 2, y: 11}
+  m_BarrelClipping: 0.25
+  m_Anamorphism: 0
+  m_SensorSize: {x: 36, y: 24}
+  m_LensShift: {x: 0, y: 0}
+  m_NormalizedViewPortRect:
+    serializedVersion: 2
+    x: 0
+    y: 0
+    width: 1
+    height: 1
+  near clip plane: 0.3
+  far clip plane: 1000
+  field of view: 60
+  orthographic: 0
+  orthographic size: 5
+  m_Depth: 0
+  m_CullingMask:
+    serializedVersion: 2
+    m_Bits: 4294967295
+  m_RenderingPath: -1
+  m_TargetTexture: {fileID: 0}
+  m_TargetDisplay: 0
+  m_TargetEye: 3
+  m_HDR: 1
+  m_AllowMSAA: 1
+  m_AllowDynamicResolution: 0
+  m_ForceIntoRT: 0
+  m_OcclusionCulling: 1
+  m_StereoConvergence: 10
+  m_StereoSeparation: 0.022
+--- !u!81 &7321445143899016650
+AudioListener:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 5619876154759688209}
+  m_Enabled: 1
+--- !u!114 &3450999096063229191
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 5619876154759688209}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: eeadcb6c2b1156149b5b7de36c38b5e0, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  xrOrigin: {fileID: 8298045291338814965}
+  xrCamera: {fileID: 7277802327414017193}
+  positionOffset: {x: 0, y: 0.5, z: -1}
+  rotationOffset: {x: 0, y: 0, z: 0}
+  positionSmoothTime: 0.15
+  rotationSmoothTime: 0.15
+  smoothRotation: 1
+  smoothPosition: 1
+  positionDamping: 0.5
+  rotationDamping: 0.5
+--- !u!114 &3027666375171427404
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 5619876154759688209}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: fb47d93d4e6cb45498b98c41d9fc2b4c, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  enableWindowDrag: 1
+  renderResolution: {x: 1920, y: 1080}
 --- !u!1001 &1865269505668036478
 PrefabInstance:
   m_ObjectHideFlags: 0
@@ -78,23 +257,23 @@ PrefabInstance:
       objectReference: {fileID: 0}
     - target: {fileID: 3582865935180742671, guid: d312efb32cfde1a4592787da1e12f8ec, type: 3}
       propertyPath: m_AnchorMax.y
-      value: 0
+      value: 1
       objectReference: {fileID: 0}
     - target: {fileID: 3582865935180742671, guid: d312efb32cfde1a4592787da1e12f8ec, type: 3}
       propertyPath: m_AnchorMin.y
-      value: 0
+      value: 1
       objectReference: {fileID: 0}
     - target: {fileID: 3582865935180742671, guid: d312efb32cfde1a4592787da1e12f8ec, type: 3}
       propertyPath: m_SizeDelta.x
-      value: 0
+      value: 352
       objectReference: {fileID: 0}
     - target: {fileID: 3582865935180742671, guid: d312efb32cfde1a4592787da1e12f8ec, type: 3}
       propertyPath: m_AnchoredPosition.x
-      value: 0
+      value: 200
       objectReference: {fileID: 0}
     - target: {fileID: 3582865935180742671, guid: d312efb32cfde1a4592787da1e12f8ec, type: 3}
       propertyPath: m_AnchoredPosition.y
-      value: 0
+      value: -111
       objectReference: {fileID: 0}
     - target: {fileID: 4063634363251406371, guid: d312efb32cfde1a4592787da1e12f8ec, type: 3}
       propertyPath: SnapTurnProvider
@@ -130,43 +309,43 @@ PrefabInstance:
       objectReference: {fileID: 0}
     - target: {fileID: 4843115453168706472, guid: d312efb32cfde1a4592787da1e12f8ec, type: 3}
       propertyPath: m_AnchorMax.y
-      value: 0
+      value: 1
       objectReference: {fileID: 0}
     - target: {fileID: 4843115453168706472, guid: d312efb32cfde1a4592787da1e12f8ec, type: 3}
       propertyPath: m_AnchorMin.y
-      value: 0
+      value: 1
       objectReference: {fileID: 0}
     - target: {fileID: 4843115453168706472, guid: d312efb32cfde1a4592787da1e12f8ec, type: 3}
       propertyPath: m_SizeDelta.x
-      value: 0
+      value: 352
       objectReference: {fileID: 0}
     - target: {fileID: 4843115453168706472, guid: d312efb32cfde1a4592787da1e12f8ec, type: 3}
       propertyPath: m_AnchoredPosition.x
-      value: 0
+      value: 200
       objectReference: {fileID: 0}
     - target: {fileID: 4843115453168706472, guid: d312efb32cfde1a4592787da1e12f8ec, type: 3}
       propertyPath: m_AnchoredPosition.y
-      value: 0
+      value: -245
       objectReference: {fileID: 0}
     - target: {fileID: 5233380041029087467, guid: d312efb32cfde1a4592787da1e12f8ec, type: 3}
       propertyPath: m_AnchorMax.y
-      value: 0
+      value: 1
       objectReference: {fileID: 0}
     - target: {fileID: 5233380041029087467, guid: d312efb32cfde1a4592787da1e12f8ec, type: 3}
       propertyPath: m_AnchorMin.y
-      value: 0
+      value: 1
       objectReference: {fileID: 0}
     - target: {fileID: 5233380041029087467, guid: d312efb32cfde1a4592787da1e12f8ec, type: 3}
       propertyPath: m_SizeDelta.x
-      value: 0
+      value: 352
       objectReference: {fileID: 0}
     - target: {fileID: 5233380041029087467, guid: d312efb32cfde1a4592787da1e12f8ec, type: 3}
       propertyPath: m_AnchoredPosition.x
-      value: 0
+      value: 200
       objectReference: {fileID: 0}
     - target: {fileID: 5233380041029087467, guid: d312efb32cfde1a4592787da1e12f8ec, type: 3}
       propertyPath: m_AnchoredPosition.y
-      value: 0
+      value: -178
       objectReference: {fileID: 0}
     - target: {fileID: 5603239251682444825, guid: d312efb32cfde1a4592787da1e12f8ec, type: 3}
       propertyPath: m_AnchorMax.x
@@ -210,23 +389,23 @@ PrefabInstance:
       objectReference: {fileID: 0}
     - target: {fileID: 7559043828606092874, guid: d312efb32cfde1a4592787da1e12f8ec, type: 3}
       propertyPath: m_AnchorMax.y
-      value: 0
+      value: 1
       objectReference: {fileID: 0}
     - target: {fileID: 7559043828606092874, guid: d312efb32cfde1a4592787da1e12f8ec, type: 3}
       propertyPath: m_AnchorMin.y
-      value: 0
+      value: 1
       objectReference: {fileID: 0}
     - target: {fileID: 7559043828606092874, guid: d312efb32cfde1a4592787da1e12f8ec, type: 3}
       propertyPath: m_SizeDelta.x
-      value: 0
+      value: 352
       objectReference: {fileID: 0}
     - target: {fileID: 7559043828606092874, guid: d312efb32cfde1a4592787da1e12f8ec, type: 3}
       propertyPath: m_AnchoredPosition.x
-      value: 0
+      value: 200
       objectReference: {fileID: 0}
     - target: {fileID: 7559043828606092874, guid: d312efb32cfde1a4592787da1e12f8ec, type: 3}
       propertyPath: m_AnchoredPosition.y
-      value: 0
+      value: -312
       objectReference: {fileID: 0}
     - target: {fileID: 8027956442804198582, guid: d312efb32cfde1a4592787da1e12f8ec, type: 3}
       propertyPath: m_Value
@@ -234,23 +413,23 @@ PrefabInstance:
       objectReference: {fileID: 0}
     - target: {fileID: 9223145543918184230, guid: d312efb32cfde1a4592787da1e12f8ec, type: 3}
       propertyPath: m_AnchorMax.y
-      value: 0
+      value: 1
       objectReference: {fileID: 0}
     - target: {fileID: 9223145543918184230, guid: d312efb32cfde1a4592787da1e12f8ec, type: 3}
       propertyPath: m_AnchorMin.y
-      value: 0
+      value: 1
       objectReference: {fileID: 0}
     - target: {fileID: 9223145543918184230, guid: d312efb32cfde1a4592787da1e12f8ec, type: 3}
       propertyPath: m_SizeDelta.x
-      value: 0
+      value: 352
       objectReference: {fileID: 0}
     - target: {fileID: 9223145543918184230, guid: d312efb32cfde1a4592787da1e12f8ec, type: 3}
       propertyPath: m_AnchoredPosition.x
-      value: 0
+      value: 200
       objectReference: {fileID: 0}
     - target: {fileID: 9223145543918184230, guid: d312efb32cfde1a4592787da1e12f8ec, type: 3}
       propertyPath: m_AnchoredPosition.y
-      value: 0
+      value: -44
       objectReference: {fileID: 0}
     m_RemovedComponents: []
     m_RemovedGameObjects: []
@@ -1160,6 +1339,12 @@ PrefabInstance:
     - targetCorrespondingSourceObject: {fileID: 1717954561962503726, guid: f6336ac4ac8b4d34bc5072418cdc62a0, type: 3}
       insertIndex: -1
       addedObject: {fileID: 536801052177267210}
+    - targetCorrespondingSourceObject: {fileID: 1717954561962503726, guid: f6336ac4ac8b4d34bc5072418cdc62a0, type: 3}
+      insertIndex: -1
+      addedObject: {fileID: 24857229647593761}
+    - targetCorrespondingSourceObject: {fileID: 1717954561962503726, guid: f6336ac4ac8b4d34bc5072418cdc62a0, type: 3}
+      insertIndex: -1
+      addedObject: {fileID: 4075259292299637730}
     - targetCorrespondingSourceObject: {fileID: 1767192434, guid: f6336ac4ac8b4d34bc5072418cdc62a0, type: 3}
       insertIndex: -1
       addedObject: {fileID: 5518899284880179292}
@@ -1391,6 +1576,11 @@ MonoBehaviour:
         z: 0
       radius: 0
   mProbeBatchesUsed: []
+--- !u!20 &7277802327414017196 stripped
+Camera:
+  m_CorrespondingSourceObject: {fileID: 1767192439, guid: f6336ac4ac8b4d34bc5072418cdc62a0, type: 3}
+  m_PrefabInstance: {fileID: 7277802326999645147}
+  m_PrefabAsset: {fileID: 0}
 --- !u!4 &7277802327528778794 stripped
 Transform:
   m_CorrespondingSourceObject: {fileID: 1670256625, guid: f6336ac4ac8b4d34bc5072418cdc62a0, type: 3}
diff --git a/AVVR/Assets/_Scripts/DisplayManager.cs b/AVVR/Assets/_Scripts/DisplayManager.cs
new file mode 100644
index 0000000..43d1893
--- /dev/null
+++ b/AVVR/Assets/_Scripts/DisplayManager.cs
@@ -0,0 +1,73 @@
+using UnityEngine;
+using UnityEngine.XR;
+using System.Linq;
+
+public class DisplayManager : MonoBehaviour
+{
+    [Header("Camera References")]
+    [SerializeField] private Camera xrCamera;
+    [SerializeField] private Camera spectatorCamera;
+    
+    [Header("Display Settings")]
+    [SerializeField] private bool useSecondaryWindow = false;
+    [SerializeField] private Vector2Int windowSize = new Vector2Int(1920, 1080);
+    [SerializeField] private bool windowedMode = true;
+    
+    private void Start()
+    {
+        if (xrCamera == null || spectatorCamera == null)
+        {
+            Debug.LogError("Camera references not set in Display Manager!");
+            return;
+        }
+
+        SetupDisplays();
+    }
+
+    private void SetupDisplays()
+    {
+        // Configure XR Camera
+        xrCamera.targetDisplay = 0; // VR Display
+        
+        if (useSecondaryWindow)
+        {
+            CreateSecondaryWindow();
+        }
+        else
+        {
+            // Use main display for spectator view
+            spectatorCamera.targetDisplay = 0;
+            xrCamera.depth = 0;
+            spectatorCamera.depth = 1; // Ensure spectator camera renders over XR camera on desktop
+        }
+
+        // Set up spectator camera to not render to the VR display
+        spectatorCamera.stereoTargetEye = StereoTargetEyeMask.None;
+    }
+
+    private void CreateSecondaryWindow()
+    {
+#if UNITY_STANDALONE_WIN || UNITY_EDITOR
+        // Create a secondary window
+        if (!UnityEngine.Display.displays.Any(d => d.active && d != UnityEngine.Display.main))
+        {
+            if (UnityEngine.Display.displays.Length > 1)
+            {
+                UnityEngine.Display.displays[1].Activate();
+                spectatorCamera.targetDisplay = 1;
+            }
+        }
+#endif
+    }
+
+#if UNITY_EDITOR
+    [UnityEditor.MenuItem("Window/Create Game View 2")]
+    public static void CreateSecondGameView()
+    {
+        var assembly = typeof(UnityEditor.EditorWindow).Assembly;
+        var gameViewType = assembly.GetType("UnityEditor.GameView");
+        var gameView = UnityEditor.EditorWindow.GetWindow(gameViewType, false, "Game (Spectator)", true);
+        gameView.Show();
+    }
+#endif
+}
\ No newline at end of file
diff --git a/AVVR/Assets/_Scripts/DisplayManager.cs.meta b/AVVR/Assets/_Scripts/DisplayManager.cs.meta
new file mode 100644
index 0000000..103f9f2
--- /dev/null
+++ b/AVVR/Assets/_Scripts/DisplayManager.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f86999510c81f1347962db37765a2031
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/AVVR/Assets/_Scripts/SpectatorCamera.cs b/AVVR/Assets/_Scripts/SpectatorCamera.cs
new file mode 100644
index 0000000..3e4eebf
--- /dev/null
+++ b/AVVR/Assets/_Scripts/SpectatorCamera.cs
@@ -0,0 +1,130 @@
+using UnityEngine;
+using UnityEngine.XR;
+
+[RequireComponent(typeof(Camera))]
+public class SpectatorCamera : MonoBehaviour
+{
+    [Header("Target Settings")]
+    [SerializeField] private Transform xrOrigin;
+    [SerializeField] private Transform xrCamera;
+    [SerializeField] private Vector3 positionOffset = new Vector3(0f, 0.5f, -1f);
+    [SerializeField] private Vector3 rotationOffset;
+
+    [Header("Smoothing")]
+    [SerializeField] private float positionSmoothTime = 0.15f;
+    [SerializeField] private float rotationSmoothTime = 0.15f;
+    [SerializeField] private bool smoothRotation = true;
+    [SerializeField] private bool smoothPosition = true;
+
+    [Header("Damping")]
+    [Range(0f, 1f)]
+    [SerializeField] private float positionDamping = 0.5f;
+    [Range(0f, 1f)]
+    [SerializeField] private float rotationDamping = 0.5f;
+
+    // Internal tracking variables
+    private Vector3 currentVelocity;
+    private Vector3 currentAngularVelocity;
+    private Vector3 targetPosition;
+    private Quaternion targetRotation;
+
+    private void Start()
+    {
+        if (xrOrigin == null || xrCamera == null)
+        {
+            Debug.LogError("XR Origin or Camera reference not set in Spectator Camera!");
+            enabled = false;
+            return;
+        }
+
+        // Initialize position and rotation
+        transform.position = xrCamera.position + xrCamera.TransformDirection(positionOffset);
+        transform.rotation = xrCamera.rotation * Quaternion.Euler(rotationOffset);
+    }
+
+    private void LateUpdate()
+    {
+        UpdateTargetTransform();
+        SmoothlyFollowTarget();
+    }
+
+    private void UpdateTargetTransform()
+    {
+        // Calculate target position with offset
+        targetPosition = xrCamera.position + xrCamera.TransformDirection(positionOffset);
+
+        // Calculate target rotation with offset
+        targetRotation = xrCamera.rotation * Quaternion.Euler(rotationOffset);
+    }
+
+    private void SmoothlyFollowTarget()
+    {
+        if (smoothPosition)
+        {
+            // Smooth position following
+            transform.position = Vector3.SmoothDamp(
+                transform.position,
+                targetPosition,
+                ref currentVelocity,
+                positionSmoothTime,
+                Mathf.Infinity,
+                Time.deltaTime
+            );
+        }
+        else
+        {
+            transform.position = targetPosition;
+        }
+
+        if (smoothRotation)
+        {
+            // Smooth rotation following
+            transform.rotation = SmoothDampQuaternion(
+                transform.rotation,
+                targetRotation,
+                ref currentAngularVelocity,
+                rotationSmoothTime
+            );
+        }
+        else
+        {
+            transform.rotation = targetRotation;
+        }
+    }
+
+    // Custom quaternion smooth damp function
+    private Quaternion SmoothDampQuaternion(Quaternion current, Quaternion target, ref Vector3 currentVelocity, float smoothTime)
+    {
+        Vector3 currentEuler = current.eulerAngles;
+        Vector3 targetEuler = target.eulerAngles;
+
+        // Ensure we rotate the shortest path
+        float deltaX = Mathf.DeltaAngle(currentEuler.x, targetEuler.x);
+        float deltaY = Mathf.DeltaAngle(currentEuler.y, targetEuler.y);
+        float deltaZ = Mathf.DeltaAngle(currentEuler.z, targetEuler.z);
+
+        Vector3 targetAngles = currentEuler + new Vector3(deltaX, deltaY, deltaZ);
+
+        Vector3 smoothedEuler = Vector3.SmoothDamp(
+            currentEuler,
+            targetAngles,
+            ref currentVelocity,
+            smoothTime,
+            Mathf.Infinity,
+            Time.deltaTime
+        );
+
+        return Quaternion.Euler(smoothedEuler);
+    }
+
+    // Editor gizmos for visualization
+    private void OnDrawGizmosSelected()
+    {
+        if (xrCamera != null)
+        {
+            Gizmos.color = Color.green;
+            Gizmos.DrawWireSphere(xrCamera.position + xrCamera.TransformDirection(positionOffset), 0.1f);
+            Gizmos.DrawLine(xrCamera.position, xrCamera.position + xrCamera.TransformDirection(positionOffset));
+        }
+    }
+}
\ No newline at end of file
diff --git a/AVVR/Assets/_Scripts/SpectatorCamera.cs.meta b/AVVR/Assets/_Scripts/SpectatorCamera.cs.meta
new file mode 100644
index 0000000..b8a14c9
--- /dev/null
+++ b/AVVR/Assets/_Scripts/SpectatorCamera.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: eeadcb6c2b1156149b5b7de36c38b5e0
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/AVVR/Assets/_Scripts/SpectatorWindow.cs b/AVVR/Assets/_Scripts/SpectatorWindow.cs
new file mode 100644
index 0000000..dba9e90
--- /dev/null
+++ b/AVVR/Assets/_Scripts/SpectatorWindow.cs
@@ -0,0 +1,92 @@
+using UnityEngine;
+
+public class SpectatorWindow : MonoBehaviour
+{
+    private static SpectatorWindow instance;
+    private Camera spectatorCam;
+    private RenderTexture renderTexture;
+    private bool showWindow = true;
+    private Rect windowRect = new Rect(100, 100, 800, 450);
+
+    [SerializeField] private bool enableWindowDrag = true;
+    [SerializeField] private Vector2Int renderResolution = new Vector2Int(1920, 1080);
+
+    private void Awake()
+    {
+        if (instance == null)
+        {
+            instance = this;
+            DontDestroyOnLoad(gameObject);
+        }
+        else
+        {
+            Destroy(gameObject);
+            return;
+        }
+
+        spectatorCam = GetComponent<Camera>();
+        if (spectatorCam == null)
+        {
+            Debug.LogError("No camera attached to SpectatorWindow!");
+            return;
+        }
+
+        InitializeRenderTexture();
+    }
+
+    private void InitializeRenderTexture()
+    {
+        renderTexture = new RenderTexture(renderResolution.x, renderResolution.y, 24);
+        renderTexture.antiAliasing = 4;
+        spectatorCam.targetTexture = renderTexture;
+    }
+
+    private void OnGUI()
+    {
+        if (!showWindow) return;
+
+        // Draw the window
+        windowRect = enableWindowDrag ? 
+            GUI.Window(0, windowRect, DrawWindowContents, "Spectator View") :
+            GUI.Window(0, windowRect, DrawWindowContents, "");
+    }
+
+    private void DrawWindowContents(int windowID)
+    {
+        // Draw the render texture
+        GUI.DrawTexture(new Rect(0, 20, windowRect.width, windowRect.height - 20), renderTexture);
+
+        // Allow window dragging
+        if (enableWindowDrag)
+        {
+            GUI.DragWindow();
+        }
+
+        // Add a close button
+        if (GUI.Button(new Rect(windowRect.width - 25, 2, 20, 16), "X"))
+        {
+            showWindow = false;
+        }
+    }
+
+    private void OnDestroy()
+    {
+        if (renderTexture != null)
+        {
+            renderTexture.Release();
+            Destroy(renderTexture);
+        }
+    }
+
+    // Public methods to control the window
+    public void ShowWindow() => showWindow = true;
+    public void HideWindow() => showWindow = false;
+    public void ToggleWindow() => showWindow = !showWindow;
+    
+    // Method to change window size
+    public void SetWindowSize(float width, float height)
+    {
+        windowRect.width = width;
+        windowRect.height = height;
+    }
+}
\ No newline at end of file
diff --git a/AVVR/Assets/_Scripts/SpectatorWindow.cs.meta b/AVVR/Assets/_Scripts/SpectatorWindow.cs.meta
new file mode 100644
index 0000000..8748a50
--- /dev/null
+++ b/AVVR/Assets/_Scripts/SpectatorWindow.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: fb47d93d4e6cb45498b98c41d9fc2b4c
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
-- 
GitLab