diff --git a/code/VR Art Gallery/Assets/Scenes/SampleScene.unity b/code/VR Art Gallery/Assets/Scenes/SampleScene.unity index a60be50..63afd18 100644 --- a/code/VR Art Gallery/Assets/Scenes/SampleScene.unity +++ b/code/VR Art Gallery/Assets/Scenes/SampleScene.unity @@ -509,19 +509,19 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 440948656723770332, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchorMax.y - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 440948656723770332, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchorMin.y - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 440948656723770332, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchoredPosition.x - value: 20 + value: 0 objectReference: {fileID: 0} - target: {fileID: 440948656723770332, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchoredPosition.y - value: -12 + value: 0 objectReference: {fileID: 0} - target: {fileID: 440948656945103206, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_IsActive @@ -585,19 +585,19 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 440948657677736307, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchorMax.y - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 440948657677736307, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchorMin.y - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 440948657677736307, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchoredPosition.x - value: 304 + value: 0 objectReference: {fileID: 0} - target: {fileID: 440948657677736307, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchoredPosition.y - value: -12 + value: 0 objectReference: {fileID: 0} - target: {fileID: 440948657680231979, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_SizeDelta.x @@ -1065,19 +1065,19 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 1565022366488384926, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchorMax.y - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 1565022366488384926, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchorMin.y - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 1565022366488384926, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchoredPosition.x - value: 20 + value: 0 objectReference: {fileID: 0} - target: {fileID: 1565022366488384926, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchoredPosition.y - value: -15 + value: 0 objectReference: {fileID: 0} - target: {fileID: 1643146213795589592, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchorMax.y @@ -1393,19 +1393,19 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 2814095900973319199, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchorMax.y - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 2814095900973319199, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchorMin.y - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 2814095900973319199, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchoredPosition.x - value: 20 + value: 0 objectReference: {fileID: 0} - target: {fileID: 2814095900973319199, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchoredPosition.y - value: -15 + value: 0 objectReference: {fileID: 0} - target: {fileID: 2827505965204158257, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchorMax.y @@ -1777,19 +1777,19 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 4292373663111151148, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchorMax.y - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 4292373663111151148, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchorMin.y - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 4292373663111151148, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchoredPosition.x - value: 15 + value: 0 objectReference: {fileID: 0} - target: {fileID: 4292373663111151148, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchoredPosition.y - value: -12 + value: 0 objectReference: {fileID: 0} - target: {fileID: 4422127295404337792, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_Spacing.x @@ -1881,19 +1881,19 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 4998242230571431511, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchorMax.y - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 4998242230571431511, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchorMin.y - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 4998242230571431511, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchoredPosition.x - value: 50 + value: 0 objectReference: {fileID: 0} - target: {fileID: 4998242230571431511, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchoredPosition.y - value: -12 + value: 0 objectReference: {fileID: 0} - target: {fileID: 5124595908052133201, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchorMax.y @@ -2509,19 +2509,19 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 8073791155062023922, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchorMax.y - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8073791155062023922, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchorMin.y - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8073791155062023922, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchoredPosition.x - value: 15 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8073791155062023922, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchoredPosition.y - value: -12 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8126797845790104188, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchorMax.y @@ -2705,19 +2705,19 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 8640415441524746198, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchorMax.y - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8640415441524746198, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchorMin.y - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8640415441524746198, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchoredPosition.x - value: 50 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8640415441524746198, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchoredPosition.y - value: -12 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8694544668885805580, guid: e385e155969dc274691335562166cbfb, type: 3} propertyPath: m_AnchorMax.y @@ -4750,7 +4750,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 7951c64acb0fa62458bf30a60089fe2d, type: 3} m_Name: m_EditorClassIdentifier: - m_IgnoreReversedGraphics: 0 + m_IgnoreReversedGraphics: 1 m_CheckFor2DOcclusion: 0 m_CheckFor3DOcclusion: 0 m_BlockingMask: @@ -5369,7 +5369,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 6598815579406187037, guid: 18ddb545287c546e19cc77dc9fbb2189, type: 3} propertyPath: m_IsActive - value: 1 + value: 0 objectReference: {fileID: 0} m_RemovedComponents: [] m_RemovedGameObjects: [] @@ -8512,7 +8512,8 @@ MonoBehaviour: m_EditorClassIdentifier: VRMP::HUDSpawner vrSpawnButton: {fileID: 0} desktopSpawnButton: {fileID: 0} - hudObject: {fileID: 0} + hudObject: {fileID: 525470198} + hudPanelName: Gallery Selection UI headCamera: {fileID: 4607428429233221385} spawnDistance: 1.5 spawnHeightOffset: 0.5 @@ -10684,7 +10685,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 1646060278908075255, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_IsActive - value: 0 + value: 1 objectReference: {fileID: 0} - target: {fileID: 1648624753946291760, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_Enabled @@ -10712,15 +10713,15 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 2104892051373117235, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_SizeDelta.x - value: 80 + value: 0 objectReference: {fileID: 0} - target: {fileID: 2104892051373117235, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_SizeDelta.y - value: 46 + value: 0 objectReference: {fileID: 0} - target: {fileID: 2841105847340944790, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_IsActive - value: 0 + value: 1 objectReference: {fileID: 0} - target: {fileID: 3088556171141835317, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_AnchorMax.x @@ -10732,7 +10733,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 3491604736502946245, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_IsActive - value: 0 + value: 1 objectReference: {fileID: 0} - target: {fileID: 3650905337081058800, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_Value @@ -10784,15 +10785,15 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 4293411706908256769, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_SizeDelta.x - value: 91.78 + value: 0 objectReference: {fileID: 0} - target: {fileID: 4293411706908256769, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_SizeDelta.y - value: 46 + value: 0 objectReference: {fileID: 0} - target: {fileID: 4850823803574754670, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_IsActive - value: 0 + value: 1 objectReference: {fileID: 0} - target: {fileID: 5187516752444087277, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_AnchorMax.y @@ -10812,11 +10813,11 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 5481505349926868397, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_SizeDelta.x - value: 80 + value: 0 objectReference: {fileID: 0} - target: {fileID: 5481505349926868397, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_SizeDelta.y - value: 46 + value: 0 objectReference: {fileID: 0} - target: {fileID: 5481505350310966840, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_AnchorMax.y @@ -10840,7 +10841,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 5485567801699379225, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_IsActive - value: 0 + value: 1 objectReference: {fileID: 0} - target: {fileID: 5493747427735050679, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_IsActive @@ -10848,7 +10849,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 6293670324847006103, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_IsActive - value: 0 + value: 1 objectReference: {fileID: 0} - target: {fileID: 6683097358647424652, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_AnchorMax.x @@ -10868,11 +10869,11 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 6863966910340453416, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_IsActive - value: 0 + value: 1 objectReference: {fileID: 0} - target: {fileID: 6914086229423109313, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_IsActive - value: 0 + value: 1 objectReference: {fileID: 0} - target: {fileID: 6967245486335044556, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_AnchoredPosition.y @@ -10880,15 +10881,15 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 7012245614385447771, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_IsActive - value: 0 + value: 1 objectReference: {fileID: 0} - target: {fileID: 7017205198832254703, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_SizeDelta.x - value: 110.3 + value: 0 objectReference: {fileID: 0} - target: {fileID: 7017205198832254703, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_SizeDelta.y - value: 46 + value: 0 objectReference: {fileID: 0} - target: {fileID: 7017205199212174714, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_AnchorMax.y @@ -10960,7 +10961,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 7236287184081664708, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_IsActive - value: 0 + value: 1 objectReference: {fileID: 0} - target: {fileID: 7413506793532227578, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_AnchorMax.y @@ -11040,11 +11041,11 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 7931734657012468632, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_SizeDelta.x - value: 90.759995 + value: 0 objectReference: {fileID: 0} - target: {fileID: 7931734657012468632, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_SizeDelta.y - value: 46 + value: 0 objectReference: {fileID: 0} - target: {fileID: 7960896337995949517, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_FarAttachMode @@ -11116,11 +11117,11 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 8215425129507177182, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_SizeDelta.x - value: 90.759995 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8215425129507177182, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_SizeDelta.y - value: 46 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8215425130334822731, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_AnchorMax.y @@ -11144,11 +11145,11 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 8264524101722841655, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_SizeDelta.x - value: 80 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8264524101722841655, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_SizeDelta.y - value: 46 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8264524102105252258, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_AnchorMax.y @@ -11200,11 +11201,11 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 8796977002278766433, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_SizeDelta.x - value: 80 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8796977002278766433, guid: 77ac6b10722bcca4a8baf70e44d89e78, type: 3} propertyPath: m_SizeDelta.y - value: 46 + value: 0 objectReference: {fileID: 0} m_RemovedComponents: [] m_RemovedGameObjects: [] diff --git a/code/VR Art Gallery/Assets/Scripts/CanvasSpawner.cs b/code/VR Art Gallery/Assets/Scripts/CanvasSpawner.cs index 4fc675e..e216c7c 100644 --- a/code/VR Art Gallery/Assets/Scripts/CanvasSpawner.cs +++ b/code/VR Art Gallery/Assets/Scripts/CanvasSpawner.cs @@ -28,6 +28,16 @@ public class CanvasSpawner : NetworkBehaviour [Header("Test image (optional override)")] public Texture2D testTexture; + [Header("HUD Follow")] + public string hudPanelName = "Gallery Selection UI"; + public string hudFallbackName = "World Space Canvas"; + public float workspaceBehindHudDistance = 0.35f; + public float workspaceBehindHudHeightOffset = -1.1f; + + private NetworkObject m_SpawnedWorkspace; + private bool m_IsWorkspaceVisible; + private NetworkObjectReference m_SpawnedBrushRef; + public void Create() { if (!workspacePrefab) @@ -35,45 +45,61 @@ public void Create() Debug.LogError("[CanvasSpawner] workspacePrefab not assigned."); return; } - if (!playerHead) + + Transform activeHead = playerHead; + if (!activeHead && Camera.main != null) + activeHead = Camera.main.transform; + + if (!activeHead) { Debug.LogError("[CanvasSpawner] playerHead not assigned."); return; } - Vector3 forwardFlat = Vector3.ProjectOnPlane(playerHead.forward, Vector3.up).normalized; - if (forwardFlat.sqrMagnitude < 0.001f) - forwardFlat = playerHead.forward; - - Vector3 pos = playerHead.position + forwardFlat * spawnDistance + Vector3.up * heightOffset; + Vector3 pos; + Quaternion rot; - for (int i = 0; i < maxPushTries; i++) + if (!TryBuildSpawnPoseBehindHud(activeHead, out pos, out rot)) { - if (!Physics.CheckSphere(pos, checkRadius, overlapMask, QueryTriggerInteraction.Ignore)) - break; - pos += forwardFlat * pushStep; - } + Vector3 forwardFlat = Vector3.ProjectOnPlane(activeHead.forward, Vector3.up).normalized; + if (forwardFlat.sqrMagnitude < 0.001f) + forwardFlat = activeHead.forward; - Quaternion rot; - if (faceUser) - { - Vector3 toUserFlat = Vector3.ProjectOnPlane(playerHead.position - pos, Vector3.up).normalized; - if (toUserFlat.sqrMagnitude < 0.001f) - toUserFlat = -forwardFlat; + pos = activeHead.position + forwardFlat * spawnDistance + Vector3.up * heightOffset; - rot = Quaternion.LookRotation(toUserFlat, Vector3.up); - } - else - { - rot = Quaternion.LookRotation(forwardFlat, Vector3.up); + for (int i = 0; i < maxPushTries; i++) + { + if (!Physics.CheckSphere(pos, checkRadius, overlapMask, QueryTriggerInteraction.Ignore)) + break; + pos += forwardFlat * pushStep; + } + + if (faceUser) + { + Vector3 toUserFlat = Vector3.ProjectOnPlane(activeHead.position - pos, Vector3.up).normalized; + if (toUserFlat.sqrMagnitude < 0.001f) + toUserFlat = -forwardFlat; + + rot = Quaternion.LookRotation(toUserFlat, Vector3.up); + } + else + { + rot = Quaternion.LookRotation(forwardFlat, Vector3.up); + } } - SpawnWorkspaceServerRpc(pos, rot); + CreateOrToggleWorkspaceServerRpc(pos, rot); } [ServerRpc(RequireOwnership = false)] - private void SpawnWorkspaceServerRpc(Vector3 pos, Quaternion rot) + private void CreateOrToggleWorkspaceServerRpc(Vector3 pos, Quaternion rot) { + if (m_SpawnedWorkspace != null && m_SpawnedWorkspace.IsSpawned) + { + SetWorkspaceVisibility(!m_IsWorkspaceVisible, pos, rot); + return; + } + // Instantiate the full Workspace prefab (root has NetworkObject) GameObject workspace = Instantiate(workspacePrefab, pos, rot); NetworkObject netObj = workspace.GetComponent(); @@ -85,6 +111,10 @@ private void SpawnWorkspaceServerRpc(Vector3 pos, Quaternion rot) } netObj.Spawn(); + m_SpawnedWorkspace = netObj; + m_IsWorkspaceVisible = true; + + ApplyWorkspacePlacementLocal(workspace, pos, rot, true); if (paintbrushPrefab != null) { @@ -102,6 +132,214 @@ private void SpawnWorkspaceServerRpc(Vector3 pos, Quaternion rot) brushSpawner.brushOffset = brushOffset; brushSpawner.brushRotationEuler = brushRotationEuler; brushSpawner.SpawnBrush(); + + NetworkObject spawnedBrush = FindClosestSpawnedBrush(workspace.transform.position); + if (spawnedBrush != null) + m_SpawnedBrushRef = spawnedBrush; } } + + private void SetWorkspaceVisibility(bool visible, Vector3 pos, Quaternion rot) + { + if (m_SpawnedWorkspace == null) + return; + + if (visible) + ApplyWorkspacePlacementLocal(m_SpawnedWorkspace.gameObject, pos, rot, true); + + ApplyWorkspaceVisibilityLocal(m_SpawnedWorkspace.gameObject, visible); + + if (!m_SpawnedBrushRef.TryGet(out NetworkObject brushObj) || brushObj == null || !brushObj.IsSpawned) + { + NetworkObject refreshedBrush = FindClosestSpawnedBrush(m_SpawnedWorkspace.transform.position); + if (refreshedBrush != null) + m_SpawnedBrushRef = refreshedBrush; + } + + if (m_SpawnedBrushRef.TryGet(out brushObj) && brushObj != null && brushObj.IsSpawned) + { + if (visible) + ApplyBrushPlacementRelativeToWorkspaceLocal(brushObj.gameObject, m_SpawnedWorkspace.transform, brushOffset, brushRotationEuler); + + ApplyWorkspaceVisibilityLocal(brushObj.gameObject, visible); + } + + if (IsServer) + SetWorkspaceVisibilityClientRpc(m_SpawnedWorkspace, m_SpawnedBrushRef, visible, pos, rot); + + m_IsWorkspaceVisible = visible; + } + + [ClientRpc] + private void SetWorkspaceVisibilityClientRpc(NetworkObjectReference workspaceRef, NetworkObjectReference brushRef, bool visible, Vector3 pos, Quaternion rot) + { + if (!workspaceRef.TryGet(out NetworkObject workspace) || workspace == null) + return; + + if (visible) + ApplyWorkspacePlacementLocal(workspace.gameObject, pos, rot, true); + + ApplyWorkspaceVisibilityLocal(workspace.gameObject, visible); + + if (brushRef.TryGet(out NetworkObject brush) && brush != null) + { + if (visible) + ApplyBrushPlacementRelativeToWorkspaceLocal(brush.gameObject, workspace.transform, brushOffset, brushRotationEuler); + + ApplyWorkspaceVisibilityLocal(brush.gameObject, visible); + } + } + + private bool TryBuildSpawnPoseBehindHud(Transform activeHead, out Vector3 pos, out Quaternion rot) + { + pos = Vector3.zero; + rot = Quaternion.identity; + + GameObject hudAnchor = FindSceneObjectByNameIncludingInactive(hudPanelName); + + if (hudAnchor == null) + return false; + + Vector3 anchorPos = hudAnchor.transform.position; + RectTransform rectTransform = hudAnchor.GetComponent(); + if (rectTransform != null) + anchorPos = GetRectTransformCenterWorld(rectTransform); + + Vector3 toUser = activeHead.position - anchorPos; + Vector3 toUserFlat = Vector3.ProjectOnPlane(toUser, Vector3.up); + + if (toUserFlat.sqrMagnitude < 0.001f) + toUserFlat = Vector3.ProjectOnPlane(-hudAnchor.transform.forward, Vector3.up); + + if (toUserFlat.sqrMagnitude < 0.001f) + return false; + + toUserFlat.Normalize(); + + pos = anchorPos - toUserFlat * workspaceBehindHudDistance; + pos.y = anchorPos.y + workspaceBehindHudHeightOffset; + rot = Quaternion.LookRotation(toUserFlat, Vector3.up); + return true; + } + + private static GameObject FindSceneObjectByNameIncludingInactive(string objectName) + { + if (string.IsNullOrWhiteSpace(objectName)) + return null; + + Transform[] transforms = Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); + for (int i = 0; i < transforms.Length; i++) + { + Transform t = transforms[i]; + if (t == null) + continue; + + if (!t.gameObject.scene.IsValid()) + continue; + + if (t.name == objectName) + return t.gameObject; + } + + return null; + } + + private static Vector3 GetRectTransformCenterWorld(RectTransform rectTransform) + { + Vector3[] corners = new Vector3[4]; + rectTransform.GetWorldCorners(corners); + return (corners[0] + corners[2]) * 0.5f; + } + + private static void ApplyWorkspacePlacementLocal(GameObject workspaceRoot, Vector3 pos, Quaternion rot, bool alignVisualCenterToTargetY) + { + if (workspaceRoot == null) + return; + + workspaceRoot.transform.SetPositionAndRotation(pos, rot); + + if (!alignVisualCenterToTargetY) + return; + + if (!TryGetVisualCenter(workspaceRoot, out Vector3 visualCenter)) + return; + + float yDelta = pos.y - visualCenter.y; + if (Mathf.Abs(yDelta) > 0.0001f) + workspaceRoot.transform.position += Vector3.up * yDelta; + } + + private static bool TryGetVisualCenter(GameObject root, out Vector3 center) + { + center = Vector3.zero; + + Renderer[] renderers = root.GetComponentsInChildren(true); + if (renderers == null || renderers.Length == 0) + return false; + + Bounds bounds = renderers[0].bounds; + for (int i = 1; i < renderers.Length; i++) + bounds.Encapsulate(renderers[i].bounds); + + center = bounds.center; + return true; + } + + private static NetworkObject FindClosestSpawnedBrush(Vector3 fromPosition) + { + BrushRespawnOnGrab[] brushes = Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); + NetworkObject closest = null; + float closestSqrDist = float.MaxValue; + + for (int i = 0; i < brushes.Length; i++) + { + if (brushes[i] == null) + continue; + + NetworkObject netObj = brushes[i].GetComponent(); + if (netObj == null || !netObj.IsSpawned) + continue; + + float sqrDist = (netObj.transform.position - fromPosition).sqrMagnitude; + if (sqrDist < closestSqrDist) + { + closest = netObj; + closestSqrDist = sqrDist; + } + } + + return closest; + } + + private static void ApplyBrushPlacementRelativeToWorkspaceLocal(GameObject brushObject, Transform workspaceTransform, Vector3 localOffset, Vector3 localEulerOffset) + { + if (brushObject == null || workspaceTransform == null) + return; + + Vector3 brushWorldPos = workspaceTransform.TransformPoint(localOffset); + Quaternion brushWorldRot = workspaceTransform.rotation * Quaternion.Euler(localEulerOffset); + brushObject.transform.SetPositionAndRotation(brushWorldPos, brushWorldRot); + } + + private static void ApplyWorkspaceVisibilityLocal(GameObject workspaceRoot, bool visible) + { + if (workspaceRoot == null) + return; + + Renderer[] renderers = workspaceRoot.GetComponentsInChildren(true); + for (int i = 0; i < renderers.Length; i++) + renderers[i].enabled = visible; + + Canvas[] canvases = workspaceRoot.GetComponentsInChildren(true); + for (int i = 0; i < canvases.Length; i++) + canvases[i].enabled = visible; + + Collider[] colliders = workspaceRoot.GetComponentsInChildren(true); + for (int i = 0; i < colliders.Length; i++) + colliders[i].enabled = visible; + + Collider2D[] colliders2D = workspaceRoot.GetComponentsInChildren(true); + for (int i = 0; i < colliders2D.Length; i++) + colliders2D[i].enabled = visible; + } } \ No newline at end of file diff --git a/code/VR Art Gallery/Assets/VRMPAssets/Prefabs/UIPrefabs/Gallery Selection UI.prefab b/code/VR Art Gallery/Assets/VRMPAssets/Prefabs/UIPrefabs/Gallery Selection UI.prefab index 282e5bd..809fa78 100644 --- a/code/VR Art Gallery/Assets/VRMPAssets/Prefabs/UIPrefabs/Gallery Selection UI.prefab +++ b/code/VR Art Gallery/Assets/VRMPAssets/Prefabs/UIPrefabs/Gallery Selection UI.prefab @@ -857,9 +857,9 @@ RectTransform: m_Children: [] m_Father: {fileID: 440948657677736307} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 1} - m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 20, y: -12} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} m_SizeDelta: {x: 40, y: 14} m_Pivot: {x: 0.5, y: 0.5} --- !u!222 &440948656723770330 @@ -1639,9 +1639,9 @@ RectTransform: - {fileID: 440948656723770332} m_Father: {fileID: 440948657680231979} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 1} - m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 304, y: -12} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} m_SizeDelta: {x: 608, y: 24} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &440948657677736306 @@ -3741,9 +3741,9 @@ RectTransform: m_Children: [] m_Father: {fileID: 2472384503224016385} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 1} - m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 15, y: -12} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} m_SizeDelta: {x: 30, y: 30} m_Pivot: {x: 0.5, y: 0.5} --- !u!222 &7956025665359965660 @@ -8638,9 +8638,9 @@ RectTransform: m_Children: [] m_Father: {fileID: 1223310419018911616} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 1} - m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 15, y: -12} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} m_SizeDelta: {x: 30, y: 30} m_Pivot: {x: 0.5, y: 0.5} --- !u!222 &8569151047129022020 @@ -10927,7 +10927,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 7468343364591670855, guid: d9700882b8551b240a1e635b782657ae, type: 3} propertyPath: m_Size - value: 0.9999999 + value: 1 objectReference: {fileID: 0} - target: {fileID: 7468343364591670855, guid: d9700882b8551b240a1e635b782657ae, type: 3} propertyPath: m_Value @@ -12537,19 +12537,19 @@ PrefabInstance: m_Modifications: - target: {fileID: 715407541506831666, guid: 9751b51ddd005974bb2afb20cc27e4f5, type: 3} propertyPath: m_AnchorMax.y - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 715407541506831666, guid: 9751b51ddd005974bb2afb20cc27e4f5, type: 3} propertyPath: m_AnchorMin.y - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 715407541506831666, guid: 9751b51ddd005974bb2afb20cc27e4f5, type: 3} propertyPath: m_AnchoredPosition.x - value: 20 + value: 0 objectReference: {fileID: 0} - target: {fileID: 715407541506831666, guid: 9751b51ddd005974bb2afb20cc27e4f5, type: 3} propertyPath: m_AnchoredPosition.y - value: -15 + value: 0 objectReference: {fileID: 0} - target: {fileID: 1027347858865070824, guid: 9751b51ddd005974bb2afb20cc27e4f5, type: 3} propertyPath: m_AnchorMax.y @@ -12771,19 +12771,19 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 7763463321467176826, guid: 9751b51ddd005974bb2afb20cc27e4f5, type: 3} propertyPath: m_AnchorMax.y - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 7763463321467176826, guid: 9751b51ddd005974bb2afb20cc27e4f5, type: 3} propertyPath: m_AnchorMin.y - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 7763463321467176826, guid: 9751b51ddd005974bb2afb20cc27e4f5, type: 3} propertyPath: m_AnchoredPosition.x - value: 50 + value: 0 objectReference: {fileID: 0} - target: {fileID: 7763463321467176826, guid: 9751b51ddd005974bb2afb20cc27e4f5, type: 3} propertyPath: m_AnchoredPosition.y - value: -12 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8440492077479114691, guid: 9751b51ddd005974bb2afb20cc27e4f5, type: 3} propertyPath: m_Pivot.x @@ -14302,19 +14302,19 @@ PrefabInstance: m_Modifications: - target: {fileID: 715407541506831666, guid: 9751b51ddd005974bb2afb20cc27e4f5, type: 3} propertyPath: m_AnchorMax.y - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 715407541506831666, guid: 9751b51ddd005974bb2afb20cc27e4f5, type: 3} propertyPath: m_AnchorMin.y - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 715407541506831666, guid: 9751b51ddd005974bb2afb20cc27e4f5, type: 3} propertyPath: m_AnchoredPosition.x - value: 20 + value: 0 objectReference: {fileID: 0} - target: {fileID: 715407541506831666, guid: 9751b51ddd005974bb2afb20cc27e4f5, type: 3} propertyPath: m_AnchoredPosition.y - value: -15 + value: 0 objectReference: {fileID: 0} - target: {fileID: 1027347858865070824, guid: 9751b51ddd005974bb2afb20cc27e4f5, type: 3} propertyPath: m_AnchorMax.y @@ -14482,19 +14482,19 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 7763463321467176826, guid: 9751b51ddd005974bb2afb20cc27e4f5, type: 3} propertyPath: m_AnchorMax.y - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 7763463321467176826, guid: 9751b51ddd005974bb2afb20cc27e4f5, type: 3} propertyPath: m_AnchorMin.y - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 7763463321467176826, guid: 9751b51ddd005974bb2afb20cc27e4f5, type: 3} propertyPath: m_AnchoredPosition.x - value: 50 + value: 0 objectReference: {fileID: 0} - target: {fileID: 7763463321467176826, guid: 9751b51ddd005974bb2afb20cc27e4f5, type: 3} propertyPath: m_AnchoredPosition.y - value: -12 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8440492077479114691, guid: 9751b51ddd005974bb2afb20cc27e4f5, type: 3} propertyPath: m_Pivot.x diff --git a/code/VR Art Gallery/Assets/VRMPAssets/Scripts/UI/LobbyList/HUDSpawnerScript.cs b/code/VR Art Gallery/Assets/VRMPAssets/Scripts/UI/LobbyList/HUDSpawnerScript.cs index 81d74e8..58b9a93 100644 --- a/code/VR Art Gallery/Assets/VRMPAssets/Scripts/UI/LobbyList/HUDSpawnerScript.cs +++ b/code/VR Art Gallery/Assets/VRMPAssets/Scripts/UI/LobbyList/HUDSpawnerScript.cs @@ -10,40 +10,201 @@ public class HUDSpawner : MonoBehaviour [Header("HUD")] public GameObject hudObject; + public string hudPanelName = "Gallery Selection UI"; public Transform headCamera; public float spawnDistance = 1.5f; public float spawnHeightOffset = 0.5f; - private InputActionReference ActiveAction => - PlatformModeManager.CurrentMode == PlatformMode.VR ? vrSpawnButton : desktopSpawnButton; + [Header("Spawn Comfort")] + public float minVisibleDistance = 0.45f; + public float maxVisibleDistance = 0.75f; + public float minVisibleHeightOffset = -0.15f; + public float maxVisibleHeightOffset = 0.07f; + public bool keepPanelLevel = false; + public Vector3 panelRotationOffsetEuler = new Vector3(0f, 180f, 0f); + + private InputAction m_RuntimeVrAction; + private InputAction m_RuntimeDesktopAction; + private bool m_WarnedMissingHud; + private bool m_WarnedMissingHudPanel; + private GameObject m_HudPanelObject; private void OnEnable() { - ActiveAction.action.performed += OnTogglePressed; - ActiveAction.action.Enable(); + SubscribeAction(GetOrCreateVrAction()); + SubscribeAction(GetOrCreateDesktopAction()); } private void OnDisable() { - ActiveAction.action.performed -= OnTogglePressed; - ActiveAction.action.Disable(); + UnsubscribeAction(GetOrCreateVrAction()); + UnsubscribeAction(GetOrCreateDesktopAction()); + } + + private void OnDestroy() + { + m_RuntimeVrAction?.Dispose(); + m_RuntimeDesktopAction?.Dispose(); + } + + private InputAction GetOrCreateVrAction() + { + if (vrSpawnButton != null && vrSpawnButton.action != null) + return vrSpawnButton.action; + + if (m_RuntimeVrAction == null) + m_RuntimeVrAction = new InputAction("ToggleHUD_VR", InputActionType.Button, "{LeftHand}/primaryButton"); + + return m_RuntimeVrAction; + } + + private InputAction GetOrCreateDesktopAction() + { + if (desktopSpawnButton != null && desktopSpawnButton.action != null) + return desktopSpawnButton.action; + + if (m_RuntimeDesktopAction == null) + m_RuntimeDesktopAction = new InputAction("ToggleHUD_Desktop", InputActionType.Button, "/h"); + + return m_RuntimeDesktopAction; + } + + private void SubscribeAction(InputAction action) + { + if (action == null) + return; + + action.performed -= OnTogglePressed; + action.performed += OnTogglePressed; + action.Enable(); + } + + private void UnsubscribeAction(InputAction action) + { + if (action == null) + return; + + action.performed -= OnTogglePressed; + + if (action == m_RuntimeVrAction || action == m_RuntimeDesktopAction) + action.Disable(); } private void OnTogglePressed(InputAction.CallbackContext ctx) { - if (hudObject == null || hudObject.transform.childCount == 0) return; + if (hudObject == null) + hudObject = GameObject.Find("World Space Canvas"); - GameObject child = hudObject.transform.GetChild(0).gameObject; - bool show = !child.activeSelf; + if (hudObject == null) + { + if (!m_WarnedMissingHud) + { + Debug.LogWarning("[HUDSpawner] No HUD object assigned. Assign hudObject in inspector or ensure 'World Space Canvas' exists."); + m_WarnedMissingHud = true; + } + return; + } + + GameObject panelToToggle = ResolveHudPanelObject(); + if (panelToToggle == null) + { + if (!m_WarnedMissingHudPanel) + { + Debug.LogWarning($"[HUDSpawner] Could not find HUD panel '{hudPanelName}' under '{hudObject.name}'."); + m_WarnedMissingHudPanel = true; + } + return; + } + + bool show = !panelToToggle.activeSelf; if (show) { - if (headCamera == null) headCamera = Camera.main.transform; - Vector3 pos = headCamera.position + headCamera.forward * spawnDistance; - pos.y += spawnHeightOffset; - hudObject.transform.SetPositionAndRotation(pos, Quaternion.LookRotation(headCamera.forward)); + Transform currentHead = ResolveCurrentHeadCamera(); + + if (currentHead == null) + return; + + float clampedDistance = Mathf.Clamp(spawnDistance, minVisibleDistance, maxVisibleDistance); + float clampedHeightOffset = Mathf.Clamp(spawnHeightOffset, minVisibleHeightOffset, maxVisibleHeightOffset); + + Vector3 forward = keepPanelLevel + ? Vector3.ProjectOnPlane(currentHead.forward, Vector3.up) + : currentHead.forward; + + if (forward.sqrMagnitude < 0.0001f) + forward = currentHead.forward; + + forward.Normalize(); + + Vector3 upAxis = keepPanelLevel ? Vector3.up : currentHead.up; + Vector3 pos = currentHead.position + forward * clampedDistance + upAxis * clampedHeightOffset; + + Vector3 lookDirection = keepPanelLevel + ? Vector3.ProjectOnPlane(currentHead.position - pos, Vector3.up) + : (currentHead.position - pos); + + if (lookDirection.sqrMagnitude < 0.0001f) + lookDirection = -forward; + + Vector3 lookUp = keepPanelLevel ? Vector3.up : currentHead.up; + Quaternion baseRotation = Quaternion.LookRotation(lookDirection.normalized, lookUp); + Quaternion finalRotation = baseRotation * Quaternion.Euler(panelRotationOffsetEuler); + + panelToToggle.transform.SetPositionAndRotation(pos, finalRotation); + } + + panelToToggle.SetActive(show); + } + + private GameObject ResolveHudPanelObject() + { + if (m_HudPanelObject != null) + return m_HudPanelObject; + + if (hudObject == null) + return null; + + if (string.IsNullOrWhiteSpace(hudPanelName)) + { + m_HudPanelObject = hudObject; + return m_HudPanelObject; + } + + GameObject globalMatch = GameObject.Find(hudPanelName); + if (globalMatch != null) + { + m_HudPanelObject = globalMatch; + return m_HudPanelObject; + } + + if (hudObject.name == hudPanelName) + { + m_HudPanelObject = hudObject; + return m_HudPanelObject; + } + + Transform[] descendants = hudObject.GetComponentsInChildren(true); + for (int i = 0; i < descendants.Length; i++) + { + if (descendants[i].name == hudPanelName) + { + m_HudPanelObject = descendants[i].gameObject; + return m_HudPanelObject; + } + } + + return null; + } + + private Transform ResolveCurrentHeadCamera() + { + if (Camera.main != null) + { + headCamera = Camera.main.transform; + return headCamera; } - child.SetActive(show); + return headCamera; } } \ No newline at end of file diff --git a/code/VR Art Gallery/Assets/VRMPAssets/Scripts/UI/PlayerOptions.cs b/code/VR Art Gallery/Assets/VRMPAssets/Scripts/UI/PlayerOptions.cs index 10f4a2f..7f45598 100644 --- a/code/VR Art Gallery/Assets/VRMPAssets/Scripts/UI/PlayerOptions.cs +++ b/code/VR Art Gallery/Assets/VRMPAssets/Scripts/UI/PlayerOptions.cs @@ -49,6 +49,8 @@ public class PlayerOptions : MonoBehaviour [SerializeField] Vector2 m_MinMaxTurnAmount = new Vector2(15.0f, 180.0f); [SerializeField] float m_SnapTurnUpdateAmount = 15.0f; + private const string LeftPrimaryButtonPathFragment = "{LeftHand}/primaryButton"; + VoiceChatManager m_VoiceChatManager; DynamicMoveProvider m_MoveProvider; SnapTurnProvider m_TurnProvider; @@ -74,7 +76,7 @@ private void Awake() ConnectOnline(false); if (m_ToggleMenuAction != null) - m_ToggleMenuAction.action.performed += ctx => ToggleMenu(); + m_ToggleMenuAction.action.performed += OnToggleMenuPerformed; else Utils.Log("No toggle menu action assigned to OptionsPanel", 1); @@ -117,6 +119,19 @@ private void OnDestroy() m_VoiceChatManager.connectionStatus.Unsubscribe(UpdateVoiceChatStatus); m_InputVolumeSlider.onValueChanged.RemoveListener(SetInputVolume); m_OutputVolumeSlider.onValueChanged.RemoveListener(SetOutputVolume); + + if (m_ToggleMenuAction != null) + m_ToggleMenuAction.action.performed -= OnToggleMenuPerformed; + } + + private void OnToggleMenuPerformed(InputAction.CallbackContext ctx) + { + // Keep the left primary (X) reserved for HUD toggling. + string controlPath = ctx.control?.path; + if (!string.IsNullOrEmpty(controlPath) && controlPath.Contains(LeftPrimaryButtonPathFragment)) + return; + + ToggleMenu(); } private void Update()