A game about forced loneliness, made by TACStudios
at master 435 lines 18 kB view raw
1using System; 2using System.Collections.Generic; 3using System.Linq; 4using UnityEngine; 5using UnityEditor; 6using UnityEngineInternal; 7using UnityEngine.Timeline; 8using UnityEngine.Playables; 9using Object = UnityEngine.Object; 10 11namespace UnityEditor.Timeline 12{ 13 class TimelineAnimationUtilities 14 { 15 public enum OffsetEditMode 16 { 17 None = -1, 18 Translation = 0, 19 Rotation = 1 20 } 21 22 public static bool ValidateOffsetAvailabitity(PlayableDirector director, Animator animator) 23 { 24 if (director == null || animator == null) 25 return false; 26 27 return true; 28 } 29 30 public static TimelineClip GetPreviousClip(TimelineClip clip) 31 { 32 TimelineClip previousClip = null; 33 foreach (var c in clip.GetParentTrack().clips) 34 { 35 if (c.start < clip.start && (previousClip == null || c.start >= previousClip.start)) 36 previousClip = c; 37 } 38 return previousClip; 39 } 40 41 public static TimelineClip GetNextClip(TimelineClip clip) 42 { 43 return clip.GetParentTrack().clips.Where(c => c.start > clip.start).OrderBy(c => c.start).FirstOrDefault(); 44 } 45 46 public struct RigidTransform 47 { 48 public Vector3 position; 49 public Quaternion rotation; 50 51 public static RigidTransform Compose(Vector3 pos, Quaternion rot) 52 { 53 RigidTransform ret; 54 ret.position = pos; 55 ret.rotation = rot; 56 return ret; 57 } 58 59 public static RigidTransform Mul(RigidTransform a, RigidTransform b) 60 { 61 RigidTransform ret; 62 ret.rotation = a.rotation * b.rotation; 63 ret.position = a.position + a.rotation * b.position; 64 return ret; 65 } 66 67 public static RigidTransform Inverse(RigidTransform a) 68 { 69 RigidTransform ret; 70 ret.rotation = Quaternion.Inverse(a.rotation); 71 ret.position = ret.rotation * (-a.position); 72 return ret; 73 } 74 75 public static RigidTransform identity 76 { 77 get { return Compose(Vector3.zero, Quaternion.identity); } 78 } 79 } 80 81 82 private static Matrix4x4 GetTrackMatrix(Transform transform, AnimationTrack track) 83 { 84 Matrix4x4 trackMatrix = Matrix4x4.TRS(track.position, track.rotation, Vector3.one); 85 86 // in scene off mode, the track offsets are set to the preview position which is stored in the track 87 if (track.trackOffset == TrackOffset.ApplySceneOffsets) 88 { 89 trackMatrix = Matrix4x4.TRS(track.sceneOffsetPosition, Quaternion.Euler(track.sceneOffsetRotation), Vector3.one); 90 } 91 92 // put the parent transform on to the track matrix 93 if (transform.parent != null) 94 { 95 trackMatrix = transform.parent.localToWorldMatrix * trackMatrix; 96 } 97 98 return trackMatrix; 99 } 100 101 // Given a world space position and rotation, updates the clip offsets to match that 102 public static RigidTransform UpdateClipOffsets(AnimationPlayableAsset asset, AnimationTrack track, Transform transform, Vector3 globalPosition, Quaternion globalRotation) 103 { 104 Matrix4x4 worldToLocal = transform.worldToLocalMatrix; 105 Matrix4x4 clipMatrix = Matrix4x4.TRS(asset.position, asset.rotation, Vector3.one); 106 Matrix4x4 trackMatrix = GetTrackMatrix(transform, track); 107 108 109 // Use the transform to find the proper goal matrix with scale taken into account 110 var oldPos = transform.position; 111 var oldRot = transform.rotation; 112 transform.position = globalPosition; 113 transform.rotation = globalRotation; 114 Matrix4x4 goal = transform.localToWorldMatrix; 115 transform.position = oldPos; 116 transform.rotation = oldRot; 117 118 // compute the new clip matrix. 119 Matrix4x4 newClip = trackMatrix.inverse * goal * worldToLocal * trackMatrix * clipMatrix; 120 return RigidTransform.Compose(newClip.GetColumn(3), MathUtils.QuaternionFromMatrix(newClip)); 121 } 122 123 public static RigidTransform GetTrackOffsets(AnimationTrack track, Transform transform) 124 { 125 Vector3 position = track.position; 126 Quaternion rotation = track.rotation; 127 if (transform != null && transform.parent != null) 128 { 129 position = transform.parent.TransformPoint(position); 130 rotation = transform.parent.rotation * rotation; 131 MathUtils.QuaternionNormalize(ref rotation); 132 } 133 134 return RigidTransform.Compose(position, rotation); 135 } 136 137 public static void UpdateTrackOffset(AnimationTrack track, Transform transform, RigidTransform offsets) 138 { 139 if (transform != null && transform.parent != null) 140 { 141 offsets.position = transform.parent.InverseTransformPoint(offsets.position); 142 offsets.rotation = Quaternion.Inverse(transform.parent.rotation) * offsets.rotation; 143 MathUtils.QuaternionNormalize(ref offsets.rotation); 144 } 145 146 track.position = offsets.position; 147 track.eulerAngles = AnimationUtility.GetClosestEuler(offsets.rotation, track.eulerAngles, RotationOrder.OrderZXY); 148 track.UpdateClipOffsets(); 149 } 150 151 static MatchTargetFields GetMatchFields(TimelineClip clip) 152 { 153 var track = clip.GetParentTrack() as AnimationTrack; 154 if (track == null) 155 return MatchTargetFieldConstants.None; 156 157 var asset = clip.asset as AnimationPlayableAsset; 158 var fields = track.matchTargetFields; 159 if (asset != null && !asset.useTrackMatchFields) 160 fields = asset.matchTargetFields; 161 return fields; 162 } 163 164 static void WriteMatchFields(AnimationPlayableAsset asset, RigidTransform result, MatchTargetFields fields) 165 { 166 Vector3 position = asset.position; 167 168 position.x = fields.HasAny(MatchTargetFields.PositionX) ? result.position.x : position.x; 169 position.y = fields.HasAny(MatchTargetFields.PositionY) ? result.position.y : position.y; 170 position.z = fields.HasAny(MatchTargetFields.PositionZ) ? result.position.z : position.z; 171 172 asset.position = position; 173 174 // check first to avoid unnecessary conversion errors 175 if (fields.HasAny(MatchTargetFieldConstants.Rotation)) 176 { 177 Vector3 eulers = asset.eulerAngles; 178 Vector3 resultEulers = result.rotation.eulerAngles; 179 180 eulers.x = fields.HasAny(MatchTargetFields.RotationX) ? resultEulers.x : eulers.x; 181 eulers.y = fields.HasAny(MatchTargetFields.RotationY) ? resultEulers.y : eulers.y; 182 eulers.z = fields.HasAny(MatchTargetFields.RotationZ) ? resultEulers.z : eulers.z; 183 184 asset.eulerAngles = AnimationUtility.GetClosestEuler(Quaternion.Euler(eulers), asset.eulerAngles, RotationOrder.OrderZXY); 185 } 186 } 187 188 public static void MatchPrevious(TimelineClip currentClip, Transform matchPoint, PlayableDirector director) 189 { 190 const double timeEpsilon = 0.00001; 191 MatchTargetFields matchFields = GetMatchFields(currentClip); 192 if (matchFields == MatchTargetFieldConstants.None || matchPoint == null) 193 return; 194 195 double cachedTime = director.time; 196 197 // finds previous clip 198 TimelineClip previousClip = GetPreviousClip(currentClip); 199 if (previousClip == null || currentClip == previousClip) 200 return; 201 202 // make sure the transform is properly updated before modifying the graph 203 director.Evaluate(); 204 205 var parentTrack = currentClip.GetParentTrack() as AnimationTrack; 206 207 var blendIn = currentClip.blendInDuration; 208 currentClip.blendInDuration = 0; 209 var blendOut = previousClip.blendOutDuration; 210 previousClip.blendOutDuration = 0; 211 212 //evaluate previous without current 213 parentTrack.RemoveClip(currentClip); 214 director.RebuildGraph(); 215 double previousEndTime = currentClip.start > previousClip.end ? previousClip.end : currentClip.start; 216 director.time = previousEndTime - timeEpsilon; 217 director.Evaluate(); // add port to evaluate only track 218 219 var targetPosition = matchPoint.position; 220 var targetRotation = matchPoint.rotation; 221 222 // evaluate current without previous 223 parentTrack.AddClip(currentClip); 224 parentTrack.RemoveClip(previousClip); 225 director.RebuildGraph(); 226 director.time = currentClip.start + timeEpsilon; 227 director.Evaluate(); 228 229 ////////////////////////////////////////////////////////////////////// 230 //compute offsets 231 232 var animationPlayable = currentClip.asset as AnimationPlayableAsset; 233 var match = UpdateClipOffsets(animationPlayable, parentTrack, matchPoint, targetPosition, targetRotation); 234 WriteMatchFields(animationPlayable, match, matchFields); 235 236 ////////////////////////////////////////////////////////////////////// 237 238 currentClip.blendInDuration = blendIn; 239 previousClip.blendOutDuration = blendOut; 240 241 parentTrack.AddClip(previousClip); 242 director.RebuildGraph(); 243 director.time = cachedTime; 244 director.Evaluate(); 245 } 246 247 public static void MatchNext(TimelineClip currentClip, Transform matchPoint, PlayableDirector director) 248 { 249 const double timeEpsilon = 0.00001; 250 MatchTargetFields matchFields = GetMatchFields(currentClip); 251 if (matchFields == MatchTargetFieldConstants.None || matchPoint == null) 252 return; 253 254 double cachedTime = director.time; 255 256 // finds next clip 257 TimelineClip nextClip = GetNextClip(currentClip); 258 if (nextClip == null || currentClip == nextClip) 259 return; 260 261 // make sure the transform is properly updated before modifying the graph 262 director.Evaluate(); 263 264 var parentTrack = currentClip.GetParentTrack() as AnimationTrack; 265 266 var blendOut = currentClip.blendOutDuration; 267 var blendIn = nextClip.blendInDuration; 268 currentClip.blendOutDuration = 0; 269 nextClip.blendInDuration = 0; 270 271 //evaluate previous without current 272 parentTrack.RemoveClip(currentClip); 273 director.RebuildGraph(); 274 director.time = nextClip.start + timeEpsilon; 275 director.Evaluate(); // add port to evaluate only track 276 277 var targetPosition = matchPoint.position; 278 var targetRotation = matchPoint.rotation; 279 280 // evaluate current without next 281 parentTrack.AddClip(currentClip); 282 parentTrack.RemoveClip(nextClip); 283 director.RebuildGraph(); 284 director.time = Math.Min(nextClip.start, currentClip.end - timeEpsilon); 285 director.Evaluate(); 286 287 ////////////////////////////////////////////////////////////////////// 288 //compute offsets 289 290 var animationPlayable = currentClip.asset as AnimationPlayableAsset; 291 var match = UpdateClipOffsets(animationPlayable, parentTrack, matchPoint, targetPosition, targetRotation); 292 WriteMatchFields(animationPlayable, match, matchFields); 293 294 ////////////////////////////////////////////////////////////////////// 295 296 currentClip.blendOutDuration = blendOut; 297 nextClip.blendInDuration = blendIn; 298 299 parentTrack.AddClip(nextClip); 300 director.RebuildGraph(); 301 director.time = cachedTime; 302 director.Evaluate(); 303 } 304 305 public static TimelineWindowTimeControl CreateTimeController(TimelineClip clip) 306 { 307 var animationWindow = EditorWindow.GetWindow<AnimationWindow>(); 308 var timeController = ScriptableObject.CreateInstance<TimelineWindowTimeControl>(); 309 timeController.Init(animationWindow.state, clip); 310 return timeController; 311 } 312 313 public static TimelineWindowTimeControl CreateTimeController(TimelineWindowTimeControl.ClipData clipData) 314 { 315 var animationWindow = EditorWindow.GetWindow<AnimationWindow>(); 316 var timeController = ScriptableObject.CreateInstance<TimelineWindowTimeControl>(); 317 timeController.Init(animationWindow.state, clipData); 318 return timeController; 319 } 320 321 public static void EditAnimationClipWithTimeController(AnimationClip animationClip, TimelineWindowTimeControl timeController, Object sourceObject) 322 { 323 var animationWindow = EditorWindow.GetWindow<AnimationWindow>(); 324 animationWindow.EditSequencerClip(animationClip, sourceObject, timeController); 325 } 326 327 public static void UnlinkAnimationWindowFromTracks(IEnumerable<TrackAsset> tracks) 328 { 329 var clips = new List<AnimationClip>(); 330 foreach (var track in tracks) 331 { 332 var animationTrack = track as AnimationTrack; 333 if (animationTrack != null && animationTrack.infiniteClip != null) 334 clips.Add(animationTrack.infiniteClip); 335 336 GetAnimationClips(track.GetClips(), clips); 337 } 338 UnlinkAnimationWindowFromAnimationClips(clips); 339 } 340 341 public static void UnlinkAnimationWindowFromClips(IEnumerable<TimelineClip> timelineClips) 342 { 343 var clips = new List<AnimationClip>(); 344 GetAnimationClips(timelineClips, clips); 345 UnlinkAnimationWindowFromAnimationClips(clips); 346 } 347 348 public static void UnlinkAnimationWindowFromAnimationClips(ICollection<AnimationClip> clips) 349 { 350 if (clips.Count == 0) 351 return; 352 353 UnityEngine.Object[] windows = Resources.FindObjectsOfTypeAll(typeof(AnimationWindow)); 354 foreach (var animWindow in windows.OfType<AnimationWindow>()) 355 { 356 if (animWindow != null && animWindow.state != null && animWindow.state.linkedWithSequencer && clips.Contains(animWindow.state.activeAnimationClip)) 357 animWindow.UnlinkSequencer(); 358 } 359 } 360 361 public static void UnlinkAnimationWindow() 362 { 363 UnityEngine.Object[] windows = Resources.FindObjectsOfTypeAll(typeof(AnimationWindow)); 364 foreach (var animWindow in windows.OfType<AnimationWindow>()) 365 { 366 if (animWindow != null && animWindow.state != null && animWindow.state.linkedWithSequencer) 367 animWindow.UnlinkSequencer(); 368 } 369 } 370 371 private static void GetAnimationClips(IEnumerable<TimelineClip> timelineClips, List<AnimationClip> clips) 372 { 373 foreach (var timelineClip in timelineClips) 374 { 375 if (timelineClip.curves != null) 376 clips.Add(timelineClip.curves); 377 AnimationPlayableAsset apa = timelineClip.asset as AnimationPlayableAsset; 378 if (apa != null && apa.clip != null) 379 clips.Add(apa.clip); 380 } 381 } 382 383 public static int GetAnimationWindowCurrentFrame() 384 { 385 var animationWindow = EditorWindow.GetWindow<AnimationWindow>(); 386 if (animationWindow) 387 return animationWindow.state.currentFrame; 388 return -1; 389 } 390 391 public static void SetAnimationWindowCurrentFrame(int frame) 392 { 393 var animationWindow = EditorWindow.GetWindow<AnimationWindow>(); 394 if (animationWindow) 395 animationWindow.state.currentFrame = frame; 396 } 397 398 public static void ConstrainCurveToBooleanValues(AnimationCurve curve) 399 { 400 // Clamp the values first 401 var keys = curve.keys; 402 for (var i = 0; i < keys.Length; i++) 403 { 404 var key = keys[i]; 405 key.value = key.value < 0.5f ? 0.0f : 1.0f; 406 keys[i] = key; 407 } 408 curve.keys = keys; 409 410 // Update the tangents once all the values are clamped 411 for (var i = 0; i < curve.length; i++) 412 { 413 AnimationUtility.SetKeyLeftTangentMode(curve, i, AnimationUtility.TangentMode.Constant); 414 AnimationUtility.SetKeyRightTangentMode(curve, i, AnimationUtility.TangentMode.Constant); 415 } 416 } 417 418 public static void ConstrainCurveToRange(AnimationCurve curve, float minValue, float maxValue) 419 { 420 var keys = curve.keys; 421 for (var i = 0; i < keys.Length; i++) 422 { 423 var key = keys[i]; 424 key.value = Mathf.Clamp(key.value, minValue, maxValue); 425 keys[i] = key; 426 } 427 curve.keys = keys; 428 } 429 430 public static bool IsAnimationClip(TimelineClip clip) 431 { 432 return clip != null && (clip.asset as AnimationPlayableAsset) != null; 433 } 434 } 435}