頂点変換アニメーションの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