Assignment for RMIT Mixed Reality in 2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

479 lines
23 KiB

  1. namespace VRTK
  2. {
  3. using UnityEngine;
  4. using UnityEngine.SceneManagement;
  5. #if UNITY_2017_2_OR_NEWER
  6. using UnityEngine.XR;
  7. #else
  8. using XRSettings = UnityEngine.VR.VRSettings;
  9. #endif
  10. using UnityEditor;
  11. using UnityEditor.SceneManagement;
  12. using System;
  13. using System.Collections.Generic;
  14. using System.Collections.ObjectModel;
  15. using System.Linq;
  16. using System.Reflection;
  17. [CustomEditor(typeof(VRTK_SDKSetup))]
  18. public class VRTK_SDKSetupEditor : Editor
  19. {
  20. private const string SDKNotInstalledDescription = " (not installed)";
  21. private const string SDKNotFoundAnymoreDescription = " (not found)";
  22. private bool? isDetailedSDKSelectionFoldOut;
  23. public override void OnInspectorGUI()
  24. {
  25. VRTK_SDKSetup setup = (VRTK_SDKSetup)target;
  26. serializedObject.Update();
  27. using (new EditorGUILayout.VerticalScope("Box"))
  28. {
  29. VRTK_EditorUtilities.AddHeader("SDK Selection", false);
  30. Func<VRTK_SDKInfo, ReadOnlyCollection<VRTK_SDKInfo>, string> sdkNameSelector = (info, installedInfos)
  31. => info.description.prettyName + (installedInfos.Contains(info) ? "" : SDKNotInstalledDescription);
  32. string[] availableSystemSDKNames = VRTK_SDKManager.AvailableSystemSDKInfos.Select(info => sdkNameSelector(info, VRTK_SDKManager.InstalledSystemSDKInfos)).ToArray();
  33. string[] availableBoundariesSDKNames = VRTK_SDKManager.AvailableBoundariesSDKInfos.Select(info => sdkNameSelector(info, VRTK_SDKManager.InstalledBoundariesSDKInfos)).ToArray();
  34. string[] availableHeadsetSDKNames = VRTK_SDKManager.AvailableHeadsetSDKInfos.Select(info => sdkNameSelector(info, VRTK_SDKManager.InstalledHeadsetSDKInfos)).ToArray();
  35. string[] availableControllerSDKNames = VRTK_SDKManager.AvailableControllerSDKInfos.Select(info => sdkNameSelector(info, VRTK_SDKManager.InstalledControllerSDKInfos)).ToArray();
  36. string[] allAvailableSDKNames = availableSystemSDKNames
  37. .Concat(availableBoundariesSDKNames)
  38. .Concat(availableHeadsetSDKNames)
  39. .Concat(availableControllerSDKNames)
  40. .Distinct()
  41. .ToArray();
  42. using (new EditorGUILayout.HorizontalScope())
  43. {
  44. EditorGUI.BeginChangeCheck();
  45. List<GUIContent> quickSelectOptions = allAvailableSDKNames
  46. .Select(sdkName => new GUIContent(sdkName))
  47. .ToList();
  48. int quicklySelectedSDKIndex = 0;
  49. if (AreAllSDKsTheSame())
  50. {
  51. quicklySelectedSDKIndex = allAvailableSDKNames
  52. .ToList()
  53. .FindIndex(availableSDKName => availableSDKName.Replace(SDKNotInstalledDescription, "")
  54. == setup.systemSDKInfo.description.prettyName);
  55. }
  56. else
  57. {
  58. quickSelectOptions.Insert(0, new GUIContent("Mixed..."));
  59. }
  60. quicklySelectedSDKIndex = EditorGUILayout.Popup(
  61. new GUIContent("Quick Select", "Quickly select one of the SDKs into all slots."),
  62. quicklySelectedSDKIndex,
  63. quickSelectOptions.ToArray());
  64. if (!AreAllSDKsTheSame())
  65. {
  66. quicklySelectedSDKIndex--;
  67. }
  68. if (EditorGUI.EndChangeCheck() && (AreAllSDKsTheSame() || quicklySelectedSDKIndex != -1))
  69. {
  70. string quicklySelectedSDKName = allAvailableSDKNames[quicklySelectedSDKIndex].Replace(SDKNotInstalledDescription, "");
  71. Func<VRTK_SDKInfo, bool> predicate = info => info.description.prettyName == quicklySelectedSDKName;
  72. VRTK_SDKInfo newSystemSDKInfo = VRTK_SDKManager.AvailableSystemSDKInfos.FirstOrDefault(predicate);
  73. VRTK_SDKInfo newBoundariesSDKInfo = VRTK_SDKManager.AvailableBoundariesSDKInfos.FirstOrDefault(predicate);
  74. VRTK_SDKInfo newHeadsetSDKInfo = VRTK_SDKManager.AvailableHeadsetSDKInfos.FirstOrDefault(predicate);
  75. VRTK_SDKInfo newControllerSDKInfo = VRTK_SDKManager.AvailableControllerSDKInfos.FirstOrDefault(predicate);
  76. Undo.RecordObject(setup, "SDK Change (Quick Select)");
  77. if (newSystemSDKInfo != null)
  78. {
  79. setup.systemSDKInfo = newSystemSDKInfo;
  80. }
  81. if (newBoundariesSDKInfo != null)
  82. {
  83. setup.boundariesSDKInfo = newBoundariesSDKInfo;
  84. }
  85. if (newHeadsetSDKInfo != null)
  86. {
  87. setup.headsetSDKInfo = newHeadsetSDKInfo;
  88. }
  89. if (newControllerSDKInfo != null)
  90. {
  91. setup.controllerSDKInfo = newControllerSDKInfo;
  92. }
  93. UpdateDetailedSDKSelectionFoldOut();
  94. }
  95. if (AreAllSDKsTheSame())
  96. {
  97. VRTK_SDKInfo selectedInfo = new[]
  98. {
  99. setup.systemSDKInfo,
  100. setup.boundariesSDKInfo,
  101. setup.headsetSDKInfo,
  102. setup.controllerSDKInfo,
  103. }.First(info => info != null);
  104. DrawVRDeviceNameLabel(selectedInfo, "System, Boundaries, Headset and Controller", 0);
  105. }
  106. }
  107. EditorGUI.indentLevel++;
  108. if (!isDetailedSDKSelectionFoldOut.HasValue)
  109. {
  110. UpdateDetailedSDKSelectionFoldOut();
  111. }
  112. isDetailedSDKSelectionFoldOut = EditorGUI.Foldout(
  113. EditorGUILayout.GetControlRect(),
  114. isDetailedSDKSelectionFoldOut.Value,
  115. "Detailed Selection",
  116. true
  117. );
  118. if (isDetailedSDKSelectionFoldOut.Value)
  119. {
  120. EditorGUI.BeginChangeCheck();
  121. DrawAndHandleSDKSelection<SDK_BaseSystem>("The SDK to use to deal with all system actions.", 1);
  122. DrawAndHandleSDKSelection<SDK_BaseBoundaries>("The SDK to use to utilize room scale boundaries.", 1);
  123. DrawAndHandleSDKSelection<SDK_BaseHeadset>("The SDK to use to utilize the VR headset.", 1);
  124. DrawAndHandleSDKSelection<SDK_BaseController>("The SDK to use to utilize the input devices.", 1);
  125. if (EditorGUI.EndChangeCheck())
  126. {
  127. UpdateDetailedSDKSelectionFoldOut();
  128. }
  129. }
  130. EditorGUI.indentLevel--;
  131. string errorDescriptions = string.Join("\n", setup.GetSimplifiedErrorDescriptions());
  132. if (!string.IsNullOrEmpty(errorDescriptions))
  133. {
  134. EditorGUILayout.HelpBox(errorDescriptions, MessageType.Error);
  135. }
  136. if (allAvailableSDKNames.Length != availableSystemSDKNames.Length
  137. || allAvailableSDKNames.Length != availableBoundariesSDKNames.Length
  138. || allAvailableSDKNames.Length != availableHeadsetSDKNames.Length
  139. || allAvailableSDKNames.Length != availableControllerSDKNames.Length)
  140. {
  141. EditorGUILayout.HelpBox("Only endpoints that are supported by the selected SDK are changed by Quick Select, the others are left untouched."
  142. + "\n\nSome of the available SDK implementations are only available for a subset of SDK endpoints. Quick Select"
  143. + " shows SDKs that provide an implementation for *any* of the different SDK endpoints in VRTK"
  144. + " (System, Boundaries, Headset, Controller).", MessageType.Info);
  145. }
  146. }
  147. using (new EditorGUILayout.VerticalScope("Box"))
  148. {
  149. VRTK_EditorUtilities.AddHeader("Object References", false);
  150. using (new EditorGUILayout.HorizontalScope())
  151. {
  152. EditorGUI.BeginChangeCheck();
  153. bool autoPopulate = EditorGUILayout.Toggle(VRTK_EditorUtilities.BuildGUIContent<VRTK_SDKSetup>("autoPopulateObjectReferences", "Auto Populate"),
  154. setup.autoPopulateObjectReferences,
  155. GUILayout.ExpandWidth(false));
  156. if (EditorGUI.EndChangeCheck())
  157. {
  158. serializedObject.FindProperty("autoPopulateObjectReferences").boolValue = autoPopulate;
  159. serializedObject.ApplyModifiedProperties();
  160. setup.PopulateObjectReferences(false);
  161. }
  162. const string populateNowDescription = "Populate Now";
  163. GUIContent populateNowGUIContent = new GUIContent(populateNowDescription, "Set the SDK object references to the objects of the selected SDKs.");
  164. if (GUILayout.Button(populateNowGUIContent, GUILayout.MaxHeight(GUI.skin.label.CalcSize(populateNowGUIContent).y)))
  165. {
  166. Undo.RecordObject(setup, populateNowDescription);
  167. setup.PopulateObjectReferences(true);
  168. }
  169. }
  170. if (setup.autoPopulateObjectReferences)
  171. {
  172. EditorGUILayout.HelpBox("The SDK Setup is configured to automatically populate object references so the following fields are disabled."
  173. + " Uncheck `Auto Populate` above to enable customization of the fields below.", MessageType.Info);
  174. }
  175. using (new EditorGUI.DisabledGroupScope(setup.autoPopulateObjectReferences))
  176. {
  177. using (new EditorGUILayout.VerticalScope("Box"))
  178. {
  179. VRTK_EditorUtilities.AddHeader("Actual Objects", false);
  180. EditorGUILayout.PropertyField(
  181. serializedObject.FindProperty("actualBoundaries"),
  182. VRTK_EditorUtilities.BuildGUIContent<VRTK_SDKSetup>("actualBoundaries", "Boundaries")
  183. );
  184. EditorGUILayout.PropertyField(
  185. serializedObject.FindProperty("actualHeadset"),
  186. VRTK_EditorUtilities.BuildGUIContent<VRTK_SDKSetup>("actualHeadset", "Headset")
  187. );
  188. EditorGUILayout.PropertyField(
  189. serializedObject.FindProperty("actualLeftController"),
  190. VRTK_EditorUtilities.BuildGUIContent<VRTK_SDKSetup>("actualLeftController", "Left Controller")
  191. );
  192. EditorGUILayout.PropertyField(
  193. serializedObject.FindProperty("actualRightController"),
  194. VRTK_EditorUtilities.BuildGUIContent<VRTK_SDKSetup>("actualRightController", "Right Controller")
  195. );
  196. }
  197. using (new EditorGUILayout.VerticalScope("Box"))
  198. {
  199. VRTK_EditorUtilities.AddHeader("Model Aliases", false);
  200. EditorGUILayout.PropertyField(
  201. serializedObject.FindProperty("modelAliasLeftController"),
  202. VRTK_EditorUtilities.BuildGUIContent<VRTK_SDKSetup>("modelAliasLeftController", "Left Controller")
  203. );
  204. EditorGUILayout.PropertyField(
  205. serializedObject.FindProperty("modelAliasRightController"),
  206. VRTK_EditorUtilities.BuildGUIContent<VRTK_SDKSetup>("modelAliasRightController", "Right Controller")
  207. );
  208. }
  209. }
  210. EditorGUILayout.HelpBox(
  211. "The game object this SDK Setup is attached to will be set inactive automatically to allow for SDK loading and switching.",
  212. MessageType.Info
  213. );
  214. IEnumerable<GameObject> referencedObjects = new[]
  215. {
  216. setup.actualBoundaries,
  217. setup.actualHeadset,
  218. setup.actualLeftController,
  219. setup.actualRightController,
  220. setup.modelAliasLeftController,
  221. setup.modelAliasRightController
  222. }.Where(referencedObject => referencedObject != null);
  223. if (referencedObjects.Any(referencedObject => !referencedObject.transform.IsChildOf(setup.transform)))
  224. {
  225. EditorGUILayout.HelpBox(
  226. "There is at least one referenced object that is neither a child of, deep child (child of a child) of nor attached to the game object this SDK Setup is attached to.",
  227. MessageType.Error
  228. );
  229. }
  230. }
  231. serializedObject.ApplyModifiedProperties();
  232. }
  233. #region Handle undo
  234. protected virtual void OnEnable()
  235. {
  236. Undo.undoRedoPerformed += UndoRedoPerformed;
  237. }
  238. protected virtual void OnDisable()
  239. {
  240. Undo.undoRedoPerformed -= UndoRedoPerformed;
  241. }
  242. private void UndoRedoPerformed()
  243. {
  244. UpdateDetailedSDKSelectionFoldOut();
  245. }
  246. #endregion
  247. /// <summary>
  248. /// Draws a popup menu and handles the selection for an SDK info.
  249. /// </summary>
  250. /// <typeparam name="BaseType">The SDK base type. Must be a subclass of <see cref="SDK_Base"/>.</typeparam>
  251. /// <param name="description">The description of the SDK base.</param>
  252. private void DrawAndHandleSDKSelection<BaseType>(string description, int indentLevelToRemove) where BaseType : SDK_Base
  253. {
  254. Type baseType = typeof(BaseType);
  255. Type sdkManagerType = typeof(VRTK_SDKManager);
  256. string baseName = baseType.Name.Remove(0, typeof(SDK_Base).Name.Length);
  257. ReadOnlyCollection<VRTK_SDKInfo> availableSDKInfos = (ReadOnlyCollection<VRTK_SDKInfo>)sdkManagerType
  258. .GetProperty(string.Format("Available{0}SDKInfos", baseName), BindingFlags.Public | BindingFlags.Static)
  259. .GetGetMethod()
  260. .Invoke(null, null);
  261. ReadOnlyCollection<VRTK_SDKInfo> installedSDKInfos = (ReadOnlyCollection<VRTK_SDKInfo>)sdkManagerType
  262. .GetProperty(string.Format("Installed{0}SDKInfos", baseName), BindingFlags.Public | BindingFlags.Static)
  263. .GetGetMethod()
  264. .Invoke(null, null);
  265. PropertyInfo sdkInfoPropertyInfo = typeof(VRTK_SDKSetup).GetProperty(string.Format("{0}SDKInfo", baseName.ToLowerInvariant()));
  266. VRTK_SDKSetup sdkSetup = (VRTK_SDKSetup)target;
  267. VRTK_SDKInfo selectedSDKInfo = (VRTK_SDKInfo)sdkInfoPropertyInfo.GetGetMethod().Invoke(sdkSetup, null);
  268. List<string> availableSDKNames = availableSDKInfos.Select(info => info.description.prettyName + (installedSDKInfos.Contains(info) ? "" : SDKNotInstalledDescription)).ToList();
  269. int selectedSDKIndex = availableSDKInfos.IndexOf(selectedSDKInfo);
  270. if (selectedSDKInfo.originalTypeNameWhenFallbackIsUsed != null)
  271. {
  272. availableSDKNames.Add(selectedSDKInfo.originalTypeNameWhenFallbackIsUsed + SDKNotFoundAnymoreDescription);
  273. selectedSDKIndex = availableSDKNames.Count - 1;
  274. }
  275. GUIContent[] availableSDKGUIContents = availableSDKNames.Select(availableSDKName => new GUIContent(availableSDKName)).ToArray();
  276. using (new EditorGUILayout.HorizontalScope())
  277. {
  278. EditorGUI.BeginChangeCheck();
  279. int newSelectedSDKIndex = EditorGUILayout.Popup(
  280. new GUIContent(baseName, description),
  281. selectedSDKIndex,
  282. availableSDKGUIContents
  283. );
  284. VRTK_SDKInfo newSelectedSDKInfo = selectedSDKInfo.originalTypeNameWhenFallbackIsUsed != null
  285. && newSelectedSDKIndex == availableSDKNames.Count - 1
  286. ? selectedSDKInfo
  287. : availableSDKInfos[newSelectedSDKIndex];
  288. if (EditorGUI.EndChangeCheck() && newSelectedSDKInfo != selectedSDKInfo)
  289. {
  290. Undo.RecordObject(sdkSetup, string.Format("{0} SDK Change", baseName));
  291. sdkInfoPropertyInfo.GetSetMethod().Invoke(sdkSetup, new object[] { newSelectedSDKInfo });
  292. }
  293. DrawVRDeviceNameLabel(selectedSDKInfo, baseName, indentLevelToRemove);
  294. }
  295. }
  296. private static void DrawVRDeviceNameLabel(VRTK_SDKInfo info, string sdkBaseName, int indentLevelToRemove)
  297. {
  298. float maxVRDeviceNameTextWidth = new[]
  299. {
  300. VRTK_SDKManager.AvailableSystemSDKInfos,
  301. VRTK_SDKManager.AvailableBoundariesSDKInfos,
  302. VRTK_SDKManager.AvailableHeadsetSDKInfos,
  303. VRTK_SDKManager.AvailableControllerSDKInfos
  304. }
  305. .SelectMany(infos => infos.Select(sdkInfo => sdkInfo.description.vrDeviceName))
  306. .Concat(XRSettings.supportedDevices)
  307. .Concat(new[] { "None" })
  308. .Distinct()
  309. .Select(deviceName => GUI.skin.label.CalcSize(new GUIContent(deviceName)).x)
  310. .Max();
  311. EditorGUI.indentLevel -= indentLevelToRemove;
  312. EditorGUILayout.LabelField(
  313. new GUIContent(info.description.vrDeviceName,
  314. string.Format("The VR Device used by the {0} {1} SDK.", info.description.prettyName, sdkBaseName)),
  315. new GUIStyle(EditorStyles.centeredGreyMiniLabel)
  316. {
  317. alignment = TextAnchor.MiddleRight
  318. },
  319. GUILayout.Width(maxVRDeviceNameTextWidth)
  320. );
  321. EditorGUI.indentLevel += indentLevelToRemove;
  322. }
  323. private bool AreAllSDKsTheSame()
  324. {
  325. VRTK_SDKSetup setup = (VRTK_SDKSetup)target;
  326. VRTK_SDKInfo[] infos =
  327. {
  328. setup.systemSDKInfo,
  329. setup.boundariesSDKInfo,
  330. setup.headsetSDKInfo,
  331. setup.controllerSDKInfo,
  332. };
  333. if (infos.Any(info => info.originalTypeNameWhenFallbackIsUsed != null))
  334. {
  335. return false;
  336. }
  337. return infos.Select(info => info.description.prettyName)
  338. .Distinct()
  339. .Count() == 1;
  340. }
  341. private void UpdateDetailedSDKSelectionFoldOut()
  342. {
  343. isDetailedSDKSelectionFoldOut = !AreAllSDKsTheSame();
  344. }
  345. private sealed class SDKSetupGameObjectsDisabler : AssetModificationProcessor
  346. {
  347. /*
  348. * Used to make sure the warning is actually seen in case the console is automatically cleared
  349. * because of 'Clear on Play' when playing the scene in the Editor.
  350. */
  351. private const string PreferencesKey = "VRTK.SDKSetupGameObjectsDisabler.InfoMessage";
  352. [InitializeOnLoadMethod]
  353. private static void ListenToPlayModeChanges()
  354. {
  355. #if UNITY_2017_2_OR_NEWER
  356. EditorApplication.playModeStateChanged += (PlayModeStateChange state) =>
  357. #else
  358. EditorApplication.playmodeStateChanged += () =>
  359. #endif
  360. {
  361. if (EditorApplication.isPlayingOrWillChangePlaymode && !EditorApplication.isPlaying)
  362. {
  363. FixOpenAndUnsavedScenes();
  364. }
  365. if (EditorApplication.isPlaying
  366. #if UNITY_5_6_OR_NEWER
  367. && SessionState.GetBool(PreferencesKey, false)
  368. #else
  369. && EditorPrefs.HasKey(PreferencesKey)
  370. #endif
  371. )
  372. {
  373. #if UNITY_5_6_OR_NEWER
  374. SessionState.EraseBool(
  375. #else
  376. EditorPrefs.DeleteKey(
  377. #endif
  378. PreferencesKey
  379. );
  380. }
  381. };
  382. }
  383. private static string[] OnWillSaveAssets(string[] paths)
  384. {
  385. FixOpenAndUnsavedScenes();
  386. return paths;
  387. }
  388. private static void FixOpenAndUnsavedScenes()
  389. {
  390. List<VRTK_SDKSetup> setups = Enumerable.Range(0, EditorSceneManager.loadedSceneCount)
  391. .SelectMany(sceneIndex => SceneManager.GetSceneAt(sceneIndex).GetRootGameObjects())
  392. .SelectMany(rootObject => rootObject.GetComponentsInChildren<VRTK_SDKManager>())
  393. .Select(manager => manager.setups.Where(setup => setup != null).ToArray())
  394. .Where(sdkSetups => sdkSetups.Length > 1)
  395. .SelectMany(sdkSetups => sdkSetups)
  396. .Where(setup => setup.gameObject.activeSelf)
  397. .ToList();
  398. if (setups.Count == 0)
  399. {
  400. return;
  401. }
  402. setups.ForEach(setup => setup.gameObject.SetActive(false));
  403. string infoMessage = string.Format(
  404. "The following game objects have been set inactive to allow for SDK loading and switching using the SDK Setups on them:\n{0}",
  405. string.Join(", ", setups.Select(setup => setup.name).ToArray()));
  406. if (EditorApplication.isPlayingOrWillChangePlaymode)
  407. {
  408. #if UNITY_5_6_OR_NEWER
  409. SessionState.SetString(
  410. #else
  411. EditorPrefs.SetString(
  412. #endif
  413. PreferencesKey,
  414. infoMessage
  415. );
  416. }
  417. else
  418. {
  419. VRTK_Logger.Info(infoMessage);
  420. }
  421. }
  422. }
  423. }
  424. }