//======= Copyright (c) Stereolabs Corporation, All rights reserved. =============== using UnityEngine; using System.IO; #if UNITY_EDITOR using UnityEditor; #endif /// /// Saves and loads the pose of this object relative to its parent. /// Used primarily when mounting the ZED to a tracked object (like a VR controller) for 3rd person mixed reality. /// This way, you can calibrate its position/rotation to line up the real/virtual worlds, and hit Save. /// It will automatically load a calibration in the future if it's found. /// Note that if you used our beta tool for SteamVR calibration, this script will load that calibration automatically. /// public class ZEDOffsetController : MonoBehaviour { /// /// ZED offset file name. /// [SerializeField] public static string ZEDOffsetFile = "ZED_Position_Offset.conf"; /// /// Where to save the ZED offset file. /// private string path = @"Stereolabs\steamvr"; /// /// The ZEDControllerTracker object in the scene from which we're offset. /// This script checks this object, its parents and its children (in that order) for such a component. /// public ZEDControllerTracker controllerTracker; /// /// If the object is instantiated and ready to save/load an offset file. /// Used by the custom Inspector editor to know if the Save/Load buttons should be pressable. /// public bool isReady = false; /// /// Save the local position/rotation of the ZED into an offset file. /// public void SaveZEDPos() { using (System.IO.StreamWriter file = new System.IO.StreamWriter(path)) { string tx = "x=" + transform.localPosition.x.ToString() + " //Translation x"; string ty = "y=" + transform.localPosition.y.ToString() + " //Translation y"; string tz = "z=" + transform.localPosition.z.ToString() + " //Translation z"; string rx = "rx=" + transform.localRotation.eulerAngles.x.ToString() + " //Rotation x"; string ry = "ry=" + transform.localRotation.eulerAngles.y.ToString() + " //Rotation y"; string rz = "rz=" + transform.localRotation.eulerAngles.z.ToString() + " //Rotation z"; //Write those values into the file. file.WriteLine(tx); file.WriteLine(ty); file.WriteLine(tz); file.WriteLine(rx); file.WriteLine(ry); file.WriteLine(rz); #if ZED_STEAM_VR if (TrackerComponentExist()) { //If using SteamVR, get the serial number of the tracked device, or write "NONE" to indicate we checked but couldn't find it. string result = "indexController = "; if (controllerTracker.index > 0) { var snerror = Valve.VR.ETrackedPropertyError.TrackedProp_Success; var snresult = new System.Text.StringBuilder((int)64); result += Valve.VR.OpenVR.System.GetStringTrackedDeviceProperty((uint)controllerTracker.index, Valve.VR.ETrackedDeviceProperty.Prop_SerialNumber_String, snresult, 64, ref snerror); //OpenVR.System.GetStringTrackedDeviceProperty((uint)index, ETrackedDeviceProperty.Prop_SerialNumber_String, snresult, 64, ref snerror); } else { result += "NONE"; } file.WriteLine(result); } #endif file.Close(); //Finalize the new file. } } /// /// Whether there is a referenced ZEDControllerTracker object in this object, a parent, or a child. /// /// True if such a component exists and is used to handle the offset. public bool TrackerComponentExist() { if (controllerTracker != null) return true; else return false; } private void OnEnable() { LoadTrackerComponent(); } /// /// Searched for a ZEDControllerTracker component in this object, its parents, and its children. /// Sets the controllerTracker value to the first one it finds. /// private void LoadTrackerComponent() { ZEDControllerTracker zct = GetComponent(); if (zct == null) zct = GetComponentInParent(); if (zct == null) zct = GetComponentInChildren(); if (zct != null) controllerTracker = zct; } /// /// Tries to find the relevant ZEDControllerTracker object, and loads the existing /// offset file if there is one. /// void Awake() { LoadTrackerComponent(); string folder = System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData); string specificFolder = Path.Combine(folder, @"Stereolabs\steamvr"); path = Path.Combine(specificFolder, ZEDOffsetFile); // Check if folder exists and if not, create it if (!Directory.Exists(specificFolder)) Directory.CreateDirectory(specificFolder); LoadZEDPos(); CreateFileWatcher(specificFolder); isReady = true; } private void Update() { if (isReady) LoadZEDPos(); } /// /// Loads the offset file and sets the local position/rotation to the loaded values. /// public void LoadZEDPos() { if (!System.IO.File.Exists(path)) return; string[] lines = null; try { lines = System.IO.File.ReadAllLines(path); } catch (System.Exception) { controllerTracker.SNHolder = "NONE"; } if (lines == null) { controllerTracker.SNHolder = "NONE"; return; } if (lines == null) return; Vector3 position = new Vector3(0, 0, 0); Vector3 eulerRotation = new Vector3(0, 0, 0); foreach (string line in lines) { string[] splittedLine = line.Split('='); if (splittedLine != null && splittedLine.Length >= 2) { string key = splittedLine[0]; string field = splittedLine[1].Split(' ')[0]; if (key == "x") { position.x = float.Parse(field, System.Globalization.CultureInfo.InvariantCulture); } else if (key == "y") { position.y = float.Parse(field, System.Globalization.CultureInfo.InvariantCulture); } else if (key == "z") { position.z = float.Parse(field, System.Globalization.CultureInfo.InvariantCulture); } else if (key == "rx") { eulerRotation.x = float.Parse(field, System.Globalization.CultureInfo.InvariantCulture); } else if (key == "ry") { eulerRotation.y = float.Parse(field, System.Globalization.CultureInfo.InvariantCulture); } else if (key == "rz") { eulerRotation.z = float.Parse(field, System.Globalization.CultureInfo.InvariantCulture); } else if (key == "indexController") { LoadTrackerComponent(); if (TrackerComponentExist()) { controllerTracker.SNHolder = field; } } } } transform.localPosition = position; transform.localRotation = Quaternion.Euler(eulerRotation.x, eulerRotation.y, eulerRotation.z); } /// /// Creates a FileSystemWatcher that keeps track of the offset file, in case it /// changes or moves. /// /// public void CreateFileWatcher(string path) { // Create a new FileSystemWatcher and set its properties. FileSystemWatcher watcher = new FileSystemWatcher(); watcher.Path = path; /* Watch for changes in LastAccess and LastWrite times, and the renaming of files or directories. */ watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; // Only watch text files. watcher.Filter = ZEDOffsetFile; // Add event handlers. watcher.Changed += new FileSystemEventHandler(OnChanged); // Begin watching. watcher.EnableRaisingEvents = true; } /// /// Event handler for when the offset file changes or moves. /// Called by the FileSystemWatcher created in CreateFileWatcher(). /// /// /// private void OnChanged(object source, FileSystemEventArgs e) { if (TrackerComponentExist()) { LoadZEDPos(); } } } #if UNITY_EDITOR /// /// Custom editor for ZEDOffsetController, to define its Inspector layout. /// Specifically, it doesn't draw public fields like normal but instead places Save/Load buttons /// for the offset file that are only pressable during runtime. /// [CustomEditor(typeof(ZEDOffsetController))] public class ZEDPositionEditor : Editor { private ZEDOffsetController positionManager; public void OnEnable() { positionManager = (ZEDOffsetController)target; } public override void OnInspectorGUI() //Called when the Inspector GUI becomes visible, or changes at all. { GUILayout.Space(5); EditorGUILayout.BeginHorizontal(); EditorGUILayout.EndHorizontal(); EditorGUILayout.BeginHorizontal(); GUI.enabled = positionManager.isReady; GUIContent savecontent = new GUIContent("Save Offset", "Saves the object's local position/rotation to a text file to be loaded anytime in the future."); if (GUILayout.Button(savecontent)) { positionManager.SaveZEDPos(); } GUIContent loadcontent = new GUIContent("Load Offset", "Loads local position/rotation from an offset file previously saved, or created by the beta ZED calibration tool."); if (GUILayout.Button(loadcontent)) { positionManager.LoadZEDPos(); } EditorGUILayout.EndHorizontal(); } } #endif