Created
July 11, 2016 13:40
-
-
Save f-space/074e0c116018aa8eccf5484c44e1de4b to your computer and use it in GitHub Desktop.
頂点変換アニメーションのUnityエディタ拡張
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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