Skip to content

Instantly share code, notes, and snippets.

@f-space
Created July 11, 2016 13:40
Show Gist options
  • Save f-space/074e0c116018aa8eccf5484c44e1de4b to your computer and use it in GitHub Desktop.
Save f-space/074e0c116018aa8eccf5484c44e1de4b to your computer and use it in GitHub Desktop.
頂点変換アニメーションのUnityエディタ拡張
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;
using UnityObject = UnityEngine.Object;
public static class VertexTransformation
{
#region Constants
private const string MenuPath = "Vertex Transformation/Enabled";
#endregion
#region Fields
private static bool enabled;
private static AnimationPlayer player = new AnimationPlayer() { Speed = 1.0f };
private static GUIStyle textStyle, labelStyle;
#endregion
#region Properties
private static bool Playable
{
get { return (enabled && EditorApplication.isPlaying && EditorApplication.isPaused); }
}
private static Camera TargetCamera
{
get { return Camera.main; }
}
private static bool FlipViewportY
{
get { return false; }
}
#endregion
#region Events
private static event Action<bool> AnimationStateChanged;
#endregion
#region Methods
[MenuItem(MenuPath)]
public static void Toggle()
{
SetEnabled(!enabled);
}
[MenuItem(MenuPath, true)]
public static bool CanToggle()
{
return EditorApplication.isPlayingOrWillChangePlaymode;
}
[InitializeOnLoadMethod]
private static void Initialize()
{
EditorApplication.playmodeStateChanged += OnRelatedStateChanged;
EditorApplication.update += Update;
SetupGUIStyles();
}
private static void SetEnabled(bool value)
{
if (enabled != value)
{
enabled = value;
Menu.SetChecked(MenuPath, value);
OnRelatedStateChanged();
}
}
private static void OnRelatedStateChanged()
{
if (player.Playing)
{
if (!Playable)
{
player.Stop();
SceneView.onSceneGUIDelegate -= OnSceneView;
DetachComponentFromCameras<CameraEventHook>();
if (AnimationStateChanged != null) AnimationStateChanged.Invoke(false);
}
}
else
{
if (Playable)
{
AttachComponentToCameras<CameraEventHook>();
SceneView.onSceneGUIDelegate += OnSceneView;
player.Play();
if (AnimationStateChanged != null) AnimationStateChanged.Invoke(true);
}
}
}
private static void Update()
{
if (player.Playing)
{
player.Update();
}
}
private static void AttachComponentToCameras<T>() where T : Component
{
if (TargetCamera) TargetCamera.gameObject.AddComponent<T>();
foreach (Camera camera in SceneView.GetAllSceneCameras())
{
camera.gameObject.AddComponent<T>();
}
}
private static void DetachComponentFromCameras<T>() where T : Component
{
if (TargetCamera) UnityObject.DestroyImmediate(TargetCamera.gameObject.GetComponent<T>());
foreach (Camera camera in SceneView.GetAllSceneCameras())
{
UnityObject.DestroyImmediate(camera.gameObject.GetComponent<T>());
}
}
private static void SetupGUIStyles()
{
textStyle = new GUIStyle();
textStyle.fontSize = 24;
textStyle.normal.textColor = Color.red;
labelStyle = new GUIStyle();
labelStyle.normal.textColor = Color.white;
}
private static void OnSceneView(SceneView sceneView)
{
const float offset = 10.0f;
Rect area = sceneView.camera.pixelRect;
area.xMin += offset;
area.xMax -= offset;
area.yMin += offset;
area.yMax -= offset;
Handles.BeginGUI();
GUILayout.BeginArea(area);
GUILayout.BeginVertical();
GUILayout.Label(player.Description, textStyle);
GUILayout.FlexibleSpace();
GUILayout.BeginHorizontal();
GUILayout.Label("Speed", labelStyle, GUILayout.Width(50.0f));
player.Speed = Mathf.Pow(10.0f, GUILayout.HorizontalSlider(Mathf.Log10(player.Speed), -1.0f, 1.0f, GUILayout.MaxWidth(200.0f)));
if (GUILayout.Button("Stop", GUILayout.Width(50.0f))) player.Speed = 0.0f;
GUILayout.EndHorizontal();
GUILayout.EndVertical();
GUILayout.EndArea();
Handles.EndGUI();
}
#endregion
#region AnimationPlayer
private class AnimationPlayer
{
#region Fields
private float speed;
private int phase;
private float time;
private float lastUpdateTime;
private bool playing;
private string description;
private readonly List<AnimatorBase> animators = new List<AnimatorBase>();
private readonly List<int> offsets = new List<int>();
#endregion
#region Properties
public float Speed
{
get { return speed; }
set { speed = value; }
}
public bool Playing { get { return playing; } }
public string Description { get { return description; } }
#endregion
#region Methods
public void Play()
{
Renderer[] renderers = CollectTargetRenderers();
Camera camera = GetTargetCamera();
SetupTransformationRenderers(renderers);
SetupTransformationCamera(camera);
SetupTransformAnimators(renderers);
SetupCameraAnimators(camera);
SetupIdleAnimator();
PrepareOffsetTable();
InitializeAnimationState();
lastUpdateTime = Time.realtimeSinceStartup;
playing = true;
}
public void Stop()
{
DeleteAnimators();
playing = false;
}
public void Update()
{
if (!playing) return;
UpdatePhaseAndTime(Time.realtimeSinceStartup);
Animate();
MarkAnimatorsDirty();
}
private static Renderer[] CollectTargetRenderers()
{
Scene activeScene = SceneManager.GetActiveScene();
GameObject[] roots = activeScene.GetRootGameObjects();
Renderer[] renderers = roots.Where(x => x.activeInHierarchy)
.SelectMany(x => x.GetComponentsInChildren<Renderer>())
.Where(x => x.enabled)
.ToArray();
Array.Reverse(renderers);
return renderers;
}
private Camera GetTargetCamera()
{
Camera camera = TargetCamera;
return (camera && camera.enabled ? camera : null);
}
private void SetupTransformationRenderers(Renderer[] renderers)
{
foreach (var renderer in renderers)
{
renderer.gameObject.AddComponent<TransformationRenderer>();
}
}
private void SetupTransformationCamera(Camera camera)
{
if (camera) camera.gameObject.AddComponent<TransformationCamera>();
}
private void SetupTransformAnimators(Renderer[] renderers)
{
HashSet<Transform> processed = new HashSet<Transform>();
int start = 0;
foreach (var renderer in renderers)
{
for (var transform = renderer.transform; transform != null; transform = transform.parent)
{
if (processed.Contains(transform)) break;
ModelTransformationAnimator animator = transform.gameObject.AddComponent<ModelTransformationAnimator>();
animators.Add(animator);
processed.Add(transform);
}
int end = animators.Count;
animators.Reverse(start, end - start);
start = end;
}
animators.Reverse();
}
private void SetupCameraAnimators(Camera camera)
{
if (camera)
{
GameObject gameObject = camera.gameObject;
ViewTransformationAnimator viewAnimator = gameObject.AddComponent<ViewTransformationAnimator>();
ProjectionTransformationAnimator projectionAnimator = gameObject.AddComponent<ProjectionTransformationAnimator>();
ViewportTransformationAnimator viewportAnimator = gameObject.AddComponent<ViewportTransformationAnimator>();
animators.Add(viewAnimator);
animators.Add(projectionAnimator);
animators.Add(viewportAnimator);
}
}
private void SetupIdleAnimator()
{
IdleAnimator animator = IdleAnimator.Create();
animators.Add(animator);
}
private void PrepareOffsetTable()
{
offsets.Clear();
int size = animators.Count + 1;
if (offsets.Capacity < size) offsets.Capacity = size;
int offset = 0;
for (int i = 0; i < animators.Count; i++)
{
offsets.Add(offset);
offset += animators[i].PhaseCount;
}
offsets.Add(offset);
}
private void InitializeAnimationState()
{
phase = 0;
time = 0.0f;
foreach (var animator in animators)
{
animator.Initialize();
}
}
private void DeleteAnimators()
{
animators.Clear();
}
private void UpdatePhaseAndTime(float currentTime)
{
if (time == 1.0f)
{
phase++;
time = 0.0f;
}
else
{
float deltaTime = currentTime - lastUpdateTime;
float elapsedTime = deltaTime * speed;
time = Mathf.Min(1.0f, time + elapsedTime);
}
lastUpdateTime = currentTime;
}
private void Animate()
{
int result = offsets.BinarySearch(phase);
int index = (result >= 0 ? result : (~result - 1));
if (index < animators.Count)
{
var animator = animators[index];
int localPhase = phase - offsets[index];
if (animator.Animate(localPhase, time))
{
description = animator.GetDescription(localPhase);
}
else
{
phase++;
Animate();
}
}
else
{
InitializeAnimationState();
description = null;
}
}
private void MarkAnimatorsDirty()
{
foreach (var animator in animators)
{
EditorUtility.SetDirty(animator.transform);
}
}
#endregion
}
#endregion
#region TemporaryMonoBehaviour
private abstract class TemporaryMonoBehaviour : MonoBehaviour
{
#region Messages
protected void Awake()
{
hideFlags = HideFlags.HideInInspector | HideFlags.DontSave;
AnimationStateChanged += OnAnimationStateChanged;
}
protected void OnDestroy()
{
AnimationStateChanged -= OnAnimationStateChanged;
}
#endregion
#region Methods
private void OnAnimationStateChanged(bool playing)
{
if (!playing) DestroyImmediate(this);
}
#endregion
}
#endregion
#region CameraEventHook
[RequireComponent(typeof(Camera))]
private sealed class CameraEventHook : TemporaryMonoBehaviour
{
#region Events
public static event Action Render;
#endregion
#region Messages
private void OnPreCull()
{
if (Render != null) Render.Invoke();
}
#endregion
}
#endregion
#region TransformationCamera
[RequireComponent(typeof(Camera))]
private sealed class TransformationCamera : TemporaryMonoBehaviour
{
#region Constants
private const float ScreenScale = 5.0e-2f;
#endregion
#region Fields
public Matrix4x4 ModelMatrix;
public Matrix4x4 ViewMatrix;
public Matrix4x4 ProjectionMatrix;
public Matrix4x4 ViewportMatrix;
private Vector3[] points = new Vector3[8];
private Camera original;
private static TransformationCamera instance;
#endregion
#region Properties
public static TransformationCamera Instance { get { return instance; } }
public Camera Original { get { return original; } }
#endregion
#region Messages
private new void Awake()
{
base.Awake();
ModelMatrix = transform.localToWorldMatrix;
ViewMatrix = Matrix4x4.identity;
ProjectionMatrix = Matrix4x4.identity;
ViewportMatrix = Matrix4x4.identity;
original = GetComponent<Camera>();
instance = this;
if (!original) DestroyImmediate(this);
}
private new void OnDestroy()
{
base.OnDestroy();
instance = null;
}
private void OnDrawGizmos()
{
Camera camera = original;
if (camera.orthographic)
{
DrawBox(camera);
}
else
{
DrawFrustum(camera);
}
DrawScreen();
}
#endregion
#region Methods
public Matrix4x4 GetTranformationMatrix(Transform transform)
{
return ViewportMatrix * ProjectionMatrix * ViewMatrix * transform.localToWorldMatrix;
}
public Rect GetViewport()
{
Rect result = original.pixelRect;
result.xMin *= ScreenScale;
result.xMax *= ScreenScale;
result.yMin *= ScreenScale;
result.yMax *= ScreenScale;
return result;
}
private void DrawBox(Camera camera)
{
float size = camera.orthographicSize;
float aspect = camera.aspect;
float nearClip = camera.nearClipPlane;
float farClip = camera.farClipPlane;
float halfHeight = size;
float halfWidth = halfHeight * aspect;
points[0] = new Vector3(-halfWidth, halfHeight, nearClip);
points[1] = new Vector3(halfWidth, halfHeight, nearClip);
points[2] = new Vector3(halfWidth, -halfHeight, nearClip);
points[3] = new Vector3(-halfWidth, -halfHeight, nearClip);
points[4] = new Vector3(-halfWidth, halfHeight, farClip);
points[5] = new Vector3(halfWidth, halfHeight, farClip);
points[6] = new Vector3(halfWidth, -halfHeight, farClip);
points[7] = new Vector3(-halfWidth, -halfHeight, farClip);
DrawHexahedral();
}
private void DrawFrustum(Camera camera)
{
float fov = camera.fieldOfView;
float aspect = camera.aspect;
float nearClip = camera.nearClipPlane;
float farClip = camera.farClipPlane;
float halfHeight = Mathf.Tan(fov * Mathf.Deg2Rad * 0.5f);
float halfWidth = halfHeight * aspect;
points[0] = new Vector3(-halfWidth, halfHeight, 1.0f) * nearClip;
points[1] = new Vector3(halfWidth, halfHeight, 1.0f) * nearClip;
points[2] = new Vector3(halfWidth, -halfHeight, 1.0f) * nearClip;
points[3] = new Vector3(-halfWidth, -halfHeight, 1.0f) * nearClip;
points[4] = new Vector3(-halfWidth, halfHeight, 1.0f) * farClip;
points[5] = new Vector3(halfWidth, halfHeight, 1.0f) * farClip;
points[6] = new Vector3(halfWidth, -halfHeight, 1.0f) * farClip;
points[7] = new Vector3(-halfWidth, -halfHeight, 1.0f) * farClip;
DrawHexahedral();
}
private void DrawHexahedral()
{
Matrix4x4 matrix = ViewportMatrix * ProjectionMatrix * ViewMatrix * ModelMatrix;
for (int i = 0; i < points.Length; i++)
{
Vector3 point = points[i];
Vector4 transformed = matrix * new Vector4(point.x, point.y, point.z, 1.0f);
points[i] = (Vector3)transformed / transformed.w;
}
Gizmos.color = Color.yellow;
for (int i = 0; i < 4; i++)
{
Gizmos.DrawLine(points[i], points[i + 4]);
Gizmos.DrawLine(points[i], points[(i + 1) % 4]);
Gizmos.DrawLine(points[i + 4], points[(i + 1) % 4 + 4]);
}
}
private void DrawScreen()
{
Rect viewport = GetViewport();
points[0] = new Vector3(viewport.xMin, viewport.yMax, 0.0f);
points[1] = new Vector3(viewport.xMax, viewport.yMax, 0.0f);
points[2] = new Vector3(viewport.xMax, viewport.yMin, 0.0f);
points[3] = new Vector3(viewport.xMin, viewport.yMin, 0.0f);
points[4] = points[0];
Gizmos.color = Color.green;
for (int i = 0; i < 4; i++)
{
Gizmos.DrawLine(points[i], points[i + 1]);
}
}
#endregion
}
#endregion
#region TransformationRenderer
[RequireComponent(typeof(Renderer))]
private sealed class TransformationRenderer : TemporaryMonoBehaviour
{
#region Constants
private const float BoundingBoxSize = 1.0e5f;
#endregion
#region Fields
private new Renderer renderer;
private Mesh mesh;
private Material[] materials;
private MaterialPropertyBlock properties;
#endregion
#region Messages
private new void Awake()
{
base.Awake();
renderer = GetComponent<Renderer>();
properties = new MaterialPropertyBlock();
CreateMesh();
CreateMaterials();
CameraEventHook.Render += Render;
renderer.enabled = false;
}
private new void OnDestroy()
{
base.OnDestroy();
DeleteMesh();
DeleteMaterials();
CameraEventHook.Render -= Render;
renderer.enabled = true;
}
#endregion
#region Methods
private void CreateMesh()
{
Mesh source = null;
MeshFilter meshFilter;
SkinnedMeshRenderer skinnedMeshRenderer;
if (meshFilter = GetComponent<MeshFilter>())
{
source = meshFilter.sharedMesh;
}
else if (skinnedMeshRenderer = GetComponent<SkinnedMeshRenderer>())
{
source = skinnedMeshRenderer.sharedMesh;
}
if (source)
{
mesh = Instantiate(source);
mesh.hideFlags = HideFlags.DontSave;
mesh.bounds = new Bounds(Vector3.zero, Vector3.one * BoundingBoxSize);
}
}
private void DeleteMesh()
{
if (mesh) DestroyImmediate(mesh);
}
private void CreateMaterials()
{
Material[] source = renderer.sharedMaterials;
materials = new Material[source.Length];
for (int i = 0; i < materials.Length; i++)
{
Material material = Instantiate(source[i]);
material.hideFlags = HideFlags.DontSave;
materials[i] = material;
}
}
private void DeleteMaterials()
{
foreach (var material in materials) DestroyImmediate(material);
}
private void Render()
{
if (!enabled) return;
TransformationCamera camera = TransformationCamera.Instance;
if (camera && mesh)
{
Matrix4x4 matrix = camera ? camera.GetTranformationMatrix(transform) : transform.localToWorldMatrix;
int layer = gameObject.layer;
ShadowCastingMode castShadows = renderer.shadowCastingMode;
bool receiveShadows = renderer.receiveShadows;
Transform probeAnchor = renderer.probeAnchor;
renderer.GetPropertyBlock(properties);
for (int i = 0; i < mesh.subMeshCount; i++)
{
Graphics.DrawMesh(mesh, matrix, materials[i], layer, null, i, properties, castShadows, receiveShadows, probeAnchor);
}
}
}
#endregion
}
#endregion
#region AnimatorBase
private abstract class AnimatorBase : TemporaryMonoBehaviour
{
#region Properties
public abstract int PhaseCount { get; }
#endregion
#region Methods
public abstract void Initialize();
public abstract bool Animate(int phase, float time);
public abstract string GetDescription(int phase);
#endregion
}
#endregion
#region ModelTransformationAnimator
private sealed class ModelTransformationAnimator : AnimatorBase
{
#region Fields
private Vector3 targetPosition;
private Quaternion targetRotation;
private Vector3 targetScale;
private new TransformationRenderer renderer;
#endregion
#region Properties
public override int PhaseCount { get { return 3; } }
#endregion
#region Messages
private new void Awake()
{
base.Awake();
renderer = GetComponent<TransformationRenderer>();
GetTransform(out targetPosition, out targetRotation, out targetScale);
}
private new void OnDestroy()
{
base.OnDestroy();
SetTransform(ref targetPosition, ref targetRotation, ref targetScale);
}
#endregion
#region Methods
public override void Initialize()
{
Vector3 position = Vector3.zero;
Quaternion rotation = Quaternion.identity;
Vector3 scale = Vector3.one;
SetTransform(ref position, ref rotation, ref scale);
if (renderer) renderer.enabled = false;
}
public override bool Animate(int phase, float time)
{
Vector3 position;
Quaternion rotation;
Vector3 scale;
switch (phase)
{
case 0:
if (targetScale == Vector3.one) return false;
position = Vector3.zero;
rotation = Quaternion.identity;
scale = Vector3.Lerp(Vector3.one, targetScale, time);
break;
case 1:
if (targetRotation == Quaternion.identity) return false;
position = Vector3.zero;
rotation = Quaternion.Slerp(Quaternion.identity, targetRotation, time);
scale = targetScale;
break;
case 2:
if (targetPosition == Vector3.zero) return false;
position = Vector3.Lerp(Vector3.zero, targetPosition, time);
rotation = targetRotation;
scale = targetScale;
break;
default:
throw new InvalidOperationException();
}
SetTransform(ref position, ref rotation, ref scale);
if (renderer) renderer.enabled = true;
return true;
}
public override string GetDescription(int phase)
{
const string Label = "モデル変換(ワールド変換) Model Transformation: ";
switch (phase)
{
case 0: return Label + "拡大縮小 Scaling";
case 1: return Label + "回転 Rotation";
case 2: return Label + "平行移動 Translation";
default: throw new InvalidOperationException();
}
}
private void GetTransform(out Vector3 position, out Quaternion rotation, out Vector3 scale)
{
position = transform.localPosition;
rotation = transform.localRotation;
scale = transform.localScale;
}
private void SetTransform(ref Vector3 position, ref Quaternion rotation, ref Vector3 scale)
{
transform.localPosition = position;
transform.localRotation = rotation;
transform.localScale = scale;
}
#endregion
}
#endregion
#region ViewTransformationAnimator
private sealed class ViewTransformationAnimator : AnimatorBase
{
#region Fields
private Vector3 startPosition;
private Quaternion startRotation;
#endregion
#region Properties
public override int PhaseCount { get { return 3; } }
#endregion
#region Messages
private new void Awake()
{
base.Awake();
GetTransform(out startPosition, out startRotation);
}
private new void OnDestroy()
{
base.OnDestroy();
SetTransform(ref startPosition, ref startRotation);
}
#endregion
#region Methods
public override void Initialize()
{
SetTransform(ref startPosition, ref startRotation);
TransformationCamera camera = TransformationCamera.Instance;
if (camera)
{
camera.ViewMatrix = Matrix4x4.identity;
}
}
public override bool Animate(int phase, float time)
{
TransformationCamera camera = TransformationCamera.Instance;
if (!camera) return false;
Vector3 position;
Quaternion rotation;
Vector3 scale;
switch (phase)
{
case 0:
if (startPosition == Vector3.zero) return false;
position = Vector3.Lerp(startPosition, Vector3.zero, time);
rotation = startRotation;
scale = Vector3.one;
break;
case 1:
if (startRotation == Quaternion.identity) return false;
position = Vector3.zero;
rotation = Quaternion.Slerp(startRotation, Quaternion.identity, time);
scale = Vector3.one;
break;
case 2:
position = Vector3.zero;
rotation = Quaternion.identity;
scale = Vector3.Lerp(Vector3.one, new Vector3(1.0f, 1.0f, -1.0f), time);
break;
default:
throw new InvalidOperationException();
}
SetTransform(ref position, ref rotation);
camera.ViewMatrix = GetViewMatrix(ref position, ref rotation, ref scale);
return true;
}
public override string GetDescription(int phase)
{
const string Label = "ビュー変換(視野変換) View Transformation: ";
switch (phase)
{
case 0: return Label + "平行移動 Translation";
case 1: return Label + "回転 Rotation";
case 2: return Label + "Z軸反転 Flipping Z-Axis";
default: throw new InvalidOperationException();
}
}
private void GetTransform(out Vector3 position, out Quaternion rotation)
{
position = transform.position;
rotation = transform.rotation;
}
private void SetTransform(ref Vector3 position, ref Quaternion rotation)
{
transform.position = position;
transform.rotation = rotation;
}
private Matrix4x4 GetViewMatrix(ref Vector3 position, ref Quaternion rotation, ref Vector3 scale)
{
Vector3 deltaPosition = startPosition - position;
Quaternion deltaRotation = startRotation * Quaternion.Inverse(rotation);
Matrix4x4 matrix = Matrix4x4.Scale(scale) * Matrix4x4.TRS(deltaPosition, deltaRotation, Vector3.one).inverse;
return matrix;
}
#endregion
}
#endregion
#region ProjectionTransformationAnimator
private sealed class ProjectionTransformationAnimator : AnimatorBase
{
#region Properties
public override int PhaseCount { get { return 3; } }
#endregion
#region Methods
public override void Initialize()
{
TransformationCamera camera = TransformationCamera.Instance;
if (camera)
{
camera.ProjectionMatrix = Matrix4x4.identity;
}
}
public override bool Animate(int phase, float time)
{
TransformationCamera camera = TransformationCamera.Instance;
if (!camera) return false;
Camera target = camera.Original;
Matrix4x4 matrix;
switch (phase)
{
case 0:
FlipZAxis(time, out matrix);
break;
case 1:
if (target.orthographic)
{
Orthographic(target, time, out matrix);
}
else
{
PerspectiveClip(target, time, out matrix);
}
break;
case 2:
if (target.orthographic)
{
return false;
}
else
{
PerspectiveNormalize(target, time, out matrix);
}
break;
default:
throw new InvalidOperationException();
}
camera.ProjectionMatrix = matrix;
return true;
}
public override string GetDescription(int phase)
{
const string Label = "射影変換(投影変換) Projection Transformation: ";
switch (phase)
{
case 0: return Label + "Z軸反転 Flipping Z-Axis";
case 1:
if (TransformationCamera.Instance.Original.orthographic)
{
return Label + "正射影変換 Orthographic Transformation";
}
else
{
return Label + "透視変換 Perspective Transformation";
}
case 2: return Label + "透視除算 Perspective Division";
default: throw new InvalidOperationException();
}
}
private void FlipZAxis(float time, out Matrix4x4 matrix)
{
Vector3 scale = Vector3.Lerp(Vector3.one, new Vector3(1.0f, 1.0f, -1.0f), time);
matrix = Matrix4x4.Scale(scale);
}
private void Orthographic(Camera camera, float time, out Matrix4x4 matrix)
{
GetTargetOrthographicMatrix(camera, out matrix);
LerpTranslationAndScale(time, ref matrix);
}
private void PerspectiveClip(Camera camera, float time, out Matrix4x4 matrix)
{
GetTargetPerspectiveMatrix(camera, out matrix);
LerpTranslationAndScale(time, ref matrix);
LerpCoordinatesScale(0.0f, ref matrix);
}
private void PerspectiveNormalize(Camera camera, float time, out Matrix4x4 matrix)
{
GetTargetPerspectiveMatrix(camera, out matrix);
LerpCoordinatesScale(time, ref matrix);
}
private void GetTargetOrthographicMatrix(Camera camera, out Matrix4x4 matrix)
{
float halfHeight = camera.orthographicSize;
float halfWidth = halfHeight * camera.aspect;
float zNear = camera.nearClipPlane;
float zFar = camera.farClipPlane;
matrix = Matrix4x4.Ortho(-halfWidth, halfWidth, -halfHeight, halfHeight, zNear, zFar);
}
private void GetTargetPerspectiveMatrix(Camera camera, out Matrix4x4 matrix)
{
float fov = camera.fieldOfView;
float aspect = camera.aspect;
float zNear = camera.nearClipPlane;
float zFar = camera.farClipPlane;
matrix = Matrix4x4.Perspective(fov, aspect, zNear, zFar);
}
private static void LerpTranslationAndScale(float time, ref Matrix4x4 matrix)
{
matrix.m03 = Mathf.Lerp(0.0f, matrix.m03, time);
matrix.m13 = Mathf.Lerp(0.0f, matrix.m13, time);
matrix.m23 = Mathf.Lerp(0.0f, matrix.m23, time);
matrix.m00 = Mathf.Lerp(1.0f, matrix.m00, time);
matrix.m11 = Mathf.Lerp(1.0f, matrix.m11, time);
matrix.m22 = Mathf.Lerp(-1.0f, matrix.m22, time);
}
private static void LerpCoordinatesScale(float time, ref Matrix4x4 matrix)
{
matrix.m32 = Mathf.Lerp(0.0f, matrix.m32, time);
matrix.m33 = Mathf.Lerp(1.0f, 0.0f, time);
}
#endregion
}
#endregion
#region ViewportTransformationAnimator
private sealed class ViewportTransformationAnimator : AnimatorBase
{
#region Properties
public override int PhaseCount { get { return 2; } }
#endregion
#region Methods
public override void Initialize()
{
TransformationCamera camera = TransformationCamera.Instance;
if (camera)
{
camera.ViewportMatrix = Matrix4x4.identity;
}
}
public override bool Animate(int phase, float time)
{
TransformationCamera camera = TransformationCamera.Instance;
if (!camera) return false;
Rect viewport = camera.GetViewport();
float halfWidth = viewport.width * 0.5f;
float halfHeight = viewport.height * 0.5f;
Vector3 targetCenter = new Vector3(viewport.xMin + halfWidth, viewport.yMin + halfHeight, 0.5f);
Vector3 targetScale = new Vector3(halfWidth, FlipViewportY ? -halfHeight : halfHeight, 0.5f);
Vector3 center;
Vector3 scale;
switch (phase)
{
case 0:
center = Vector3.zero;
scale = Vector3.Lerp(Vector3.one, targetScale, time);
break;
case 1:
center = Vector3.Lerp(Vector3.zero, targetCenter, time);
scale = targetScale;
break;
default:
throw new InvalidOperationException();
}
camera.ViewportMatrix = Matrix4x4.TRS(center, Quaternion.identity, scale);
return true;
}
public override string GetDescription(int phase)
{
const string Label = "ビューポート変換(スクリーン変換) Viewport Transformation: ";
switch (phase)
{
case 0: return Label + "拡大縮小 Scaling";
case 1: return Label + "平行移動 Translation";
default: throw new InvalidOperationException();
}
}
#endregion
}
#endregion
#region IdleAnimator
private sealed class IdleAnimator : AnimatorBase
{
#region Properties
public override int PhaseCount { get { return 5; } }
#endregion
#region Messages
private new void Awake()
{
AnimationStateChanged += OnAnimationStateChanged;
base.Awake();
}
private new void OnDestroy()
{
base.OnDestroy();
AnimationStateChanged -= OnAnimationStateChanged;
}
#endregion
#region Methods
public static IdleAnimator Create()
{
GameObject gameObject = new GameObject("Idle") { hideFlags = HideFlags.HideAndDontSave };
return gameObject.AddComponent<IdleAnimator>();
}
public override void Initialize() { }
public override bool Animate(int phase, float time) { return true; }
public override string GetDescription(int phase)
{
return "頂点変換完了 Complete";
}
private void OnAnimationStateChanged(bool playing)
{
if (!playing) DestroyImmediate(gameObject);
}
#endregion
}
#endregion
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment