A game about forced loneliness, made by TACStudios
1using System; 2using UnityEngine; 3using System.Collections.Generic; 4using System.Collections; 5 6namespace UnityEditor.Rendering.LookDev 7{ 8 /// <summary> 9 /// Different working views in LookDev 10 /// </summary> 11 public enum ViewIndex 12 { 13 /// <summary>First view</summary> 14 First, 15 /// <summary>Second view</summary> 16 Second 17 }; 18 19 /// <summary> 20 /// Same as <see cref="ViewIndex"/> plus a compound value 21 /// </summary> 22 public enum ViewCompositionIndex 23 { 24 /// <summary>First view</summary> 25 First = ViewIndex.First, 26 /// <summary>Second view</summary> 27 Second = ViewIndex.Second, 28 /// <summary>Composite view (Several view on screen)</summary> 29 Composite 30 }; 31 32 // /!\ WARNING: these value name are used as uss file too. 33 // if your rename here, rename in the uss too. 34 /// <summary> 35 /// Different layout supported in LookDev 36 /// </summary> 37 public enum Layout 38 { 39 /// <summary>First view display fully</summary> 40 FullFirstView, 41 /// <summary>Second view display fully</summary> 42 FullSecondView, 43 /// <summary>First and second views displayed splitted horizontally</summary> 44 HorizontalSplit, 45 /// <summary>First and second views displayed splitted vertically</summary> 46 VerticalSplit, 47 /// <summary>First and second views displayed with stacking and orientation customizable split</summary> 48 CustomSplit 49 } 50 51 /// <summary> 52 /// Status of the side panel of the LookDev window 53 /// </summary> 54 public enum SidePanel 55 { 56 /// <summary>No side panel</summary> 57 None = -1, 58 /// <summary>Environment side panel</summary> 59 Environment, 60 /// <summary>Debug side panel</summary> 61 Debug, 62 } 63 64 /// <summary> 65 /// The target views of the debug panel 66 /// </summary> 67 public enum TargetDebugView 68 { 69 /// <summary>First Debug view</summary> 70 First, 71 /// <summary>Both Debug view</summary> 72 Both, 73 /// <summary>Second Debug view</summary> 74 Second 75 }; 76 77 /// <summary> 78 /// Class containing all data used by the LookDev Window to render 79 /// </summary> 80 [System.Serializable] 81 public class Context : ScriptableObject, IDisposable 82 { 83 [SerializeField] 84 string m_EnvironmentLibraryGUID = ""; //Empty GUID 85 86 [SerializeField] 87 bool m_CameraSynced = true; 88 89 EnvironmentLibrary m_EnvironmentLibrary; 90 91 /// <summary>The currently used Environment</summary> 92 public EnvironmentLibrary environmentLibrary 93 { 94 get 95 { 96 //check if asset deleted by user 97 if (m_EnvironmentLibrary != null && AssetDatabase.Contains(m_EnvironmentLibrary)) 98 return m_EnvironmentLibrary; 99 100 if (!String.IsNullOrEmpty(m_EnvironmentLibraryGUID)) 101 { 102 //user deleted the EnvironmentLibrary asset 103 m_EnvironmentLibraryGUID = ""; //Empty GUID 104 LookDev.currentEnvironmentDisplayer.Repaint(); 105 } 106 return null; 107 } 108 private set => m_EnvironmentLibrary = value; 109 } 110 111 /// <summary>The currently used layout</summary> 112 [field: SerializeField] 113 public LayoutContext layout { get; private set; } = new LayoutContext(); 114 115 /// <summary> 116 /// State if both views camera movement are synced or not 117 /// </summary> 118 public bool cameraSynced 119 { 120 get => m_CameraSynced; 121 set 122 { 123 if (m_CameraSynced ^ value) 124 { 125 if (value) 126 EditorApplication.update += SynchronizeCameraStates; 127 else 128 EditorApplication.update -= SynchronizeCameraStates; 129 m_CameraSynced = value; 130 } 131 } 132 } 133 134 [SerializeField] 135 ViewContext[] m_Views = new ViewContext[2] 136 { 137 new ViewContext(), 138 new ViewContext() 139 }; 140 141 /// <summary> 142 /// Helper class to iterate on views 143 /// </summary> 144 public struct ViewIterator : IEnumerable<ViewContext> 145 { 146 ViewContext[] m_Views; 147 internal ViewIterator(ViewContext[] views) 148 => m_Views = views; 149 150 /// <summary> 151 /// Helper function to enumerates on ViewContexts 152 /// </summary> 153 /// <returns>Enumerator on ViewContext</returns> 154 IEnumerator IEnumerable.GetEnumerator() 155 => m_Views.GetEnumerator(); 156 157 /// <summary> 158 /// Helper function to enumerates on ViewContexts 159 /// </summary> 160 /// <returns>Enumerator on ViewContext</returns> 161 IEnumerator<ViewContext> IEnumerable<ViewContext>.GetEnumerator() 162 => ((IEnumerable<ViewContext>)m_Views).GetEnumerator(); 163 } 164 165 /// <summary> 166 /// Helper function to get ViewIterator on ViewContexts 167 /// </summary> 168 public ViewIterator viewContexts 169 => new ViewIterator(m_Views); 170 171 /// <summary> 172 /// Get datas relative to a view 173 /// </summary> 174 /// <param name="index">The view index to look at</param> 175 /// <returns>Datas for the selected view</returns> 176 public ViewContext GetViewContent(ViewIndex index) 177 => m_Views[(int)index]; 178 179 internal void Init() 180 { 181 LoadEnvironmentLibraryFromGUID(); 182 183 //recompute non serialized computes states 184 layout.gizmoState.Init(); 185 186 if (cameraSynced) 187 EditorApplication.update += SynchronizeCameraStates; 188 } 189 190 /// <summary>Update the environment library used.</summary> 191 /// <param name="library">The new EnvironmentLibrary</param> 192 public void UpdateEnvironmentLibrary(EnvironmentLibrary library) 193 { 194 m_EnvironmentLibraryGUID = ""; 195 environmentLibrary = null; 196 if (library == null || library.Equals(null)) 197 return; 198 199 m_EnvironmentLibraryGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(library)); 200 environmentLibrary = library; 201 } 202 203 void LoadEnvironmentLibraryFromGUID() 204 { 205 environmentLibrary = null; 206 207 GUID storedGUID; 208 GUID.TryParse(m_EnvironmentLibraryGUID, out storedGUID); 209 if (storedGUID.Empty()) 210 return; 211 212 string path = AssetDatabase.GUIDToAssetPath(m_EnvironmentLibraryGUID); 213 environmentLibrary = AssetDatabase.LoadAssetAtPath<EnvironmentLibrary>(path); 214 } 215 216 /// <summary> 217 /// Synchronize cameras from both view using data from the baseCameraState 218 /// </summary> 219 /// <param name="baseCameraState">The <see cref="ViewIndex"/> to be used as reference</param> 220 public void SynchronizeCameraStates(ViewIndex baseCameraState) 221 { 222 switch (baseCameraState) 223 { 224 case ViewIndex.First: 225 m_Views[1].camera.SynchronizeFrom(m_Views[0].camera); 226 break; 227 case ViewIndex.Second: 228 m_Views[0].camera.SynchronizeFrom(m_Views[1].camera); 229 break; 230 default: 231 throw new System.ArgumentException("Unknow ViewIndex given in parameter."); 232 } 233 } 234 235 void SynchronizeCameraStates() 236 => SynchronizeCameraStates(layout.lastFocusedView); 237 238 /// <summary> 239 /// Change focused view. 240 /// Focused view is the base view to copy data when syncing views' cameras 241 /// </summary> 242 /// <param name="index">The index of the view</param> 243 public void SetFocusedCamera(ViewIndex index) 244 => layout.lastFocusedView = index; 245 246 247 private bool disposedValue = false; // To detect redundant calls 248 /// <summary>Disposable behaviour</summary> 249 void IDisposable.Dispose() 250 { 251 if (!disposedValue) 252 { 253 if (cameraSynced) 254 EditorApplication.update -= SynchronizeCameraStates; 255 256 disposedValue = true; 257 } 258 } 259 260 internal bool HasLibraryAssetChanged(EnvironmentLibrary environmentLibrary) 261 { 262 if (environmentLibrary == null) 263 return !String.IsNullOrEmpty(m_EnvironmentLibraryGUID); 264 265 return m_EnvironmentLibraryGUID != AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(environmentLibrary)); 266 } 267 268 internal void FullReimportEnvironmentLibrary() 269 { 270 if (environmentLibrary == null) 271 return; 272 273 // refresh AssetDatabase in case of undo/redo creating/destructing environment subasset 274 string libraryPath = AssetDatabase.GetAssetPath(environmentLibrary); 275 AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(environmentLibrary), ImportAssetOptions.DontDownloadFromCacheServer | ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.ForceUpdate | ImportAssetOptions.ImportRecursive); 276 UpdateEnvironmentLibrary(AssetDatabase.LoadAssetAtPath<EnvironmentLibrary>(libraryPath)); 277 EditorUtility.SetDirty(environmentLibrary); 278 } 279 } 280 281 /// <summary> 282 /// Data regarding the layout currently used in LookDev 283 /// </summary> 284 [System.Serializable] 285 public class LayoutContext 286 { 287 /// <summary>The layout used</summary> 288 public Layout viewLayout; 289 /// <summary>The last focused view</summary> 290 public ViewIndex lastFocusedView = ViewIndex.First; 291 /// <summary>The state of the side panel</summary> 292 public SidePanel showedSidePanel; 293 /// <summary>The view to change when manipulating the Debug side panel</summary> 294 [NonSerialized] 295 public TargetDebugView debugPanelSource = TargetDebugView.Both; 296 297 [SerializeField] 298 internal ComparisonGizmoState gizmoState = new ComparisonGizmoState(); 299 300 internal bool isSimpleView => viewLayout == Layout.FullFirstView || viewLayout == Layout.FullSecondView; 301 internal bool isMultiView => viewLayout == Layout.HorizontalSplit || viewLayout == Layout.VerticalSplit; 302 internal bool isCombinedView => viewLayout == Layout.CustomSplit; 303 } 304 305 /// <summary> 306 /// Data container containing content of a view 307 /// </summary> 308 [System.Serializable] 309 public class ViewContext 310 { 311 /// <summary>The position and rotation of the camera</summary> 312 [field: SerializeField] 313 public CameraState camera { get; private set; } = new CameraState(); 314 315 /// <summary>The currently viewed debugState</summary> 316 public DebugContext debug { get; private set; } = new DebugContext(); 317 318 //Environment asset, sub-asset (under a library) or cubemap 319 [SerializeField] 320 string m_EnvironmentGUID = ""; //Empty GUID 321 322 /// <summary> 323 /// Check if an Environment is registered for this view. 324 /// The result will be accurate even if the Environment have not been reloaded yet. 325 /// </summary> 326 public bool hasEnvironment => !String.IsNullOrEmpty(m_EnvironmentGUID); 327 328 /// <summary>The currently used Environment</summary> 329 public Environment environment { get; private set; } 330 331 [SerializeField] 332 string viewedObjectAssetGUID = ""; //Empty GUID 333 334 // Careful here: we want to keep it while reloading script. 335 // But from one unity editor to an other, ID are not kept. 336 // So, only use it when reloading from script update. 337 [SerializeField] 338 int viewedObjecHierarchytInstanceID; 339 340 /// <summary> 341 /// Check if an Environment is registered for this view. 342 /// The result will be accurate even if the object have not been reloaded yet. 343 /// </summary> 344 public bool hasViewedObject => 345 !String.IsNullOrEmpty(viewedObjectAssetGUID) 346 || viewedObjecHierarchytInstanceID != 0; 347 348 /// <summary>Reference to the object given for instantiation.</summary> 349 public GameObject viewedObjectReference { get; private set; } 350 351 /// <summary> 352 /// The currently displayed instance of <see cref="viewedObjectReference"/>. 353 /// It will be instantiated when pushing changes to renderer. 354 /// See <see cref="LookDev.SaveContextChangeAndApply(ViewIndex)"/> 355 /// </summary> 356 public GameObject viewedInstanceInPreview { get; internal set; } 357 358 /// <summary>Update the environment used.</summary> 359 /// <param name="environmentOrCubemapAsset"> 360 /// The new <see cref="Environment"/> to use. 361 /// Or the <see cref="Cubemap"/> to use to build a new one. 362 /// Other types will raise an ArgumentException. 363 /// </param> 364 public void UpdateEnvironment(UnityEngine.Object environmentOrCubemapAsset) 365 { 366 m_EnvironmentGUID = ""; 367 environment = null; 368 if (environmentOrCubemapAsset == null || environmentOrCubemapAsset.Equals(null)) 369 return; 370 371 if (!(environmentOrCubemapAsset is Environment) 372 && !(environmentOrCubemapAsset is Cubemap)) 373 throw new System.ArgumentException("Only Environment or Cubemap accepted for environmentOrCubemapAsset parameter"); 374 375 string GUID; 376 long localIDInFile; 377 AssetDatabase.TryGetGUIDAndLocalFileIdentifier(environmentOrCubemapAsset, out GUID, out localIDInFile); 378 m_EnvironmentGUID = $"{GUID},{localIDInFile}"; 379 380 if (environmentOrCubemapAsset is Environment) 381 environment = environmentOrCubemapAsset as Environment; 382 else //Cubemap 383 environment = Environment.GetTemporaryEnvironmentForCubemap(environmentOrCubemapAsset as Cubemap); 384 } 385 386 void LoadEnvironmentFromGUID() 387 { 388 environment = null; 389 390 GUID storedGUID; 391 string[] GUIDAndLocalIDInFile = m_EnvironmentGUID.Split(new[] { ',' }); 392 GUID.TryParse(GUIDAndLocalIDInFile[0], out storedGUID); 393 if (storedGUID.Empty()) 394 return; 395 long localIDInFile = GUIDAndLocalIDInFile.Length < 2 ? 0L : long.Parse(GUIDAndLocalIDInFile[1]); 396 397 string path = AssetDatabase.GUIDToAssetPath(GUIDAndLocalIDInFile[0]); 398 399 Type savedType = AssetDatabase.GetMainAssetTypeAtPath(path); 400 if (savedType == typeof(EnvironmentLibrary)) 401 { 402 object[] loaded = AssetDatabase.LoadAllAssetsAtPath(path); 403 for (int i = 0; i < loaded.Length; ++i) 404 { 405 string garbage; 406 long testedLocalIndex; 407 if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier((UnityEngine.Object)loaded[i], out garbage, out testedLocalIndex) 408 && testedLocalIndex == localIDInFile) 409 { 410 environment = loaded[i] as Environment; 411 break; 412 } 413 } 414 } 415 else if (savedType == typeof(Environment)) 416 environment = AssetDatabase.LoadAssetAtPath<Environment>(path); 417 else if (savedType == typeof(Cubemap)) 418 { 419 Cubemap cubemap = AssetDatabase.LoadAssetAtPath<Cubemap>(path); 420 environment = Environment.GetTemporaryEnvironmentForCubemap(cubemap); 421 } 422 } 423 424 /// <summary>Update the object reference used for instantiation.</summary> 425 /// <param name="viewedObject">The new reference.</param> 426 public void UpdateViewedObject(GameObject viewedObject) 427 { 428 viewedObjectAssetGUID = ""; 429 viewedObjecHierarchytInstanceID = 0; 430 viewedObjectReference = null; 431 if (viewedObject == null || viewedObject.Equals(null)) 432 return; 433 434 bool fromHierarchy = viewedObject.scene.IsValid(); 435 if (fromHierarchy) 436 viewedObjecHierarchytInstanceID = viewedObject.GetInstanceID(); 437 else 438 viewedObjectAssetGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(viewedObject)); 439 viewedObjectReference = viewedObject; 440 } 441 442 //WARNING: only for script reloading 443 void LoadViewedObject() 444 { 445 viewedObjectReference = null; 446 447 GUID storedGUID; 448 GUID.TryParse(viewedObjectAssetGUID, out storedGUID); 449 if (!storedGUID.Empty()) 450 { 451 string path = AssetDatabase.GUIDToAssetPath(viewedObjectAssetGUID); 452 viewedObjectReference = AssetDatabase.LoadAssetAtPath<GameObject>(path); 453 } 454 else if (viewedObjecHierarchytInstanceID != 0) 455 { 456 viewedObjectReference = EditorUtility.InstanceIDToObject(viewedObjecHierarchytInstanceID) as GameObject; 457 } 458 } 459 460 internal void LoadAll(bool reloadWithTemporaryID) 461 { 462 if (!reloadWithTemporaryID) 463 CleanTemporaryObjectIndexes(); 464 LoadEnvironmentFromGUID(); 465 LoadViewedObject(); 466 } 467 468 internal void CleanTemporaryObjectIndexes() 469 => viewedObjecHierarchytInstanceID = 0; 470 471 /// <summary>Reset the camera state to default values</summary> 472 public void ResetCameraState() 473 => camera.Reset(); 474 } 475 476 477 /// <summary> 478 /// Class that will contain debug value used. 479 /// </summary> 480 public class DebugContext 481 { 482 /// <summary>Display shadows in view.</summary> 483 public bool shadow = true; 484 485 /// <summary>Debug mode displayed. -1 means none.</summary> 486 public int viewMode = -1; 487 488 ///// <summary>Display the debug grey balls</summary> 489 //public bool greyBalls; 490 491 //[SerializeField] 492 //string colorChartGUID = ""; //Empty GUID 493 494 ///// <summary>The currently used color chart</summary> 495 //public Texture2D colorChart { get; private set; } 496 } 497}