AnimancerPreviewRenderer.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
  2. #if UNITY_EDITOR && UNITY_IMGUI
  3. using System;
  4. using UnityEditor;
  5. using UnityEngine;
  6. using static Animancer.Editor.AnimancerGUI;
  7. using Object = UnityEngine.Object;
  8. namespace Animancer.Editor.Previews
  9. {
  10. /// <summary>[Editor-Only] Utility for rendering previews of animated objects.</summary>
  11. /// <remarks>Parts of this class are based on Unity's <see cref="MeshPreview"/>.</remarks>
  12. /// https://kybernetik.com.au/animancer/api/Animancer.Editor.Previews/AnimancerPreviewRenderer
  13. [Serializable]
  14. public class AnimancerPreviewRenderer :
  15. AnimancerPreviewObject.IEventHandler,
  16. IDisposable
  17. {
  18. /************************************************************************************************************************/
  19. #region Fields and Properties
  20. /************************************************************************************************************************/
  21. [NonSerialized] private PreviewRenderUtility _PreviewRenderUtility;
  22. [NonSerialized] private Light[] _PreviewLights;
  23. [SerializeField] private AnimancerPreviewObject _PreviewObject;
  24. [SerializeField] private Vector3 _OrthographicPosition = new(0.5f, 0.5f, -1);
  25. [SerializeField] private Vector2 _PreviewDirection = new(135, -30);
  26. [SerializeField] private Vector2 _LightDirection = new(-40, -40);
  27. [SerializeField] private Vector3 _PivotPositionOffset;
  28. [SerializeField] private float _ZoomFactor = 1f;
  29. /// <summary>The root object in the preview scene.</summary>
  30. public Transform PreviewSceneRoot { get; private set; }
  31. /// <summary>
  32. /// An instance of the <see cref="TransitionPreviewSettings.SceneEnvironment"/>.
  33. /// A child of the <see cref="PreviewSceneRoot"/>.
  34. /// </summary>
  35. public GameObject EnvironmentInstance { get; private set; }
  36. /************************************************************************************************************************/
  37. /// <summary>[<see cref="SerializeField"/>] The object being previewed.</summary>
  38. public AnimancerPreviewObject PreviewObject
  39. {
  40. get
  41. {
  42. InitializePreviewRenderUtility();
  43. return AnimancerPreviewObject.Initialize(ref _PreviewObject, this, PreviewSceneRoot);
  44. }
  45. }
  46. /************************************************************************************************************************/
  47. #endregion
  48. /************************************************************************************************************************/
  49. /// <summary>Cleans up this renderer.</summary>
  50. public void Dispose()
  51. {
  52. _PreviewObject?.Dispose();
  53. CleanupPreviewRenderUtility();
  54. }
  55. /************************************************************************************************************************/
  56. /// <summary>Calles when the <see cref="TransitionPreviewSettings.SceneEnvironment"/> is changed.</summary>
  57. private void OnEnvironmentPrefabChanged()
  58. {
  59. Object.DestroyImmediate(EnvironmentInstance);
  60. var prefab = TransitionPreviewSettings.SceneEnvironment;
  61. if (prefab != null)
  62. EnvironmentInstance = Object.Instantiate(prefab, PreviewSceneRoot);
  63. }
  64. /************************************************************************************************************************/
  65. /// <inheritdoc/>
  66. void AnimancerPreviewObject.IEventHandler.OnInstantiateObject()
  67. {
  68. _PivotPositionOffset = _PreviewObject.InstanceBounds.center;
  69. }
  70. /// <inheritdoc/>
  71. void AnimancerPreviewObject.IEventHandler.OnSetSelectedAnimator() { }
  72. /// <inheritdoc/>
  73. void AnimancerPreviewObject.IEventHandler.OnCreateGraph() { }
  74. /************************************************************************************************************************/
  75. #region GUI
  76. /************************************************************************************************************************/
  77. /// <summary>Draws the preview.</summary>
  78. public void DoGUI(Rect area, GUIStyle background)
  79. {
  80. if (!DrawIsRenderTextureSupported(area))
  81. return;
  82. var currentEvent = Event.current;
  83. HandleCameraControlEvent(area, currentEvent);
  84. if (currentEvent.type == EventType.Repaint)
  85. DrawPreview(area, background);
  86. }
  87. /************************************************************************************************************************/
  88. /// <summary>Shows a warning if the current device doesn't support render textures.</summary>
  89. private bool DrawIsRenderTextureSupported(Rect area)
  90. {
  91. if (ShaderUtil.hardwareSupportsRectRenderTexture)
  92. return true;
  93. EditorGUI.DropShadowLabel(
  94. area,
  95. "Device doesn't support Render Textures\nUnable to render previews");
  96. return false;
  97. }
  98. /************************************************************************************************************************/
  99. /// <summary>Initializes the <see cref="_PreviewRenderUtility"/>.</summary>
  100. private void InitializePreviewRenderUtility()
  101. {
  102. if (_PreviewRenderUtility != null)
  103. return;
  104. _PreviewRenderUtility = new();
  105. _PreviewRenderUtility.camera.fieldOfView = 30;
  106. _PreviewRenderUtility.camera.allowHDR = false;
  107. _PreviewRenderUtility.camera.allowMSAA = false;
  108. _PreviewRenderUtility.ambientColor = Grey(0.1f, 0);
  109. _PreviewLights = _PreviewRenderUtility.lights;
  110. _PreviewLights[0].intensity = 1.4f;
  111. _PreviewLights[0].transform.rotation = Quaternion.Euler(40, 40, 0);
  112. _PreviewLights[1].intensity = 1.4f;
  113. var root = AnimancerPreviewObject.CreateEmpty(nameof(AnimancerPreviewRenderer));
  114. _PreviewRenderUtility.AddSingleGO(root);
  115. PreviewSceneRoot = root.transform;
  116. OnEnvironmentPrefabChanged();
  117. }
  118. /************************************************************************************************************************/
  119. /// <summary>Cleans up the <see cref="_PreviewRenderUtility"/>.</summary>
  120. private void CleanupPreviewRenderUtility()
  121. {
  122. _PreviewRenderUtility?.Cleanup();
  123. _PreviewRenderUtility = null;
  124. _PreviewLights = null;
  125. }
  126. /************************************************************************************************************************/
  127. /// <summary>Updates and renders the preview.</summary>
  128. private void DrawPreview(Rect area, GUIStyle background)
  129. {
  130. InitializePreviewRenderUtility();
  131. _PreviewRenderUtility.BeginPreview(area, background);
  132. UpdatePreviewRenderUtility();
  133. _PreviewRenderUtility.Render(true, true);
  134. _PreviewRenderUtility.EndAndDrawPreview(area);
  135. }
  136. /************************************************************************************************************************/
  137. /// <summary>Updates the preview rendering details.</summary>
  138. private void UpdatePreviewRenderUtility()
  139. {
  140. var previewObject = PreviewObject;
  141. if (previewObject.InstanceObject == null)
  142. return;
  143. var rotation = Quaternion.Euler(_PreviewDirection.y, 0, 0) * Quaternion.Euler(0, _PreviewDirection.x, 0);
  144. previewObject.InstanceObject.rotation = rotation;
  145. var size = previewObject.InstanceBounds.extents.magnitude;
  146. var position = _ZoomFactor * -4f * size * Vector3.forward + _PivotPositionOffset;
  147. var camera = _PreviewRenderUtility.camera;
  148. camera.transform.SetPositionAndRotation(position, Quaternion.identity);
  149. camera.nearClipPlane = 0.0001f;
  150. camera.farClipPlane = 1000;
  151. camera.orthographic = false;
  152. var lights = _PreviewLights;
  153. lights[0].intensity = 1.1f;
  154. lights[0].transform.rotation = Quaternion.Euler(-_LightDirection.y, -_LightDirection.x, 0);
  155. lights[1].intensity = 1.1f;
  156. lights[1].transform.rotation = Quaternion.Euler(_LightDirection.y, _LightDirection.x, 0);
  157. _PreviewRenderUtility.ambientColor = Grey(0.1f, 0);
  158. }
  159. /************************************************************************************************************************/
  160. #endregion
  161. /************************************************************************************************************************/
  162. #region Camera Controls
  163. /************************************************************************************************************************/
  164. private static readonly int CameraControlsHint = "CameraControls".GetHashCode();
  165. /// <summary>Handles GUI events for controlling the preview camera.</summary>
  166. private void HandleCameraControlEvent(Rect area, Event currentEvent)
  167. {
  168. if (currentEvent.button == 1)
  169. {
  170. if (currentEvent.alt)
  171. _LightDirection = Drag2D(_LightDirection, area);// Could draw some lines to show the light directions.
  172. else
  173. _PreviewDirection = Drag2D(_PreviewDirection, area);
  174. }
  175. var control = new GUIControl(area, currentEvent, CameraControlsHint);
  176. switch (control.EventType)
  177. {
  178. case EventType.ScrollWheel:
  179. HandleScrollZoomEvent(area, currentEvent);
  180. break;
  181. case EventType.MouseDown:
  182. if (currentEvent.button <= 0 || currentEvent.button == 2)
  183. control.TryUseMouseDown();
  184. break;
  185. case EventType.MouseUp:
  186. control.TryUseMouseUp();
  187. break;
  188. case EventType.MouseDrag:
  189. if (control.TryUseHotControl())
  190. HandleDragPanEvent(area, currentEvent);
  191. break;
  192. case EventType.ValidateCommand:
  193. case EventType.ExecuteCommand:
  194. switch (currentEvent.commandName)
  195. {
  196. case Commands.FrameSelected:
  197. case Commands.FrameSelectedWithLock:
  198. FrameTarget();
  199. currentEvent.Use();
  200. break;
  201. }
  202. break;
  203. }
  204. }
  205. /************************************************************************************************************************/
  206. private static readonly int SliderHash = "Slider".GetHashCode();
  207. /// <summary>Handles drag input within a given `area`.</summary>
  208. /// <remarks>Copied from Unity's <see cref="PreviewGUI.Drag2D"/>.</remarks>
  209. public static Vector2 Drag2D(Vector2 scrollPosition, Rect area)
  210. {
  211. var control = new GUIControl(area, SliderHash);
  212. switch (control.EventType)
  213. {
  214. case EventType.MouseDown:
  215. if (control.TryUseMouseDown() && area.width > 50)
  216. EditorGUIUtility.SetWantsMouseJumping(1);
  217. break;
  218. case EventType.MouseUp:
  219. if (control.TryUseMouseUp())
  220. EditorGUIUtility.SetWantsMouseJumping(0);
  221. break;
  222. case EventType.MouseDrag:
  223. if (control.TryUseHotControl())
  224. {
  225. var multiplier = control.Event.shift ? 3 : 1;
  226. var size = Mathf.Min(area.width, area.height);
  227. scrollPosition -= control.Event.delta * multiplier / size * 140;
  228. }
  229. break;
  230. }
  231. return scrollPosition;
  232. }
  233. /************************************************************************************************************************/
  234. /// <summary>Handles a mouse scroll event to zoom the preview camera.</summary>
  235. private void HandleScrollZoomEvent(Rect area, Event currentEvent)
  236. {
  237. var delta = HandleUtility.niceMouseDeltaZoom * -0.025f;
  238. var zoom = _ZoomFactor * (1 + delta);
  239. zoom = Mathf.Clamp(zoom, 0.1f, 10);
  240. var vector = new Vector2(
  241. currentEvent.mousePosition.x / area.width,
  242. 1 - currentEvent.mousePosition.y / area.height);
  243. var origin = _PreviewRenderUtility.camera.ViewportToWorldPoint(vector);
  244. var direction = _OrthographicPosition - origin;
  245. var position = origin + direction * (zoom / _ZoomFactor);
  246. _PreviewRenderUtility.camera.transform.position = position;
  247. _ZoomFactor = zoom;
  248. currentEvent.Use();
  249. }
  250. /************************************************************************************************************************/
  251. /// <summary>Handles a mouse drag event to pan the preview camera.</summary>
  252. private void HandleDragPanEvent(Rect area, Event currentEvent)
  253. {
  254. var camera = _PreviewRenderUtility.camera;
  255. var direction = new Vector3(
  256. -currentEvent.delta.x * camera.pixelWidth / area.width,
  257. currentEvent.delta.y * camera.pixelHeight / area.height,
  258. 0);
  259. var position = camera.WorldToScreenPoint(_PivotPositionOffset);
  260. position += direction;
  261. direction = camera.ScreenToWorldPoint(position) - _PivotPositionOffset;
  262. _PivotPositionOffset += direction;
  263. }
  264. /************************************************************************************************************************/
  265. /// <summary>Frames the preview object in the middle of the camera.</summary>
  266. private void FrameTarget()
  267. {
  268. _ZoomFactor = 1f;
  269. _OrthographicPosition = new(0.5f, 0.5f, -1f);
  270. _PivotPositionOffset = default;
  271. }
  272. /************************************************************************************************************************/
  273. #endregion
  274. /************************************************************************************************************************/
  275. }
  276. }
  277. #endif