123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352 |
- // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
- #if UNITY_EDITOR && UNITY_IMGUI
- using System;
- using UnityEditor;
- using UnityEngine;
- using static Animancer.Editor.AnimancerGUI;
- using Object = UnityEngine.Object;
- namespace Animancer.Editor.Previews
- {
- /// <summary>[Editor-Only] Utility for rendering previews of animated objects.</summary>
- /// <remarks>Parts of this class are based on Unity's <see cref="MeshPreview"/>.</remarks>
- /// https://kybernetik.com.au/animancer/api/Animancer.Editor.Previews/AnimancerPreviewRenderer
- [Serializable]
- public class AnimancerPreviewRenderer :
- AnimancerPreviewObject.IEventHandler,
- IDisposable
- {
- /************************************************************************************************************************/
- #region Fields and Properties
- /************************************************************************************************************************/
- [NonSerialized] private PreviewRenderUtility _PreviewRenderUtility;
- [NonSerialized] private Light[] _PreviewLights;
- [SerializeField] private AnimancerPreviewObject _PreviewObject;
- [SerializeField] private Vector3 _OrthographicPosition = new(0.5f, 0.5f, -1);
- [SerializeField] private Vector2 _PreviewDirection = new(135, -30);
- [SerializeField] private Vector2 _LightDirection = new(-40, -40);
- [SerializeField] private Vector3 _PivotPositionOffset;
- [SerializeField] private float _ZoomFactor = 1f;
- /// <summary>The root object in the preview scene.</summary>
- public Transform PreviewSceneRoot { get; private set; }
- /// <summary>
- /// An instance of the <see cref="TransitionPreviewSettings.SceneEnvironment"/>.
- /// A child of the <see cref="PreviewSceneRoot"/>.
- /// </summary>
- public GameObject EnvironmentInstance { get; private set; }
- /************************************************************************************************************************/
- /// <summary>[<see cref="SerializeField"/>] The object being previewed.</summary>
- public AnimancerPreviewObject PreviewObject
- {
- get
- {
- InitializePreviewRenderUtility();
- return AnimancerPreviewObject.Initialize(ref _PreviewObject, this, PreviewSceneRoot);
- }
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- /// <summary>Cleans up this renderer.</summary>
- public void Dispose()
- {
- _PreviewObject?.Dispose();
- CleanupPreviewRenderUtility();
- }
- /************************************************************************************************************************/
- /// <summary>Calles when the <see cref="TransitionPreviewSettings.SceneEnvironment"/> is changed.</summary>
- private void OnEnvironmentPrefabChanged()
- {
- Object.DestroyImmediate(EnvironmentInstance);
- var prefab = TransitionPreviewSettings.SceneEnvironment;
- if (prefab != null)
- EnvironmentInstance = Object.Instantiate(prefab, PreviewSceneRoot);
- }
- /************************************************************************************************************************/
- /// <inheritdoc/>
- void AnimancerPreviewObject.IEventHandler.OnInstantiateObject()
- {
- _PivotPositionOffset = _PreviewObject.InstanceBounds.center;
- }
- /// <inheritdoc/>
- void AnimancerPreviewObject.IEventHandler.OnSetSelectedAnimator() { }
- /// <inheritdoc/>
- void AnimancerPreviewObject.IEventHandler.OnCreateGraph() { }
- /************************************************************************************************************************/
- #region GUI
- /************************************************************************************************************************/
- /// <summary>Draws the preview.</summary>
- public void DoGUI(Rect area, GUIStyle background)
- {
- if (!DrawIsRenderTextureSupported(area))
- return;
- var currentEvent = Event.current;
- HandleCameraControlEvent(area, currentEvent);
- if (currentEvent.type == EventType.Repaint)
- DrawPreview(area, background);
- }
- /************************************************************************************************************************/
- /// <summary>Shows a warning if the current device doesn't support render textures.</summary>
- private bool DrawIsRenderTextureSupported(Rect area)
- {
- if (ShaderUtil.hardwareSupportsRectRenderTexture)
- return true;
- EditorGUI.DropShadowLabel(
- area,
- "Device doesn't support Render Textures\nUnable to render previews");
- return false;
- }
- /************************************************************************************************************************/
- /// <summary>Initializes the <see cref="_PreviewRenderUtility"/>.</summary>
- private void InitializePreviewRenderUtility()
- {
- if (_PreviewRenderUtility != null)
- return;
- _PreviewRenderUtility = new();
- _PreviewRenderUtility.camera.fieldOfView = 30;
- _PreviewRenderUtility.camera.allowHDR = false;
- _PreviewRenderUtility.camera.allowMSAA = false;
- _PreviewRenderUtility.ambientColor = Grey(0.1f, 0);
- _PreviewLights = _PreviewRenderUtility.lights;
- _PreviewLights[0].intensity = 1.4f;
- _PreviewLights[0].transform.rotation = Quaternion.Euler(40, 40, 0);
- _PreviewLights[1].intensity = 1.4f;
- var root = AnimancerPreviewObject.CreateEmpty(nameof(AnimancerPreviewRenderer));
- _PreviewRenderUtility.AddSingleGO(root);
- PreviewSceneRoot = root.transform;
- OnEnvironmentPrefabChanged();
- }
- /************************************************************************************************************************/
- /// <summary>Cleans up the <see cref="_PreviewRenderUtility"/>.</summary>
- private void CleanupPreviewRenderUtility()
- {
- _PreviewRenderUtility?.Cleanup();
- _PreviewRenderUtility = null;
- _PreviewLights = null;
- }
- /************************************************************************************************************************/
- /// <summary>Updates and renders the preview.</summary>
- private void DrawPreview(Rect area, GUIStyle background)
- {
- InitializePreviewRenderUtility();
- _PreviewRenderUtility.BeginPreview(area, background);
- UpdatePreviewRenderUtility();
- _PreviewRenderUtility.Render(true, true);
- _PreviewRenderUtility.EndAndDrawPreview(area);
- }
- /************************************************************************************************************************/
- /// <summary>Updates the preview rendering details.</summary>
- private void UpdatePreviewRenderUtility()
- {
- var previewObject = PreviewObject;
- if (previewObject.InstanceObject == null)
- return;
- var rotation = Quaternion.Euler(_PreviewDirection.y, 0, 0) * Quaternion.Euler(0, _PreviewDirection.x, 0);
- previewObject.InstanceObject.rotation = rotation;
- var size = previewObject.InstanceBounds.extents.magnitude;
- var position = _ZoomFactor * -4f * size * Vector3.forward + _PivotPositionOffset;
- var camera = _PreviewRenderUtility.camera;
- camera.transform.SetPositionAndRotation(position, Quaternion.identity);
- camera.nearClipPlane = 0.0001f;
- camera.farClipPlane = 1000;
- camera.orthographic = false;
- var lights = _PreviewLights;
- lights[0].intensity = 1.1f;
- lights[0].transform.rotation = Quaternion.Euler(-_LightDirection.y, -_LightDirection.x, 0);
- lights[1].intensity = 1.1f;
- lights[1].transform.rotation = Quaternion.Euler(_LightDirection.y, _LightDirection.x, 0);
- _PreviewRenderUtility.ambientColor = Grey(0.1f, 0);
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Camera Controls
- /************************************************************************************************************************/
- private static readonly int CameraControlsHint = "CameraControls".GetHashCode();
- /// <summary>Handles GUI events for controlling the preview camera.</summary>
- private void HandleCameraControlEvent(Rect area, Event currentEvent)
- {
- if (currentEvent.button == 1)
- {
- if (currentEvent.alt)
- _LightDirection = Drag2D(_LightDirection, area);// Could draw some lines to show the light directions.
- else
- _PreviewDirection = Drag2D(_PreviewDirection, area);
- }
- var control = new GUIControl(area, currentEvent, CameraControlsHint);
- switch (control.EventType)
- {
- case EventType.ScrollWheel:
- HandleScrollZoomEvent(area, currentEvent);
- break;
- case EventType.MouseDown:
- if (currentEvent.button <= 0 || currentEvent.button == 2)
- control.TryUseMouseDown();
- break;
- case EventType.MouseUp:
- control.TryUseMouseUp();
- break;
- case EventType.MouseDrag:
- if (control.TryUseHotControl())
- HandleDragPanEvent(area, currentEvent);
- break;
- case EventType.ValidateCommand:
- case EventType.ExecuteCommand:
- switch (currentEvent.commandName)
- {
- case Commands.FrameSelected:
- case Commands.FrameSelectedWithLock:
- FrameTarget();
- currentEvent.Use();
- break;
- }
- break;
- }
- }
- /************************************************************************************************************************/
- private static readonly int SliderHash = "Slider".GetHashCode();
- /// <summary>Handles drag input within a given `area`.</summary>
- /// <remarks>Copied from Unity's <see cref="PreviewGUI.Drag2D"/>.</remarks>
- public static Vector2 Drag2D(Vector2 scrollPosition, Rect area)
- {
- var control = new GUIControl(area, SliderHash);
- switch (control.EventType)
- {
- case EventType.MouseDown:
- if (control.TryUseMouseDown() && area.width > 50)
- EditorGUIUtility.SetWantsMouseJumping(1);
- break;
- case EventType.MouseUp:
- if (control.TryUseMouseUp())
- EditorGUIUtility.SetWantsMouseJumping(0);
- break;
- case EventType.MouseDrag:
- if (control.TryUseHotControl())
- {
- var multiplier = control.Event.shift ? 3 : 1;
- var size = Mathf.Min(area.width, area.height);
- scrollPosition -= control.Event.delta * multiplier / size * 140;
- }
- break;
- }
- return scrollPosition;
- }
- /************************************************************************************************************************/
- /// <summary>Handles a mouse scroll event to zoom the preview camera.</summary>
- private void HandleScrollZoomEvent(Rect area, Event currentEvent)
- {
- var delta = HandleUtility.niceMouseDeltaZoom * -0.025f;
- var zoom = _ZoomFactor * (1 + delta);
- zoom = Mathf.Clamp(zoom, 0.1f, 10);
- var vector = new Vector2(
- currentEvent.mousePosition.x / area.width,
- 1 - currentEvent.mousePosition.y / area.height);
- var origin = _PreviewRenderUtility.camera.ViewportToWorldPoint(vector);
- var direction = _OrthographicPosition - origin;
- var position = origin + direction * (zoom / _ZoomFactor);
- _PreviewRenderUtility.camera.transform.position = position;
- _ZoomFactor = zoom;
- currentEvent.Use();
- }
- /************************************************************************************************************************/
- /// <summary>Handles a mouse drag event to pan the preview camera.</summary>
- private void HandleDragPanEvent(Rect area, Event currentEvent)
- {
- var camera = _PreviewRenderUtility.camera;
- var direction = new Vector3(
- -currentEvent.delta.x * camera.pixelWidth / area.width,
- currentEvent.delta.y * camera.pixelHeight / area.height,
- 0);
- var position = camera.WorldToScreenPoint(_PivotPositionOffset);
- position += direction;
- direction = camera.ScreenToWorldPoint(position) - _PivotPositionOffset;
- _PivotPositionOffset += direction;
- }
- /************************************************************************************************************************/
- /// <summary>Frames the preview object in the middle of the camera.</summary>
- private void FrameTarget()
- {
- _ZoomFactor = 1f;
- _OrthographicPosition = new(0.5f, 0.5f, -1f);
- _PivotPositionOffset = default;
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- }
- }
- #endif
|