using UnityEngine; using System.Collections; using System.Collections.Generic; using System.Linq; using SimpleJSON; using System; using static FreeControllerV3; namespace TBD { public class CameraPinnedUI : MVRScript { private Atom _uiRoot; private JSONStorableStringChooser _uiRootChooser; private UIDynamicPopup _popup; private JSONStorableFloat _rotationOffsetX; private JSONStorableFloat _rotationOffsetY; private JSONStorableFloat _rotationOffsetZ; private UIDynamicSlider _rotationOffsetSliderX; private UIDynamicSlider _rotationOffsetSliderY; private UIDynamicSlider _rotationOffsetSliderZ; private Vector3? _offset; private Vector3? _originalPosition; private Quaternion? _originalRotation; private bool _initialized, _isRestoring; private Coroutine _coroutine; private static void Log(Exception exception) => SuperController.LogError($@"{exception.Message} {exception.StackTrace}"); private static JSONClass GetJSON(Vector3 vector) => new JSONClass { ["x"] = new JSONData(vector.x), ["y"] = new JSONData(vector.y), ["z"] = new JSONData(vector.z), }; private static JSONClass GetJSON(Quaternion quaternion) => GetJSON(quaternion.eulerAngles); private static Vector3 VectorFromJSON(JSONNode json) => new Vector3(json["x"].AsFloat, json["y"].AsFloat, json["z"].AsFloat); private static Quaternion QuaternionFromJSON(JSONNode json) => Quaternion.Euler(VectorFromJSON(json)); private static bool AtomTypeFilter(Atom atom) { switch (atom.type) { case "Empty": case "UIButton": case "UISlider": case "UIText": case "UIToggle": return true; default: return false; } } public override void Init() { if (SuperController.singleton.isOVR || SuperController.singleton.isOpenVR) { SuperController.LogMessage("TBD.CameraPinnedUI: VR mode detected, disabling plugin"); enabledJSON.val = false; return; } var atoms = new List(SuperController.singleton.GetAtoms().Where(AtomTypeFilter).Where(a => a != containingAtom).Select(a => a.name)); _uiRootChooser = new JSONStorableStringChooser("UI Root", atoms, null, "UI Root", HandleUIRootChanged); RegisterStringChooser(_uiRootChooser); _popup = CreateFilterablePopup(_uiRootChooser); _rotationOffsetX = new JSONStorableFloat("Rotation offset X", 0f, (float v) => UpdateRotation(), -180f, 180f, true, true); _rotationOffsetY = new JSONStorableFloat("Rotation offset Y", 0f, (float v) => UpdateRotation(), -180f, 180f, true, true); _rotationOffsetZ = new JSONStorableFloat("Rotation offset Z", 0f, (float v) => UpdateRotation(), -180f, 180f, true, true); RegisterFloat(_rotationOffsetX); RegisterFloat(_rotationOffsetY); RegisterFloat(_rotationOffsetZ); _rotationOffsetSliderX = CreateSlider(_rotationOffsetX); _rotationOffsetSliderY = CreateSlider(_rotationOffsetY); _rotationOffsetSliderZ = CreateSlider(_rotationOffsetZ); _initialized = true; } public override JSONClass GetJSON(bool includePhysical = true, bool includeAppearance = true, bool forceStore = false) { var json = base.GetJSON(includePhysical, includeAppearance, forceStore); try { var centerCamera = SuperController.singleton.MonitorCenterCamera; var offset = containingAtom.mainController.transform.position - centerCamera.transform.position; json["offset"] = GetJSON(offset); if (_originalPosition.HasValue) json["original position"] = GetJSON(_originalPosition.Value); if (_originalRotation.HasValue) json["original rotation"] = GetJSON(_originalRotation.Value); } catch (Exception e) { Log(e); } return json; } public override void RestoreFromJSON(JSONClass jc, bool restorePhysical = true, bool restoreAppearance = true, JSONArray presetAtoms = null, bool setMissingToDefault = true) { _isRestoring = true; base.RestoreFromJSON(jc, restorePhysical, restoreAppearance, presetAtoms, setMissingToDefault); try { if (jc.HasKey("offset")) _offset = VectorFromJSON(jc["offset"]); if (jc.HasKey("original position")) _originalPosition = VectorFromJSON(jc["original position"]); if (jc.HasKey("original rotation")) _originalRotation = QuaternionFromJSON(jc["original rotation"]); } catch (Exception e) { Log(e); } } public override void LateRestoreFromJSON(JSONClass jc, bool restorePhysical = true, bool restoreAppearance = true, bool setMissingToDefault = true) { base.LateRestoreFromJSON(jc, restorePhysical, restoreAppearance, setMissingToDefault); _isRestoring = false; SetPinMode(enabledJSON.val); } public void OnEnable() { if (SuperController.singleton.isOVR || SuperController.singleton.isOpenVR) { SuperController.LogMessage("TBD.CameraPinnedUI: VR mode detected, disabling plugin"); enabledJSON.val = false; } else SetPinMode(true); } public void OnDisable() => SetPinMode(false); private void HandleUIRootChanged(string val) => SetPinMode(enabledJSON.val); private void UpdateRotation() { if (!_initialized || _isRestoring) return; var navigationRig = SuperController.singleton.navigationRig; var centerCamera = SuperController.singleton.MonitorCenterCamera; containingAtom.mainController.currentRotationState = RotationState.On; containingAtom.mainController.transform.eulerAngles = new Vector3(centerCamera.transform.eulerAngles.x, navigationRig.eulerAngles.y, 0f); containingAtom.mainController.transform.forward = -containingAtom.mainController.transform.forward; containingAtom.mainController.transform.Rotate(_rotationOffsetX.val, _rotationOffsetY.val, _rotationOffsetZ.val); containingAtom.mainController.currentRotationState = RotationState.ParentLink; } private void SetPinMode(bool pinMode) { if (_initialized && !_isRestoring) { if (_coroutine != null) StopCoroutine(_coroutine); _coroutine = StartCoroutine(SetPinModeRoutine(pinMode)); } } private IEnumerator SetPinModeRoutine(bool pinned) { if (!_initialized || _isRestoring) yield break; while (SuperController.singleton.isLoading) yield return new WaitForEndOfFrame(); while (SuperController.singleton.GetAtomByUid("[CameraRig]") == null) yield return new WaitForEndOfFrame(); var navigationRig = SuperController.singleton.navigationRig; var centerCamera = SuperController.singleton.MonitorCenterCamera; if (this == null || navigationRig == null || centerCamera == null || containingAtom == null) yield break; // work around for vam's monitor UI offset changing during atom restoration if (_offset.HasValue) { containingAtom.mainController.currentPositionState = PositionState.On; containingAtom.mainController.transform.position = centerCamera.transform.position + _offset.Value; _offset = null; } UpdateRotation(); yield return new WaitForFixedUpdate(); containingAtom.mainController.SetLinkToRigidbody("[CameraRig]:CenterEye"); containingAtom.mainController.SelectLinkToRigidbody(containingAtom.mainController.linkToRB, SelectLinkState.PositionAndRotation); yield return new WaitForFixedUpdate(); containingAtom.SetParentAtomSelectPopupValues(); containingAtom.SetParentAtom("[CameraRig]"); // don't ask me why this is necessary. currentValue should be set to parent.uid in SetParent() if (containingAtom.parentAtomSelectionPopup != null) containingAtom.parentAtomSelectionPopup.currentValue = "[CameraRig]"; var uiRoot = SuperController.singleton.GetAtomByUid(_uiRootChooser.val); if (_uiRoot != null && _uiRoot != uiRoot) { _uiRoot.ClearParentAtom(); yield return new WaitForFixedUpdate(); _uiRoot.mainController.currentPositionState = PositionState.On; _uiRoot.mainController.currentRotationState = RotationState.On; _uiRoot.mainController.linkToRB = null; yield return new WaitForFixedUpdate(); if (_originalPosition.HasValue) _uiRoot.mainController.transform.position = _originalPosition.Value; if (_originalRotation.HasValue) _uiRoot.mainController.transform.rotation = _originalRotation.Value; } _uiRoot = uiRoot; if (_uiRoot == null) yield break; _uiRoot.mainController.currentPositionState = PositionState.On; _uiRoot.mainController.currentRotationState = RotationState.On; _uiRoot.mainController.linkToRB = null; yield return new WaitForFixedUpdate(); if (pinned) { // if it has a parent at this point, we're loading a saved scene and not toggling the pin mode on/off if (_uiRoot.parentAtom == null) { _originalPosition = _uiRoot.mainController.transform.position; _originalRotation = _uiRoot.mainController.transform.rotation; } _uiRoot.mainController.transform.position = containingAtom.mainController.transform.position; _uiRoot.mainController.transform.rotation = containingAtom.mainController.transform.rotation; yield return new WaitForFixedUpdate(); _uiRoot.mainController.SetLinkToRigidbody($"{containingAtom.name}:control"); _uiRoot.mainController.SelectLinkToRigidbody(_uiRoot.mainController.linkToRB, SelectLinkState.PositionAndRotation); _uiRoot.SetParentAtomSelectPopupValues(); _uiRoot.SetParentAtom(containingAtom); // don't ask me why this is necessary. currentValue should be set to parent.uid in SetParent() if (_uiRoot.parentAtomSelectionPopup != null) _uiRoot.parentAtomSelectionPopup.currentValue = containingAtom.name; } else { _uiRoot.ClearParentAtom(); if (_uiRoot.mainController.linkToSelectionPopup != null) _uiRoot.mainController.linkToSelectionPopup.currentValue = "None"; if (_uiRoot.mainController.linkToAtomSelectionPopup != null) _uiRoot.mainController.linkToAtomSelectionPopup.currentValue = "None"; yield return new WaitForFixedUpdate(); if (_originalPosition.HasValue) _uiRoot.mainController.transform.position = _originalPosition.Value; if (_originalRotation.HasValue) _uiRoot.mainController.transform.rotation = _originalRotation.Value; } } public void OnDestroy() { if (_popup != null) RemovePopup(_popup); if (_rotationOffsetSliderX != null) RemoveSlider(_rotationOffsetSliderX); if (_rotationOffsetSliderY != null) RemoveSlider(_rotationOffsetSliderY); if (_rotationOffsetSliderZ != null) RemoveSlider(_rotationOffsetSliderZ); if (_coroutine != null) StopCoroutine(_coroutine); } } }