diff options
Diffstat (limited to 'Library/PackageCache/com.unity.timeline@1.2.13/Runtime/TrackAsset.cs')
| -rw-r--r-- | Library/PackageCache/com.unity.timeline@1.2.13/Runtime/TrackAsset.cs | 1264 |
1 files changed, 1264 insertions, 0 deletions
diff --git a/Library/PackageCache/com.unity.timeline@1.2.13/Runtime/TrackAsset.cs b/Library/PackageCache/com.unity.timeline@1.2.13/Runtime/TrackAsset.cs new file mode 100644 index 0000000..cc0f607 --- /dev/null +++ b/Library/PackageCache/com.unity.timeline@1.2.13/Runtime/TrackAsset.cs @@ -0,0 +1,1264 @@ +using System; +using System.Collections.Generic; +using System.IO; +using UnityEngine.Animations; +using UnityEngine.Playables; + +namespace UnityEngine.Timeline +{ + /// <summary> + /// A PlayableAsset representing a track inside a timeline. + /// </summary> + [Serializable] + [IgnoreOnPlayableTrack] + public abstract partial class TrackAsset : PlayableAsset, IPropertyPreview, ICurvesOwner + { + // Internal caches used to avoid memory allocation during graph construction + private struct TransientBuildData + { + public List<TrackAsset> trackList; + public List<TimelineClip> clipList; + public List<IMarker> markerList; + + public static TransientBuildData Create() + { + return new TransientBuildData() + { + trackList = new List<TrackAsset>(20), + clipList = new List<TimelineClip>(500), + markerList = new List<IMarker>(100), + }; + } + + public void Clear() + { + trackList.Clear(); + clipList.Clear(); + markerList.Clear(); + } + } + + private static TransientBuildData s_BuildData = TransientBuildData.Create(); + + internal const string kDefaultCurvesName = "Track Parameters"; + + internal static event Action<TimelineClip, GameObject, Playable> OnClipPlayableCreate; + internal static event Action<TrackAsset, GameObject, Playable> OnTrackAnimationPlayableCreate; + + [SerializeField, HideInInspector] bool m_Locked; + [SerializeField, HideInInspector] bool m_Muted; + [SerializeField, HideInInspector] string m_CustomPlayableFullTypename = string.Empty; + [SerializeField, HideInInspector] AnimationClip m_Curves; + [SerializeField, HideInInspector] PlayableAsset m_Parent; + [SerializeField, HideInInspector] List<ScriptableObject> m_Children; + + [NonSerialized] int m_ItemsHash; + [NonSerialized] TimelineClip[] m_ClipsCache; + + DiscreteTime m_Start; + DiscreteTime m_End; + bool m_CacheSorted; + bool? m_SupportsNotifications; + + static TrackAsset[] s_EmptyCache = new TrackAsset[0]; + IEnumerable<TrackAsset> m_ChildTrackCache; + + static Dictionary<Type, TrackBindingTypeAttribute> s_TrackBindingTypeAttributeCache = new Dictionary<Type, TrackBindingTypeAttribute>(); + + [SerializeField, HideInInspector] protected internal List<TimelineClip> m_Clips = new List<TimelineClip>(); + + [SerializeField, HideInInspector] MarkerList m_Markers = new MarkerList(0); + +#if UNITY_EDITOR + internal int DirtyIndex { get; private set; } + internal void MarkDirty() + { + DirtyIndex++; + foreach (var clip in GetClips()) + { + if (clip != null) + clip.MarkDirty(); + } + } + +#endif + + /// <summary> + /// The start time, in seconds, of this track + /// </summary> + public double start + { + get + { + UpdateDuration(); + return (double)m_Start; + } + } + + /// <summary> + /// The end time, in seconds, of this track + /// </summary> + public double end + { + get + { + UpdateDuration(); + return (double)m_End; + } + } + + /// <summary> + /// The length, in seconds, of this track + /// </summary> + public sealed override double duration + { + get + { + UpdateDuration(); + return (double)(m_End - m_Start); + } + } + + /// <summary> + /// Whether the track is muted or not. + /// </summary> + /// <remarks> + /// A muted track is excluded from the generated PlayableGraph + /// </remarks> + public bool muted + { + get { return m_Muted; } + set { m_Muted = value; } + } + + /// <summary> + /// The muted state of a track. + /// </summary> + /// <remarks> + /// A track is also muted when one of its parent tracks are muted. + /// </remarks> + public bool mutedInHierarchy + { + get + { + if (muted) + return true; + + TrackAsset p = this; + while (p.parent as TrackAsset != null) + { + p = (TrackAsset)p.parent; + if (p as GroupTrack != null) + return p.mutedInHierarchy; + } + + return false; + } + } + + /// <summary> + /// The TimelineAsset that this track belongs to. + /// </summary> + public TimelineAsset timelineAsset + { + get + { + var node = this; + while (node != null) + { + if (node.parent == null) + return null; + + var seq = node.parent as TimelineAsset; + if (seq != null) + return seq; + + node = node.parent as TrackAsset; + } + return null; + } + } + + /// <summary> + /// The owner of this track. + /// </summary> + /// <remarks> + /// If this track is a subtrack, the parent is a TrackAsset. Otherwise the parent is a TimelineAsset. + /// </remarks> + public PlayableAsset parent + { + get { return m_Parent; } + internal set { m_Parent = value; } + } + + /// <summary> + /// A list of clips owned by this track + /// </summary> + /// <returns>Returns an enumerable list of clips owned by the track.</returns> + public IEnumerable<TimelineClip> GetClips() + { + return clips; + } + + internal TimelineClip[] clips + { + get + { + if (m_Clips == null) + m_Clips = new List<TimelineClip>(); + + if (m_ClipsCache == null) + { + m_CacheSorted = false; + m_ClipsCache = m_Clips.ToArray(); + } + + return m_ClipsCache; + } + } + + /// <summary> + /// Whether this track is considered empty. + /// </summary> + /// <remarks> + /// A track is considered empty when it does not contain a TimelineClip, Marker, or Curve. + /// </remarks> + /// <remarks> + /// Empty tracks are not included in the playable graph. + /// </remarks> + public virtual bool isEmpty + { + get { return !hasClips && !hasCurves && GetMarkerCount() == 0; } + } + + /// <summary> + /// Whether this track contains any TimelineClip. + /// </summary> + public bool hasClips + { + get { return m_Clips != null && m_Clips.Count != 0; } + } + + /// <summary> + /// Whether this track contains animated properties for the attached PlayableAsset. + /// </summary> + /// <remarks> + /// This property is false if the curves property is null or if it contains no information. + /// </remarks> + public bool hasCurves + { + get { return m_Curves != null && !m_Curves.empty; } + } + + /// <summary> + /// Returns whether this track is a subtrack + /// </summary> + public bool isSubTrack + { + get + { + var owner = parent as TrackAsset; + return owner != null && owner.GetType() == GetType(); + } + } + + + /// <summary> + /// Returns a description of the PlayableOutputs that will be created by this track. + /// </summary> + public override IEnumerable<PlayableBinding> outputs + { + get + { + TrackBindingTypeAttribute attribute; + if (!s_TrackBindingTypeAttributeCache.TryGetValue(GetType(), out attribute)) + { + attribute = (TrackBindingTypeAttribute)Attribute.GetCustomAttribute(GetType(), typeof(TrackBindingTypeAttribute)); + s_TrackBindingTypeAttributeCache.Add(GetType(), attribute); + } + + var trackBindingType = attribute != null ? attribute.type : null; + yield return ScriptPlayableBinding.Create(name, this, trackBindingType); + } + } + + /// <summary> + /// The list of subtracks or child tracks attached to this track. + /// </summary> + /// <returns>Returns an enumerable list of child tracks owned directly by this track.</returns> + /// <remarks> + /// In the case of GroupTracks, this returns all tracks contained in the group. This will return the all subtracks or override tracks, if supported by the track. + /// </remarks> + public IEnumerable<TrackAsset> GetChildTracks() + { + UpdateChildTrackCache(); + return m_ChildTrackCache; + } + + internal string customPlayableTypename + { + get { return m_CustomPlayableFullTypename; } + set { m_CustomPlayableFullTypename = value; } + } + + /// <summary> + /// An animation clip storing animated properties of the attached PlayableAsset + /// </summary> + public AnimationClip curves + { + get { return m_Curves; } + internal set { m_Curves = value; } + } + + string ICurvesOwner.defaultCurvesName + { + get { return kDefaultCurvesName; } + } + + Object ICurvesOwner.asset + { + get { return this; } + } + + Object ICurvesOwner.assetOwner + { + get { return timelineAsset; } + } + + TrackAsset ICurvesOwner.targetTrack + { + get { return this; } + } + + // for UI where we need to detect 'null' objects + internal List<ScriptableObject> subTracksObjects + { + get { return m_Children; } + } + + /// <summary> + /// The local locked state of the track. + /// </summary> + /// <remarks> + /// Note that locking a track only affects operations in the Timeline Editor. It does not prevent other API calls from changing a track or it's clips. + /// + /// This returns or sets the local locked state of the track. A track may still be locked for editing because one or more of it's parent tracks in the hierarchy is locked. Use lockedInHierarchy to test if a track is locked because of it's own locked state or because of a parent tracks locked state. + /// </remarks> + public bool locked + { + get { return m_Locked; } + set { m_Locked = value; } + } + + /// <summary> + /// The locked state of a track. (RO) + /// </summary> + /// <remarks> + /// Note that locking a track only affects operations in the Timeline Editor. It does not prevent other API calls from changing a track or it's clips. + /// + /// This indicates whether a track is locked in the Timeline Editor because either it's locked property is enabled or a parent track is locked. + /// </remarks> + public bool lockedInHierarchy + { + get + { + if (locked) + return true; + + TrackAsset p = this; + while (p.parent as TrackAsset != null) + { + p = (TrackAsset)p.parent; + if (p as GroupTrack != null) + return p.lockedInHierarchy; + } + + return false; + } + } + + /// <summary> + /// Indicates if a track accepts markers that implement <see cref="UnityEngine.Playables.INotification"/>. + /// </summary> + /// <remarks> + /// Only tracks with a bound object of type <see cref="UnityEngine.GameObject"/> or <see cref="UnityEngine.Component"/> can accept notifications. + /// </remarks> + public bool supportsNotifications + { + get + { + if (!m_SupportsNotifications.HasValue) + { + m_SupportsNotifications = NotificationUtilities.TrackTypeSupportsNotifications(GetType()); + } + + return m_SupportsNotifications.Value; + } + } + + void __internalAwake() //do not use OnEnable, since users will want it to initialize their class + { + if (m_Clips == null) + m_Clips = new List<TimelineClip>(); + + m_ChildTrackCache = null; + if (m_Children == null) + m_Children = new List<ScriptableObject>(); +#if UNITY_EDITOR + // validate the array. DON'T remove Unity null objects, just actual null objects + for (int i = m_Children.Count - 1; i >= 0; i--) + { + object o = m_Children[i]; + if (o == null) + { + Debug.LogWarning("Empty child track found while loading timeline. It will be removed."); + m_Children.RemoveAt(i); + } + } +#endif + } + + /// <summary> + /// Creates an AnimationClip to store animated properties for the attached PlayableAsset. + /// </summary> + /// <remarks> + /// If curves already exists for this track, this method produces no result regardless of + /// the value specified for curvesClipName. + /// </remarks> + /// <remarks> + /// When used from the editor, this method attempts to save the created curves clip to the TimelineAsset. + /// The TimelineAsset must already exist in the AssetDatabase to save the curves clip. If the TimelineAsset + /// does not exist, the curves clip is still created but it is not saved. + /// </remarks> + /// <param name="curvesClipName"> + /// The name of the AnimationClip to create. + /// This method does not ensure unique names. If you want a unique clip name, you must provide one. + /// See ObjectNames.GetUniqueName for information on a method that creates unique names. + /// </param> + public void CreateCurves(string curvesClipName) + { + if (m_Curves != null) + return; + + m_Curves = TimelineCreateUtilities.CreateAnimationClipForTrack(string.IsNullOrEmpty(curvesClipName) ? kDefaultCurvesName : curvesClipName, this, true); + } + + /// <summary> + /// Creates a mixer used to blend playables generated by clips on the track. + /// </summary> + /// <param name="graph">The graph to inject playables into</param> + /// <param name="go">The GameObject that requested the graph.</param> + /// <param name="inputCount">The number of playables from clips that will be inputs to the returned mixer</param> + /// <returns>A handle to the [[Playable]] representing the mixer.</returns> + /// <remarks> + /// Override this method to provide a custom playable for mixing clips on a graph. + /// </remarks> + public virtual Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount) + { + return Playable.Create(graph, inputCount); + } + + /// <summary> + /// Overrides PlayableAsset.CreatePlayable(). Not used in Timeline. + /// </summary> + public sealed override Playable CreatePlayable(PlayableGraph graph, GameObject go) + { + return Playable.Null; + } + + /// <summary> + /// Creates a TimelineClip on this track. + /// </summary> + /// <returns>Returns a new TimelineClip that is attached to the track.</returns> + /// <remarks> + /// The type of the playable asset attached to the clip is determined by TrackClip attributes that decorate the TrackAsset derived class + /// </remarks> + public TimelineClip CreateDefaultClip() + { + var trackClipTypeAttributes = GetType().GetCustomAttributes(typeof(TrackClipTypeAttribute), true); + Type playableAssetType = null; + foreach (var trackClipTypeAttribute in trackClipTypeAttributes) + { + var attribute = trackClipTypeAttribute as TrackClipTypeAttribute; + if (attribute != null && typeof(IPlayableAsset).IsAssignableFrom(attribute.inspectedType) && typeof(ScriptableObject).IsAssignableFrom(attribute.inspectedType)) + { + playableAssetType = attribute.inspectedType; + break; + } + } + + if (playableAssetType == null) + { + Debug.LogWarning("Cannot create a default clip for type " + GetType()); + return null; + } + return CreateAndAddNewClipOfType(playableAssetType); + } + + /// <summary> + /// Creates a clip on the track with a playable asset attached, whose derived type is specified by T + /// </summary> + /// <typeparam name="T">A PlayableAsset derived type</typeparam> + /// <returns>Returns a TimelineClip whose asset is of type T</returns> + /// <remarks> + /// Throws an InvalidOperationException if the specified type is not supported by the track. + /// Supported types are determined by TrackClip attributes that decorate the TrackAsset derived class + /// </remarks> + public TimelineClip CreateClip<T>() where T : ScriptableObject, IPlayableAsset + { + return CreateClip(typeof(T)); + } + + /// <summary> + /// Creates a marker of the requested type, at a specific time, and adds the marker to the current asset. + /// </summary> + /// <param name="type">The type of marker.</param> + /// <param name="time">The time where the marker is created.</param> + /// <returns>Returns the instance of the created marker.</returns> + /// <remarks> + /// All markers that implement IMarker and inherit from <see cref="UnityEngine.ScriptableObject"/> are supported. + /// Markers that implement the INotification interface cannot be added to tracks that do not support notifications. + /// CreateMarker will throw an <code>InvalidOperationException</code> with tracks that do not support notifications if <code>type</code> implements the INotification interface. + /// </remarks> + /// <seealso cref="UnityEngine.Timeline.Marker"/> + /// <seealso cref="UnityEngine.Timeline.TrackAsset.supportsNotifications"/> + public IMarker CreateMarker(Type type, double time) + { + return m_Markers.CreateMarker(type, time, this); + } + + /// <summary> + /// Creates a marker of the requested type, at a specific time, and adds the marker to the current asset. + /// </summary> + /// <param name="time">The time where the marker is created.</param> + /// <returns>Returns the instance of the created marker.</returns> + /// <remarks> + /// All markers that implement IMarker and inherit from <see cref="UnityEngine.ScriptableObject"/> are supported. + /// CreateMarker will throw an <code>InvalidOperationException</code> with tracks that do not support notifications if <code>T</code> implements the INotification interface. + /// </remarks> + /// <seealso cref="UnityEngine.Timeline.Marker"/> + /// <seealso cref="UnityEngine.Timeline.TrackAsset.supportsNotifications"/> + public T CreateMarker<T>(double time) where T : ScriptableObject, IMarker + { + return (T)CreateMarker(typeof(T), time); + } + + /// <summary> + /// Removes a marker from the current asset. + /// </summary> + /// <param name="marker">The marker instance to be removed.</param> + /// <returns>Returns true if the marker instance was successfully removed. Returns false otherwise.</returns> + public bool DeleteMarker(IMarker marker) + { + return m_Markers.Remove(marker); + } + + /// <summary> + /// Returns an enumerable list of markers on the current asset. + /// </summary> + /// <returns>The list of markers on the asset. + /// </returns> + public IEnumerable<IMarker> GetMarkers() + { + return m_Markers.GetMarkers(); + } + + /// <summary> + /// Returns the number of markers on the current asset. + /// </summary> + /// <returns>The number of markers.</returns> + public int GetMarkerCount() + { + return m_Markers.Count; + } + + /// <summary> + /// Returns the marker at a given position, on the current asset. + /// </summary> + /// <param name="idx">The index of the marker to be returned.</param> + /// <returns>The marker.</returns> + /// <remarks>The ordering of the markers is not guaranteed. + /// </remarks> + public IMarker GetMarker(int idx) + { + return m_Markers[idx]; + } + + internal TimelineClip CreateClip(System.Type requestedType) + { + if (ValidateClipType(requestedType)) + return CreateAndAddNewClipOfType(requestedType); + + throw new InvalidOperationException("Clips of type " + requestedType + " are not permitted on tracks of type " + GetType()); + } + + internal TimelineClip CreateAndAddNewClipOfType(Type requestedType) + { + var newClip = CreateClipOfType(requestedType); + AddClip(newClip); + return newClip; + } + + internal TimelineClip CreateClipOfType(Type requestedType) + { + if (!ValidateClipType(requestedType)) + throw new System.InvalidOperationException("Clips of type " + requestedType + " are not permitted on tracks of type " + GetType()); + + var playableAsset = CreateInstance(requestedType); + if (playableAsset == null) + { + throw new System.InvalidOperationException("Could not create an instance of the ScriptableObject type " + requestedType.Name); + } + playableAsset.name = requestedType.Name; + TimelineCreateUtilities.SaveAssetIntoObject(playableAsset, this); + TimelineUndo.RegisterCreatedObjectUndo(playableAsset, "Create Clip"); + + return CreateClipFromAsset(playableAsset); + } + + /// <summary> + /// Creates a timeline clip from an existing playable asset. + /// </summary> + /// <param name="asset"></param> + /// <returns></returns> + internal TimelineClip CreateClipFromPlayableAsset(IPlayableAsset asset) + { + if (asset == null) + throw new ArgumentNullException("asset"); + + if ((asset as ScriptableObject) == null) + throw new System.ArgumentException("CreateClipFromPlayableAsset " + " only supports ScriptableObject-derived Types"); + + if (!ValidateClipType(asset.GetType())) + throw new System.InvalidOperationException("Clips of type " + asset.GetType() + " are not permitted on tracks of type " + GetType()); + + return CreateClipFromAsset(asset as ScriptableObject); + } + + private TimelineClip CreateClipFromAsset(ScriptableObject playableAsset) + { + TimelineUndo.PushUndo(this, "Create Clip"); + + var newClip = CreateNewClipContainerInternal(); + newClip.displayName = playableAsset.name; + newClip.asset = playableAsset; + + IPlayableAsset iPlayableAsset = playableAsset as IPlayableAsset; + if (iPlayableAsset != null) + { + var candidateDuration = iPlayableAsset.duration; + + if (!double.IsInfinity(candidateDuration) && candidateDuration > 0) + newClip.duration = Math.Min(Math.Max(candidateDuration, TimelineClip.kMinDuration), TimelineClip.kMaxTimeValue); + } + + try + { + OnCreateClip(newClip); + } + catch (Exception e) + { + Debug.LogError(e.Message, playableAsset); + return null; + } + + return newClip; + } + + internal IEnumerable<ScriptableObject> GetMarkersRaw() + { + return m_Markers.GetRawMarkerList(); + } + + internal void ClearMarkers() + { + m_Markers.Clear(); + } + + internal void AddMarker(ScriptableObject e) + { + m_Markers.Add(e); + } + + internal bool DeleteMarkerRaw(ScriptableObject marker) + { + return m_Markers.Remove(marker, timelineAsset, this); + } + + int GetTimeRangeHash() + { + double start = double.MaxValue, end = double.MinValue; + foreach (var marker in GetMarkers()) + { + if (!(marker is INotification)) + { + continue; + } + + if (marker.time < start) + start = marker.time; + if (marker.time > end) + end = marker.time; + } + + return start.GetHashCode().CombineHash(end.GetHashCode()); + } + + internal void AddClip(TimelineClip newClip) + { + if (!m_Clips.Contains(newClip)) + { + m_Clips.Add(newClip); + m_ClipsCache = null; + } + } + + Playable CreateNotificationsPlayable(PlayableGraph graph, Playable mixerPlayable, GameObject go, Playable timelinePlayable) + { + s_BuildData.markerList.Clear(); + GatherNotificiations(s_BuildData.markerList); + var notificationPlayable = NotificationUtilities.CreateNotificationsPlayable(graph, s_BuildData.markerList, go); + if (notificationPlayable.IsValid()) + { + notificationPlayable.GetBehaviour().timeSource = timelinePlayable; + if (mixerPlayable.IsValid()) + { + notificationPlayable.SetInputCount(1); + graph.Connect(mixerPlayable, 0, notificationPlayable, 0); + notificationPlayable.SetInputWeight(mixerPlayable, 1); + } + } + + return notificationPlayable; + } + + internal Playable CreatePlayableGraph(PlayableGraph graph, GameObject go, IntervalTree<RuntimeElement> tree, Playable timelinePlayable) + { + UpdateDuration(); + var mixerPlayable = Playable.Null; + if (CanCompileClipsRecursive()) + mixerPlayable = OnCreateClipPlayableGraph(graph, go, tree); + + var notificationsPlayable = CreateNotificationsPlayable(graph, mixerPlayable, go, timelinePlayable); + if (!notificationsPlayable.IsValid() && !mixerPlayable.IsValid()) + { + Debug.LogErrorFormat("Track {0} of type {1} has no notifications and returns an invalid mixer Playable", name, + GetType().FullName); + + return Playable.Create(graph); + } + + return notificationsPlayable.IsValid() ? notificationsPlayable : mixerPlayable; + } + + internal virtual Playable CompileClips(PlayableGraph graph, GameObject go, IList<TimelineClip> timelineClips, IntervalTree<RuntimeElement> tree) + { + var blend = CreateTrackMixer(graph, go, timelineClips.Count); + for (var c = 0; c < timelineClips.Count; c++) + { + var source = CreatePlayable(graph, go, timelineClips[c]); + if (source.IsValid()) + { + source.SetDuration(timelineClips[c].duration); + var clip = new RuntimeClip(timelineClips[c], source, blend); + tree.Add(clip); + graph.Connect(source, 0, blend, c); + blend.SetInputWeight(c, 0.0f); + } + } + ConfigureTrackAnimation(tree, go, blend); + return blend; + } + + void GatherCompilableTracks(IList<TrackAsset> tracks) + { + if (!muted && CanCompileClips()) + tracks.Add(this); + + foreach (var c in GetChildTracks()) + { + if (c != null) + c.GatherCompilableTracks(tracks); + } + } + + void GatherNotificiations(List<IMarker> markers) + { + if (!muted && CanCompileNotifications()) + markers.AddRange(GetMarkers()); + foreach (var c in GetChildTracks()) + { + if (c != null) + c.GatherNotificiations(markers); + } + } + + internal virtual Playable OnCreateClipPlayableGraph(PlayableGraph graph, GameObject go, IntervalTree<RuntimeElement> tree) + { + if (tree == null) + throw new ArgumentException("IntervalTree argument cannot be null", "tree"); + + if (go == null) + throw new ArgumentException("GameObject argument cannot be null", "go"); + + s_BuildData.Clear(); + GatherCompilableTracks(s_BuildData.trackList); + + // nothing to compile + if (s_BuildData.trackList.Count == 0) + return Playable.Null; + + // check if layers are supported + Playable layerMixer = Playable.Null; + ILayerable layerable = this as ILayerable; + if (layerable != null) + layerMixer = layerable.CreateLayerMixer(graph, go, s_BuildData.trackList.Count); + + if (layerMixer.IsValid()) + { + for (int i = 0; i < s_BuildData.trackList.Count; i++) + { + var mixer = s_BuildData.trackList[i].CompileClips(graph, go, s_BuildData.trackList[i].clips, tree); + if (mixer.IsValid()) + { + graph.Connect(mixer, 0, layerMixer, i); + layerMixer.SetInputWeight(i, 1.0f); + } + } + return layerMixer; + } + + // one track compiles. Add track mixer and clips + if (s_BuildData.trackList.Count == 1) + return s_BuildData.trackList[0].CompileClips(graph, go, s_BuildData.trackList[0].clips, tree); + + // no layer mixer provided. merge down all clips. + for (int i = 0; i < s_BuildData.trackList.Count; i++) + s_BuildData.clipList.AddRange(s_BuildData.trackList[i].clips); + +#if UNITY_EDITOR + bool applyWarning = false; + for (int i = 0; i < s_BuildData.trackList.Count; i++) + applyWarning |= i > 0 && s_BuildData.trackList[i].hasCurves; + + if (applyWarning) + Debug.LogWarning("A layered track contains animated fields, but no layer mixer has been provided. Animated fields on layers will be ignored. Override CreateLayerMixer in " + s_BuildData.trackList[0].GetType().Name + " and return a valid playable to support animated fields on layered tracks."); +#endif + // compile all the clips into a single mixer + return CompileClips(graph, go, s_BuildData.clipList, tree); + } + + internal void ConfigureTrackAnimation(IntervalTree<RuntimeElement> tree, GameObject go, Playable blend) + { + if (!hasCurves) + return; + + blend.SetAnimatedProperties(m_Curves); + tree.Add(new InfiniteRuntimeClip(blend)); + + if (OnTrackAnimationPlayableCreate != null) + OnTrackAnimationPlayableCreate.Invoke(this, go, blend); + } + + // sorts clips by start time + internal void SortClips() + { + var clipsAsArray = clips; // will alloc + if (!m_CacheSorted) + { + Array.Sort(clips, (clip1, clip2) => clip1.start.CompareTo(clip2.start)); + m_CacheSorted = true; + } + } + + // clears the clips after a clone + internal void ClearClipsInternal() + { + m_Clips = new List<TimelineClip>(); + m_ClipsCache = null; + } + + internal void ClearSubTracksInternal() + { + m_Children = new List<ScriptableObject>(); + Invalidate(); + } + + // called by an owned clip when it moves + internal void OnClipMove() + { + m_CacheSorted = false; + } + + internal TimelineClip CreateNewClipContainerInternal() + { + var clipContainer = new TimelineClip(this); + clipContainer.asset = null; + + // position clip at end of sequence + var newClipStart = 0.0; + for (var a = 0; a < m_Clips.Count - 1; a++) + { + var clipDuration = m_Clips[a].duration; + if (double.IsInfinity(clipDuration)) + clipDuration = TimelineClip.kDefaultClipDurationInSeconds; + newClipStart = Math.Max(newClipStart, m_Clips[a].start + clipDuration); + } + + clipContainer.mixInCurve = AnimationCurve.EaseInOut(0, 0, 1, 1); + clipContainer.mixOutCurve = AnimationCurve.EaseInOut(0, 1, 1, 0); + clipContainer.start = newClipStart; + clipContainer.duration = TimelineClip.kDefaultClipDurationInSeconds; + clipContainer.displayName = "untitled"; + return clipContainer; + } + + internal void AddChild(TrackAsset child) + { + if (child == null) + return; + + m_Children.Add(child); + child.parent = this; + Invalidate(); + } + + internal void MoveLastTrackBefore(TrackAsset asset) + { + if (m_Children == null || m_Children.Count < 2 || asset == null) + return; + + var lastTrack = m_Children[m_Children.Count - 1]; + if (lastTrack == asset) + return; + + for (int i = 0; i < m_Children.Count - 1; i++) + { + if (m_Children[i] == asset) + { + for (int j = m_Children.Count - 1; j > i; j--) + m_Children[j] = m_Children[j - 1]; + m_Children[i] = lastTrack; + Invalidate(); + break; + } + } + } + + internal bool RemoveSubTrack(TrackAsset child) + { + if (m_Children.Remove(child)) + { + Invalidate(); + child.parent = null; + return true; + } + return false; + } + + internal void RemoveClip(TimelineClip clip) + { + m_Clips.Remove(clip); + m_ClipsCache = null; + } + + // Is this track compilable for the sequence + // calculate the time interval that this track will be evaluated in. + internal virtual void GetEvaluationTime(out double outStart, out double outDuration) + { + outStart = double.PositiveInfinity; + var outEnd = double.NegativeInfinity; + + if (hasCurves) + { + outStart = 0.0; + outEnd = TimeUtility.GetAnimationClipLength(curves); + } + + foreach (var clip in clips) + { + outStart = Math.Min(clip.start, outStart); + outEnd = Math.Max(clip.end, outEnd); + } + + if (HasNotifications()) + { + var notificationDuration = GetNotificationDuration(); + outStart = Math.Min(notificationDuration, outStart); + outEnd = Math.Max(notificationDuration, outEnd); + } + + if (double.IsInfinity(outStart) || double.IsInfinity(outEnd)) + outStart = outDuration = 0.0; + else + outDuration = outEnd - outStart; + } + + // calculate the time interval that the sequence will use to determine length. + // by default this is the same as the evaluation, but subclasses can have different + // behaviour + internal virtual void GetSequenceTime(out double outStart, out double outDuration) + { + GetEvaluationTime(out outStart, out outDuration); + } + + /// <summary> + /// Called by the Timeline Editor to gather properties requiring preview. + /// </summary> + /// <param name="director">The PlayableDirector invoking the preview</param> + /// <param name="driver">PropertyCollector used to gather previewable properties</param> + public virtual void GatherProperties(PlayableDirector director, IPropertyCollector driver) + { + // only push on game objects if there is a binding. Subtracks + // will use objects on the stack + var gameObject = GetGameObjectBinding(director); + if (gameObject != null) + driver.PushActiveGameObject(gameObject); + + if (hasCurves) + driver.AddObjectProperties(this, m_Curves); + + foreach (var clip in clips) + { + if (clip.curves != null && clip.asset != null) + driver.AddObjectProperties(clip.asset, clip.curves); + + IPropertyPreview modifier = clip.asset as IPropertyPreview; + if (modifier != null) + modifier.GatherProperties(director, driver); + } + + foreach (var subtrack in GetChildTracks()) + { + if (subtrack != null) + subtrack.GatherProperties(director, driver); + } + + if (gameObject != null) + driver.PopActiveGameObject(); + } + + internal GameObject GetGameObjectBinding(PlayableDirector director) + { + if (director == null) + return null; + + var binding = director.GetGenericBinding(this); + + var gameObject = binding as GameObject; + if (gameObject != null) + return gameObject; + + var comp = binding as Component; + if (comp != null) + return comp.gameObject; + + return null; + } + + internal bool ValidateClipType(Type clipType) + { + var attrs = GetType().GetCustomAttributes(typeof(TrackClipTypeAttribute), true); + for (var c = 0; c < attrs.Length; ++c) + { + var attr = (TrackClipTypeAttribute)attrs[c]; + if (attr.inspectedType.IsAssignableFrom(clipType)) + return true; + } + + // special case for playable tracks, they accept all clips (in the runtime) + return typeof(PlayableTrack).IsAssignableFrom(GetType()) && + typeof(IPlayableAsset).IsAssignableFrom(clipType) && + typeof(ScriptableObject).IsAssignableFrom(clipType); + } + + /// <summary> + /// Called when a clip is created on a track. + /// </summary> + /// <param name="clip">The timeline clip added to this track</param> + /// <remarks>Use this method to set default values on a timeline clip, or it's PlayableAsset.</remarks> + protected virtual void OnCreateClip(TimelineClip clip) {} + + void UpdateDuration() + { + // check if something changed in the clips that require a re-calculation of the evaluation times. + var itemsHash = CalculateItemsHash(); + if (itemsHash == m_ItemsHash) + return; + m_ItemsHash = itemsHash; + + double trackStart, trackDuration; + GetSequenceTime(out trackStart, out trackDuration); + + m_Start = (DiscreteTime)trackStart; + m_End = (DiscreteTime)(trackStart + trackDuration); + + // calculate the extrapolations time. + // TODO Extrapolation time should probably be extracted from the SequenceClip so only a track is aware of it. + this.CalculateExtrapolationTimes(); + } + + protected internal virtual int CalculateItemsHash() + { + return HashUtility.CombineHash(GetClipsHash(), GetAnimationClipHash(m_Curves), GetTimeRangeHash()); + } + + /// <summary> + /// Constructs a Playable from a TimelineClip. + /// </summary> + /// <param name="graph">PlayableGraph that will own the playable.</param> + /// <param name="gameObject">The GameObject that builds the PlayableGraph.</param> + /// <param name="clip">The TimelineClip to construct a playable for.</param> + /// <returns>A playable that will be set as an input to the Track Mixer playable, or Playable.Null if the clip does not have a valid PlayableAsset</returns> + /// <exception cref="ArgumentException">Thrown if the specified PlayableGraph is not valid.</exception> + /// <exception cref="ArgumentNullException">Thrown if the specified TimelineClip is not valid.</exception> + /// <remarks> + /// By default, this method invokes Playable.CreatePlayable, sets animated properties, and sets the speed of the created playable. Override this method to change this default implementation. + /// </remarks> + protected virtual Playable CreatePlayable(PlayableGraph graph, GameObject gameObject, TimelineClip clip) + { + if (!graph.IsValid()) + throw new ArgumentException("graph must be a valid PlayableGraph"); + if (clip == null) + throw new ArgumentNullException("clip"); + + var asset = clip.asset as IPlayableAsset; + if (asset != null) + { + var handle = asset.CreatePlayable(graph, gameObject); + if (handle.IsValid()) + { + handle.SetAnimatedProperties(clip.curves); + handle.SetSpeed(clip.timeScale); + if (OnClipPlayableCreate != null) + OnClipPlayableCreate(clip, gameObject, handle); + } + return handle; + } + return Playable.Null; + } + + internal void Invalidate() + { + m_ChildTrackCache = null; + var timeline = timelineAsset; + if (timeline != null) + { + timeline.Invalidate(); + } + } + + internal double GetNotificationDuration() + { + if (!supportsNotifications) + { + return 0; + } + + var maxTime = 0.0; + foreach (var marker in GetMarkers()) + { + if (!(marker is INotification)) + { + continue; + } + maxTime = Math.Max(maxTime, marker.time); + } + + return maxTime; + } + + internal virtual bool CanCompileClips() + { + return hasClips || hasCurves; + } + + internal bool IsCompilable() + { + var isContainer = typeof(GroupTrack).IsAssignableFrom(GetType()); + + if (isContainer) + return false; + + var ret = !mutedInHierarchy && (CanCompileClips() || CanCompileNotifications()); + if (!ret) + { + foreach (var t in GetChildTracks()) + { + if (t.IsCompilable()) + return true; + } + } + + return ret; + } + + private void UpdateChildTrackCache() + { + if (m_ChildTrackCache == null) + { + if (m_Children == null || m_Children.Count == 0) + m_ChildTrackCache = s_EmptyCache; + else + { + var childTracks = new List<TrackAsset>(m_Children.Count); + for (int i = 0; i < m_Children.Count; i++) + { + var subTrack = m_Children[i] as TrackAsset; + if (subTrack != null) + childTracks.Add(subTrack); + } + m_ChildTrackCache = childTracks; + } + } + } + + internal virtual int Hash() + { + return clips.Length + (m_Markers.Count << 16); + } + + int GetClipsHash() + { + var hash = 0; + foreach (var clip in m_Clips) + { + hash = hash.CombineHash(clip.Hash()); + } + return hash; + } + + protected static int GetAnimationClipHash(AnimationClip clip) + { + var hash = 0; + if (clip != null && !clip.empty) + hash = hash.CombineHash(clip.frameRate.GetHashCode()) + .CombineHash(clip.length.GetHashCode()); + + return hash; + } + + bool HasNotifications() + { + return m_Markers.HasNotifications(); + } + + bool CanCompileNotifications() + { + return supportsNotifications && m_Markers.HasNotifications(); + } + + bool CanCompileClipsRecursive() + { + if (CanCompileClips()) + return true; + foreach (var track in GetChildTracks()) + { + if (track.CanCompileClipsRecursive()) + return true; + } + + return false; + } + } +} |
