using System;
using UnityEngine;
namespace UnityEditor.Rendering.LookDev
{
///
/// Different working views in LookDev
///
public enum ViewIndex
{
First,
Second
};
///
/// Same as plus a compound value
///
public enum ViewCompositionIndex
{
First = ViewIndex.First,
Second = ViewIndex.Second,
Composite
};
// /!\ WARNING: these value name are used as uss file too.
// if your rename here, rename in the uss too.
///
/// Different layout supported in LookDev
///
public enum Layout
{
FullFirstView,
FullSecondView,
HorizontalSplit,
VerticalSplit,
CustomSplit
}
///
/// Status of the side panel of the LookDev window
///
public enum SidePanel
{
None = -1,
Environment,
Debug,
}
///
/// The target views of the debug panel
///
public enum TargetDebugView
{
First,
Both,
Second
};
///
/// Class containing all data used by the LookDev Window to render
///
[System.Serializable]
public class Context : ScriptableObject, IDisposable
{
[SerializeField]
string m_EnvironmentLibraryGUID = ""; //Empty GUID
[SerializeField]
bool m_CameraSynced = true;
/// The currently used Environment
public EnvironmentLibrary environmentLibrary { get; private set; }
/// The currently used layout
[field: SerializeField]
public LayoutContext layout { get; private set; } = new LayoutContext();
///
/// State if both views camera movement are synced or not
///
public bool cameraSynced
{
get => m_CameraSynced;
set
{
if (m_CameraSynced ^ value)
{
if (value)
EditorApplication.update += SynchronizeCameraStates;
else
EditorApplication.update -= SynchronizeCameraStates;
m_CameraSynced = value;
}
}
}
[SerializeField]
ViewContext[] m_Views = new ViewContext[2]
{
new ViewContext(),
new ViewContext()
};
///
/// Get datas relative to a view
///
/// The view index to look at
/// Datas for the selected view
public ViewContext GetViewContent(ViewIndex index)
=> m_Views[(int)index];
internal void Init()
{
LoadEnvironmentLibraryFromGUID();
//recompute non serialized computes states
layout.gizmoState.Init();
if (cameraSynced)
EditorApplication.update += SynchronizeCameraStates;
}
/// Update the environment used.
///
/// The new to use.
/// Or the to use to build a new one.
/// Other types will raise an ArgumentException.
///
public void UpdateEnvironmentLibrary(EnvironmentLibrary library)
{
m_EnvironmentLibraryGUID = "";
environmentLibrary = null;
if (library == null || library.Equals(null))
return;
m_EnvironmentLibraryGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(library));
environmentLibrary = library;
}
void LoadEnvironmentLibraryFromGUID()
{
environmentLibrary = null;
GUID storedGUID;
GUID.TryParse(m_EnvironmentLibraryGUID, out storedGUID);
if (storedGUID.Empty())
return;
string path = AssetDatabase.GUIDToAssetPath(m_EnvironmentLibraryGUID);
environmentLibrary = AssetDatabase.LoadAssetAtPath(path);
}
///
/// Synchronize cameras from both view using data from the baseCameraState
///
/// The to be used as reference
public void SynchronizeCameraStates(ViewIndex baseCameraState)
{
switch (baseCameraState)
{
case ViewIndex.First:
m_Views[1].camera.SynchronizeFrom(m_Views[0].camera);
break;
case ViewIndex.Second:
m_Views[0].camera.SynchronizeFrom(m_Views[1].camera);
break;
default:
throw new System.ArgumentException("Unknow ViewIndex given in parameter.");
}
}
void SynchronizeCameraStates()
=> SynchronizeCameraStates(layout.lastFocusedView);
///
/// Change focused view.
/// Focused view is the base view to copy data when syncing views' cameras
///
/// The index of the view
public void SetFocusedCamera(ViewIndex index)
=> layout.lastFocusedView = index;
private bool disposedValue = false; // To detect redundant calls
void IDisposable.Dispose()
{
if (!disposedValue)
{
if (cameraSynced)
EditorApplication.update -= SynchronizeCameraStates;
disposedValue = true;
}
}
}
///
/// Data regarding the layout currently used in LookDev
///
[System.Serializable]
public class LayoutContext
{
/// The layout used
public Layout viewLayout;
/// The last focused view
public ViewIndex lastFocusedView = ViewIndex.First;
/// The state of the side panel
public SidePanel showedSidePanel;
/// The view to change when manipulating the Debug side panel
[NonSerialized]
public TargetDebugView debugPanelSource = TargetDebugView.Both;
[SerializeField]
internal ComparisonGizmoState gizmoState = new ComparisonGizmoState();
internal bool isSimpleView => viewLayout == Layout.FullFirstView || viewLayout == Layout.FullSecondView;
internal bool isMultiView => viewLayout == Layout.HorizontalSplit || viewLayout == Layout.VerticalSplit;
internal bool isCombinedView => viewLayout == Layout.CustomSplit;
}
///
/// Data container containing content of a view
///
[System.Serializable]
public class ViewContext
{
/// The position and rotation of the camera
[field: SerializeField]
public CameraState camera { get; private set; } = new CameraState();
/// The currently viewed debugState
public DebugContext debug { get; private set; } = new DebugContext();
//Environment asset, sub-asset (under a library) or cubemap
[SerializeField]
string environmentGUID = ""; //Empty GUID
///
/// Check if an Environment is registered for this view.
/// The result will be accurate even if the Environment have not been reloaded yet.
///
public bool hasEnvironment => !String.IsNullOrEmpty(environmentGUID);
/// The currently used Environment
public Environment environment { get; private set; }
[SerializeField]
string viewedObjectAssetGUID = ""; //Empty GUID
// Careful here: we want to keep it while reloading script.
// But from one unity editor to an other, ID are not kept.
// So, only use it when reloading from script update.
[SerializeField]
int viewedObjecHierarchytInstanceID;
///
/// Check if an Environment is registered for this view.
/// The result will be accurate even if the object have not been reloaded yet.
///
public bool hasViewedObject =>
!String.IsNullOrEmpty(viewedObjectAssetGUID)
|| viewedObjecHierarchytInstanceID != 0;
/// Reference to the object given for instantiation.
public GameObject viewedObjectReference { get; private set; }
///
/// The currently displayed instance of .
/// It will be instantiated when pushing changes to renderer.
/// See
///
public GameObject viewedInstanceInPreview { get; internal set; }
/// Update the environment used.
///
/// The new to use.
/// Or the to use to build a new one.
/// Other types will raise an ArgumentException.
///
public void UpdateEnvironment(UnityEngine.Object environmentOrCubemapAsset)
{
environmentGUID = "";
environment = null;
if (environmentOrCubemapAsset == null || environmentOrCubemapAsset.Equals(null))
return;
if (!(environmentOrCubemapAsset is Environment)
&& !(environmentOrCubemapAsset is Cubemap))
throw new System.ArgumentException("Only Environment or Cubemap accepted for environmentOrCubemapAsset parameter");
string GUID;
long localIDInFile;
AssetDatabase.TryGetGUIDAndLocalFileIdentifier(environmentOrCubemapAsset, out GUID, out localIDInFile);
environmentGUID = $"{GUID},{localIDInFile}";
if (environmentOrCubemapAsset is Environment)
environment = environmentOrCubemapAsset as Environment;
else //Cubemap
{
environment = new Environment();
environment.sky.cubemap = environmentOrCubemapAsset as Cubemap;
}
}
void LoadEnvironmentFromGUID()
{
environment = null;
GUID storedGUID;
string[] GUIDAndLocalIDInFile = environmentGUID.Split(new[] { ',' });
GUID.TryParse(GUIDAndLocalIDInFile[0], out storedGUID);
if (storedGUID.Empty())
return;
long localIDInFile = GUIDAndLocalIDInFile.Length < 2 ? 0L : long.Parse(GUIDAndLocalIDInFile[1]);
string path = AssetDatabase.GUIDToAssetPath(GUIDAndLocalIDInFile[0]);
Type savedType = AssetDatabase.GetMainAssetTypeAtPath(path);
if (savedType == typeof(EnvironmentLibrary))
{
object[] loaded = AssetDatabase.LoadAllAssetsAtPath(path);
for (int i = 0; i < loaded.Length; ++i)
{
string garbage;
long testedLocalIndex;
if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier((UnityEngine.Object)loaded[i], out garbage, out testedLocalIndex)
&& testedLocalIndex == localIDInFile)
{
environment = loaded[i] as Environment;
break;
}
}
}
else if (savedType == typeof(Environment))
environment = AssetDatabase.LoadAssetAtPath(path);
else if (savedType == typeof(Cubemap))
{
Cubemap cubemap = AssetDatabase.LoadAssetAtPath(path);
environment = new Environment();
environment.sky.cubemap = cubemap;
}
}
/// Update the object reference used for instantiation.
/// The new reference.
public void UpdateViewedObject(GameObject viewedObject)
{
viewedObjectAssetGUID = "";
viewedObjecHierarchytInstanceID = 0;
viewedObjectReference = null;
if (viewedObject == null || viewedObject.Equals(null))
return;
bool fromHierarchy = viewedObject.scene.IsValid();
if (fromHierarchy)
viewedObjecHierarchytInstanceID = viewedObject.GetInstanceID();
else
viewedObjectAssetGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(viewedObject));
viewedObjectReference = viewedObject;
}
//WARNING: only for script reloading
void LoadViewedObject()
{
viewedObjectReference = null;
GUID storedGUID;
GUID.TryParse(viewedObjectAssetGUID, out storedGUID);
if (!storedGUID.Empty())
{
string path = AssetDatabase.GUIDToAssetPath(viewedObjectAssetGUID);
viewedObjectReference = AssetDatabase.LoadAssetAtPath(path);
}
else if (viewedObjecHierarchytInstanceID != 0)
{
viewedObjectReference = EditorUtility.InstanceIDToObject(viewedObjecHierarchytInstanceID) as GameObject;
}
}
internal void LoadAll(bool reloadWithTemporaryID)
{
if (!reloadWithTemporaryID)
CleanTemporaryObjectIndexes();
LoadEnvironmentFromGUID();
LoadViewedObject();
}
internal void CleanTemporaryObjectIndexes()
=> viewedObjecHierarchytInstanceID = 0;
/// Reset the camera state to default values
public void ResetCameraState()
=> camera.Reset();
}
///
/// Class that will contain debug value used.
///
public class DebugContext
{
/// Display shadows in view.
public bool shadow = true;
/// Debug mode displayed. -1 means none.Display the debug grey balls
//public bool greyBalls;
//[SerializeField]
//string colorChartGUID = ""; //Empty GUID
///// The currently used color chart
//public Texture2D colorChart { get; private set; }
}
}