270 lines
8.8 KiB
C#
270 lines
8.8 KiB
C#
|
using System.Collections.Generic;
|
||
|
using UnityEngine;
|
||
|
|
||
|
namespace UnityEditor.U2D.Aseprite.Common
|
||
|
{
|
||
|
internal class ModelPreviewer : System.IDisposable
|
||
|
{
|
||
|
const float k_TimeControlRectHeight = 20;
|
||
|
|
||
|
readonly PreviewRenderUtility m_RenderUtility;
|
||
|
|
||
|
bool m_Disposed = false;
|
||
|
Rect m_PreviewRect;
|
||
|
Bounds m_RenderableBounds;
|
||
|
Vector2Int m_ActorSize;
|
||
|
|
||
|
TimeControl m_TimeControl;
|
||
|
int m_Fps;
|
||
|
Animator m_Animator;
|
||
|
AnimationClip[] m_Clips;
|
||
|
AnimationClip m_SelectedClip;
|
||
|
List<float> m_FrameTimings;
|
||
|
int m_ClipIndex = 0;
|
||
|
SpriteRenderer[] m_Renderers;
|
||
|
|
||
|
Texture m_Texture;
|
||
|
GameObject m_PreviewObject;
|
||
|
|
||
|
GUIContent[] m_ClipNames;
|
||
|
int[] m_ClipIndices;
|
||
|
|
||
|
public ModelPreviewer(GameObject assetPrefab, AnimationClip[] clips)
|
||
|
{
|
||
|
m_RenderUtility = new PreviewRenderUtility();
|
||
|
m_RenderUtility.camera.fieldOfView = 30f;
|
||
|
|
||
|
m_PreviewObject = m_RenderUtility.InstantiatePrefabInScene(assetPrefab);
|
||
|
m_RenderUtility.AddManagedGameObject(m_PreviewObject);
|
||
|
|
||
|
m_Renderers = m_PreviewObject.GetComponentsInChildren<SpriteRenderer>();
|
||
|
m_RenderableBounds = GetRenderableBounds(m_Renderers);
|
||
|
|
||
|
if (clips != null && clips.Length > 0)
|
||
|
{
|
||
|
SetupAnimation(clips);
|
||
|
SelectClipFromIndex(m_ClipIndex);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SetupAnimation(AnimationClip[] clips)
|
||
|
{
|
||
|
m_TimeControl = new TimeControl();
|
||
|
m_Animator = m_PreviewObject.GetComponent<Animator>();
|
||
|
m_Clips = clips;
|
||
|
|
||
|
var clipInfos = m_Animator.GetCurrentAnimatorClipInfo(0);
|
||
|
|
||
|
var defaultClipName = string.Empty;
|
||
|
if (clipInfos.Length > 0)
|
||
|
defaultClipName = clipInfos[0].clip.name;
|
||
|
|
||
|
m_ClipNames = new GUIContent[m_Clips.Length];
|
||
|
m_ClipIndices = new int[m_Clips.Length];
|
||
|
for (var i = 0; i < m_ClipNames.Length; ++i)
|
||
|
{
|
||
|
m_ClipNames[i] = new GUIContent(m_Clips[i].name);
|
||
|
m_ClipIndices[i] = i;
|
||
|
|
||
|
// Set starting clip to default clip.
|
||
|
if (m_Clips[i].name == defaultClipName)
|
||
|
m_ClipIndex = i;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SelectClipFromIndex(int index)
|
||
|
{
|
||
|
m_SelectedClip = m_Clips[index];
|
||
|
m_Fps = Mathf.RoundToInt(m_SelectedClip.frameRate);
|
||
|
m_TimeControl.playbackSpeed = 1f / m_SelectedClip.length;
|
||
|
m_TimeControl.currentTime = 0f;
|
||
|
|
||
|
var timeSet = new HashSet<float>();
|
||
|
var curveBindings = AnimationUtility.GetObjectReferenceCurveBindings(m_SelectedClip);
|
||
|
|
||
|
for (var i = 0; i < curveBindings.Length; ++i)
|
||
|
{
|
||
|
var keyFrames = AnimationUtility.GetObjectReferenceCurve(m_SelectedClip, curveBindings[i]);
|
||
|
for (var m = 0; m < keyFrames.Length; ++m)
|
||
|
timeSet.Add(keyFrames[m].time);
|
||
|
}
|
||
|
|
||
|
m_FrameTimings = new List<float>(timeSet.Count);
|
||
|
foreach (var time in timeSet)
|
||
|
m_FrameTimings.Add(time);
|
||
|
m_FrameTimings.Sort();
|
||
|
// Remove the final frame time, as we add it on generation
|
||
|
m_FrameTimings.RemoveAt(m_FrameTimings.Count - 1);
|
||
|
}
|
||
|
|
||
|
public void DrawPreview(Rect r, GUIStyle background)
|
||
|
{
|
||
|
if (!ShaderUtil.hardwareSupportsRectRenderTexture)
|
||
|
return;
|
||
|
var isRepainting = (Event.current.type == EventType.Repaint);
|
||
|
|
||
|
if (isRepainting)
|
||
|
{
|
||
|
if (m_Texture != null)
|
||
|
Object.DestroyImmediate(m_Texture);
|
||
|
m_Texture = null;
|
||
|
m_PreviewRect = r;
|
||
|
|
||
|
m_PreviewObject.transform.position = Vector3.zero;
|
||
|
m_RenderUtility.BeginPreview(r, background);
|
||
|
DoRenderPreview();
|
||
|
m_Texture = m_RenderUtility.EndPreview();
|
||
|
|
||
|
m_TimeControl?.Update();
|
||
|
}
|
||
|
|
||
|
if (m_SelectedClip != null)
|
||
|
UpdateAnimation(isRepainting);
|
||
|
UpdateActorSize();
|
||
|
|
||
|
GUI.DrawTexture(r, m_Texture, ScaleMode.StretchToFill, false);
|
||
|
|
||
|
if (m_SelectedClip != null)
|
||
|
DrawTimeControlGUI(m_PreviewRect);
|
||
|
else
|
||
|
DrawInfoText(m_PreviewRect);
|
||
|
}
|
||
|
|
||
|
void UpdateAnimation(bool isRepainting)
|
||
|
{
|
||
|
if (!isRepainting || m_PreviewObject == null)
|
||
|
return;
|
||
|
|
||
|
m_TimeControl.loop = true;
|
||
|
|
||
|
m_Animator.Play(m_SelectedClip.name, 0, m_TimeControl.normalizedTime);
|
||
|
m_Animator.Update(m_TimeControl.deltaTime);
|
||
|
}
|
||
|
|
||
|
void UpdateActorSize()
|
||
|
{
|
||
|
if (m_Renderers == null || m_Renderers.Length == 0)
|
||
|
return;
|
||
|
|
||
|
var ppu = m_Renderers[0].sprite.pixelsPerUnit;
|
||
|
var bounds = GetRenderableBounds(m_Renderers);
|
||
|
|
||
|
m_ActorSize = new Vector2Int()
|
||
|
{
|
||
|
x = Mathf.RoundToInt(bounds.size.x * ppu),
|
||
|
y = Mathf.RoundToInt(bounds.size.y * ppu)
|
||
|
};
|
||
|
}
|
||
|
|
||
|
void DrawTimeControlGUI(Rect rect)
|
||
|
{
|
||
|
const float kSliderWidth = 150f;
|
||
|
const float kSpacing = 4f;
|
||
|
var timeControlRect = rect;
|
||
|
|
||
|
// background
|
||
|
GUI.Box(rect, GUIContent.none, EditorStyles.toolbar);
|
||
|
|
||
|
timeControlRect.height = k_TimeControlRectHeight;
|
||
|
timeControlRect.xMax -= kSliderWidth;
|
||
|
|
||
|
var sliderControlRect = rect;
|
||
|
sliderControlRect.height = k_TimeControlRectHeight;
|
||
|
sliderControlRect.yMin += 1;
|
||
|
sliderControlRect.yMax -= 1;
|
||
|
sliderControlRect.xMin = sliderControlRect.xMax - kSliderWidth + kSpacing;
|
||
|
|
||
|
m_TimeControl.DoTimeControl(timeControlRect);
|
||
|
|
||
|
EditorGUI.BeginChangeCheck();
|
||
|
m_ClipIndex = EditorGUI.IntPopup(sliderControlRect, m_ClipIndex, m_ClipNames, m_ClipIndices);
|
||
|
if (EditorGUI.EndChangeCheck())
|
||
|
{
|
||
|
SelectClipFromIndex(m_ClipIndex);
|
||
|
}
|
||
|
|
||
|
DrawInfoText(rect);
|
||
|
}
|
||
|
|
||
|
void DrawInfoText(Rect rect)
|
||
|
{
|
||
|
rect.y = rect.yMax - 24;
|
||
|
rect.height = 20;
|
||
|
|
||
|
var text = "";
|
||
|
if (m_TimeControl != null)
|
||
|
{
|
||
|
var currentTime = m_TimeControl.normalizedTime * m_SelectedClip.length;
|
||
|
var currentFrame = GetFrameFromTime(currentTime);
|
||
|
text += $"Frame {currentFrame} | ";
|
||
|
}
|
||
|
|
||
|
text += $"{m_ActorSize.x}x{m_ActorSize.y}";
|
||
|
|
||
|
EditorGUI.DropShadowLabel(rect, text);
|
||
|
}
|
||
|
|
||
|
int GetFrameFromTime(float currentTime)
|
||
|
{
|
||
|
var frame = 0;
|
||
|
for (var i = 0; i < m_FrameTimings.Count; ++i)
|
||
|
{
|
||
|
if (currentTime < m_FrameTimings[i])
|
||
|
break;
|
||
|
frame++;
|
||
|
}
|
||
|
|
||
|
// Remove one to get the frame number start from 0
|
||
|
return frame - 1;
|
||
|
}
|
||
|
|
||
|
void DoRenderPreview()
|
||
|
{
|
||
|
var num1 = Mathf.Max(m_RenderableBounds.extents.magnitude, 0.0001f);
|
||
|
var num2 = num1 * 3.8f;
|
||
|
var vector3 = m_RenderableBounds.center - Quaternion.identity * (Vector3.forward * num2);
|
||
|
m_RenderUtility.camera.transform.position = vector3;
|
||
|
m_RenderUtility.camera.nearClipPlane = num2 - num1 * 1.1f;
|
||
|
m_RenderUtility.camera.farClipPlane = num2 + num1 * 5.1f;
|
||
|
m_RenderUtility.lights[0].intensity = 0.7f;
|
||
|
m_RenderUtility.lights[1].intensity = 0.7f;
|
||
|
m_RenderUtility.ambientColor = new Color(0.1f, 0.1f, 0.1f, 0.0f);
|
||
|
|
||
|
m_RenderUtility.Render(true);
|
||
|
}
|
||
|
|
||
|
static Bounds GetRenderableBounds(SpriteRenderer[] renderers)
|
||
|
{
|
||
|
if (renderers.Length == 1)
|
||
|
{
|
||
|
var renderBound = renderers[0].bounds;
|
||
|
var localPos = renderers[0].transform.localPosition;
|
||
|
renderBound.center -= localPos;
|
||
|
return renderBound;
|
||
|
}
|
||
|
|
||
|
var bounds = new Bounds();
|
||
|
foreach (var rendererComponents in renderers)
|
||
|
{
|
||
|
var renderBound = rendererComponents.bounds;
|
||
|
if (bounds.extents == Vector3.zero)
|
||
|
bounds = renderBound;
|
||
|
else if (rendererComponents.enabled)
|
||
|
bounds.Encapsulate(renderBound);
|
||
|
}
|
||
|
return bounds;
|
||
|
}
|
||
|
|
||
|
public void Dispose()
|
||
|
{
|
||
|
if (m_Disposed)
|
||
|
return;
|
||
|
|
||
|
m_RenderUtility.Cleanup();
|
||
|
Object.DestroyImmediate(m_PreviewObject);
|
||
|
m_PreviewObject = null;
|
||
|
m_Disposed = true;
|
||
|
}
|
||
|
}
|
||
|
}
|