void Update() { bool isVR = VRDevices.loadedSdk != VRDevices.LoadedSdk.None; Seekbar.instance.gameObject.SetActive(!isVR); Seekbar.instanceVR.gameObject.SetActive(isVR); //NOTE(Simon): Sync videoController/videoMesh pos with camera pos. videoController.transform.position = Camera.main.transform.position; var interactionpointRay = new Ray(); //NOTE(Kristof): Deciding on which object the Ray will be based on //NOTE(Simon): Prefers right over left controller { if (trackedControllerLeft != null && trackedControllerLeft.triggerPressed) { interactionpointRay = trackedControllerLeft.CastRay(); } if (trackedControllerRight != null && trackedControllerRight.triggerPressed) { interactionpointRay = trackedControllerRight.CastRay(); } if (VRDevices.hasNoControllers && Input.GetMouseButtonUp(0)) { interactionpointRay = Camera.main.ScreenPointToRay(Input.mousePosition); } } if (playerState == PlayerState.Watching) { RefreshShownInteractionPoints(); if (Input.GetKeyDown(KeyCode.Space) && !isVR) { videoController.TogglePlay(); } if (isVR) { Seekbar.instanceVR.RenderBlips(shownInteractionPoints); } else { Seekbar.instance.RenderBlips(shownInteractionPoints); } //Note(Simon): Interaction with points if (!chapterTransitionActive) { var reversedRay = interactionpointRay.ReverseRay(); //Note(Simon): Create a reversed raycast to find positions on the sphere with Physics.Raycast(reversedRay, out var hit, 100, 1 << LayerMask.NameToLayer("interactionPoints")); //NOTE(Simon): Update visible interactionpoints foreach (var point in shownInteractionPoints) { point.point.SetActive(true); point.point.GetComponent <InteractionPointRenderer>().SetPingActive(!point.isSeen); } foreach (var point in GetInactiveInteractionPoints()) { point.point.SetActive(false); } //NOTE(Simon): Activate hit interactionPoint if (activeInteractionPoint == null && hit.transform != null) { var pointGO = hit.transform.gameObject; InteractionPointPlayer point = null; for (int i = 0; i < interactionPoints.Count; i++) { if (pointGO == interactionPoints[i].point) { point = interactionPoints[i]; break; } } ActivateInteractionPoint(point); } //NOTE(Simon): Disable active interactionPoint if playback was started through seekbar if (videoController.playing && activeInteractionPoint != null) { DeactivateActiveInteractionPoint(); } } //NOTE(Simon): Handle mandatory interactionPoints if (!chapterTransitionActive) { double timeToNextPause = Double.MaxValue; var interactionsInChapter = MandatoryInteractionsForTime(videoController.currentTime); //NOTE(Simon): Find the next unseen mandatory interaction in this chapter for (int i = 0; i < interactionsInChapter.Count; i++) { if (!interactionsInChapter[i].isSeen && interactionsInChapter[i].endTime > videoController.currentTime) { timeToNextPause = interactionsInChapter[i].endTime - videoController.currentTime; break; } } if (timeToNextPause < pauseFadeTime) { videoController.SetPlaybackSpeed(0f); if (!mandatoryPauseActive) { ShowMandatoryInteractionMessage(); mandatoryPauseActive = true; } } else { if (mandatoryPauseActive) { mandatoryPauseActive = false; HideMandatoryInteractionMessage(); } videoController.SetPlaybackSpeed(1f); } } //NOTE(Simon): Handle chapter transitions if (!chapterTransitionActive && !mandatoryPauseActive) { var currentChapter = ChapterManager.Instance.ChapterForTime(videoController.currentTime); if (currentChapter != previousChapter) { videoController.SetPlaybackSpeed(0); chapterTransitionActive = true; chapterTransitionPanel.SetChapter(currentChapter); chapterTransitionPanel.StartTransition(OnChapterTransitionFinish); previousChapter = currentChapter; } } } if (playerState == PlayerState.Opening) { var panel = indexPanel.GetComponent <IndexPanel>(); if (panel.answered) { var projectPath = Path.Combine(Application.persistentDataPath, panel.answerVideoId); if (OpenFile(projectPath)) { Destroy(indexPanel); playerState = PlayerState.Watching; Canvass.modalBackground.SetActive(false); chapterTransitionActive = false; chapterSelector = Instantiate(chapterSelectorPrefab, Canvass.main.transform, false).GetComponent <ChapterSelectorPanel>(); if (isVR) { chapterSelector.transform.SetParent(chapterSelectorHolderVR.transform, false); chapterSelector.transform.localPosition = Vector3.zero; } chapterSelector.Init(videoController); } else { Debug.LogError("Couldn't open savefile"); } } } //NOTE(Simon): Interaction with Hittables { Physics.Raycast(interactionpointRay, out var hit, 100, LayerMask.GetMask("UI", "WorldUI")); var controllerList = new List <Controller> { trackedControllerLeft, trackedControllerRight }; //NOTE(Simon): Reset all hittables foreach (var hittable in hittables) { if (hittable == null) { continue; } //NOTE(Jitse): Check if a hittable is being held down if (!(controllerList[0].triggerDown || controllerList[1].triggerDown)) { hittable.hitting = false; } hittable.hovering = false; } //NOTE(Simon): Set hover state when hovered by controllers foreach (var con in controllerList) { if (con.uiHovering && con.hoveredGo != null) { var hittable = con.hoveredGo.GetComponent <Hittable>(); if (hittable != null) { hittable.hovering = true; } } } //NOTE(Simon): Set hitting and hovering in hittables if (hit.transform != null) { var hittable = hit.transform.GetComponent <Hittable>(); if (hittable != null) { hittable.hitting = true; } } } }
void Update() { //NOTE(Kristof): VR specific behaviour { if (XRSettings.enabled) { videoController.transform.position = Camera.main.transform.position; Canvass.seekbar.gameObject.SetActive(true); //NOTE(Kristof): Rotating the seekbar { //NOTE(Kristof): Seekbar rotation is the same as the seekbar's angle on the circle var seekbarAngle = Vector2.SignedAngle(new Vector2(Canvass.seekbar.transform.position.x, Canvass.seekbar.transform.position.z), Vector2.up); var fov = Camera.main.fieldOfView; //NOTE(Kristof): Camera rotation tells you to which angle on the circle the camera is looking towards var cameraAngle = Camera.main.transform.eulerAngles.y; //NOTE(Kristof): Calculate the absolute degree angle from the camera to the seekbar var distanceLeft = Mathf.Abs((cameraAngle - seekbarAngle + 360) % 360); var distanceRight = Mathf.Abs((cameraAngle - seekbarAngle - 360) % 360); var angle = Mathf.Min(distanceLeft, distanceRight); if (isSeekbarOutOfView) { if (angle < 2.5f) { isSeekbarOutOfView = false; } } else { if (angle > fov) { isSeekbarOutOfView = true; } } if (isSeekbarOutOfView) { float newAngle = Mathf.LerpAngle(seekbarAngle, cameraAngle, 0.025f); //NOTE(Kristof): Angle needs to be reversed, in Unity postive angles go clockwise while they go counterclockwise in the unit circle (cos and sin) //NOTE(Kristof): We also need to add an offset of 90 degrees because in Unity 0 degrees is in front of you, in the unit circle it is (1,0) on the axis float radianAngle = (-newAngle + 90) * Mathf.PI / 180; float x = 1.8f * Mathf.Cos(radianAngle); float y = Camera.main.transform.position.y - 2f; float z = 1.8f * Mathf.Sin(radianAngle); Canvass.seekbar.transform.position = new Vector3(x, y, z); Canvass.seekbar.transform.eulerAngles = new Vector3(30, newAngle, 0); } } } else { Canvass.seekbar.gameObject.SetActive(false); } } //NOTE(Simon): Zoom when not using HMD if (!XRSettings.enabled) { if (Input.mouseScrollDelta.y != 0) { Camera.main.fieldOfView = Mathf.Clamp(Camera.main.fieldOfView - Input.mouseScrollDelta.y * 5, 20, 120); } } Ray interactionpointRay = new Ray(); //NOTE(Kristof): Deciding on which object the Ray will be based on //TODO(Simon): Prefers right over left controller { if (trackedControllerLeft != null && trackedControllerLeft.triggerPressed) { interactionpointRay = trackedControllerLeft.CastRay(); } if (trackedControllerRight != null && trackedControllerRight.triggerPressed) { interactionpointRay = trackedControllerRight.CastRay(); } if (VRDevices.loadedControllerSet == VRDevices.LoadedControllerSet.NoControllers && Input.GetMouseButtonUp(0)) { interactionpointRay = Camera.main.ScreenPointToRay(Input.mousePosition); } } if (playerState == PlayerState.Watching) { if (Input.GetKeyDown(KeyCode.Space) && VRDevices.loadedSdk == VRDevices.LoadedSdk.None) { videoController.TogglePlay(); } Seekbar.instance.RenderBlips(interactionPoints, trackedControllerLeft, trackedControllerRight); //Note(Simon): Interaction with points { var reversedRay = interactionpointRay.ReverseRay(); //Note(Simon): Create a reversed raycast to find positions on the sphere with Physics.Raycast(reversedRay, out var hit, 100, 1 << LayerMask.NameToLayer("interactionPoints")); //NOTE(Simon): Update visible interactionpoints for (int i = 0; i < interactionPoints.Count; i++) { bool pointActive = videoController.currentTime >= interactionPoints[i].startTime && videoController.currentTime <= interactionPoints[i].endTime; interactionPoints[i].point.SetActive(pointActive); interactionPoints[i].point.GetComponentInChildren <MeshRenderer>(includeInactive: true).gameObject.SetActive(!interactionPoints[i].isSeen); } //NOTE(Simon): Interact with inactive interactionpoints if (activeInteractionPoint == null && hit.transform != null) { var pointGO = hit.transform.gameObject; InteractionPointPlayer point = null; for (int i = 0; i < interactionPoints.Count; i++) { if (pointGO == interactionPoints[i].point) { point = interactionPoints[i]; break; } } ActivateInteractionPoint(point); } //NOTE(Simon): Disable active interactionPoint if playback was started through seekbar if (videoController.playing && activeInteractionPoint != null) { DeactivateActiveInteractionPoint(); } } //NOTE(Simon): Handle mandatory interactionPoints { double timeToNextPause = Double.MaxValue; var interactionsInChapter = MandatoryInteractionsForTime(videoController.currentTime); //NOTE(Simon): Find the next unseen mandatory interaction in this chapter for (int i = 0; i < interactionsInChapter.Count; i++) { if (!interactionsInChapter[i].isSeen && interactionsInChapter[i].endTime > videoController.currentTime) { timeToNextPause = interactionsInChapter[i].endTime - videoController.currentTime; break; } } //NOTE(Simon): Set the playbackspeed. Speed will get lower the closer to the next pause we are. if (timeToNextPause < mandatoryPauseFadeTime) { float speed = (float)(timeToNextPause / mandatoryPauseFadeTime); if (timeToNextPause < .05f) { speed = 0; } videoController.SetPlaybackSpeed(speed); if (!mandatoryPauseActive) { ShowMandatoryInteractionMessage(); mandatoryPauseActive = true; } } else { if (mandatoryPauseActive) { mandatoryPauseActive = false; HideMandatoryInteractionMessage(); } videoController.SetPlaybackSpeed(1f); } } } if (playerState == PlayerState.Opening) { var panel = indexPanel.GetComponent <IndexPanel>(); if (panel.answered) { var projectPath = Path.Combine(Application.persistentDataPath, panel.answerVideoId); if (OpenFile(projectPath)) { Destroy(indexPanel); playerState = PlayerState.Watching; Canvass.modalBackground.SetActive(false); SetCanvasesActive(true); chapterSelector = Instantiate(chapterSelectorPrefab, Canvass.main.transform, false).GetComponent <ChapterSelectorPanel>(); if (VRDevices.loadedSdk > VRDevices.LoadedSdk.None) { StartCoroutine(FadevideoCanvasOut(videoCanvas)); EventManager.OnSpace(); videoPositions.Clear(); Seekbar.ReattachCompass(); } } else { Debug.LogError("Couldn't open savefile"); } } } //NOTE(Simon): Interaction with Hittables { Physics.Raycast(interactionpointRay, out var hit, 100, LayerMask.GetMask("UI", "WorldUI")); var controllerList = new List <Controller> { trackedControllerLeft, trackedControllerRight }; //NOTE(Simon): Reset all hittables foreach (var hittable in hittables) { if (hittable == null) { continue; } //NOTE(Jitse): Check if a hittable is being held down if (!(controllerList[0].triggerDown || controllerList[1].triggerDown)) { hittable.hitting = false; } hittable.hovering = false; } //NOTE(Simon): Set hover state when hovered by controllers foreach (var con in controllerList) { if (con.uiHovering && con.hoveredGo != null) { var hittable = con.hoveredGo.GetComponent <Hittable>(); if (hittable != null) { hittable.hovering = true; } } } //NOTE(Simon): Set hitting and hovering in hittables if (hit.transform != null) { hit.transform.GetComponent <Hittable>().hitting = true; } } }