797 lines
30 KiB
C#
797 lines
30 KiB
C#
#if VFX_HAS_TIMELINE
|
|
using UnityEngine.Playables;
|
|
using System.Collections.Generic;
|
|
using System;
|
|
|
|
namespace UnityEngine.VFX
|
|
{
|
|
#if UNITY_EDITOR
|
|
class VisualEffectControlErrorHelper
|
|
{
|
|
public struct MaxScrubbingWarning
|
|
{
|
|
public float requestedTime;
|
|
public float fixedTimeStep;
|
|
public VisualEffectControlTrackController controller;
|
|
}
|
|
|
|
private void UpdateConflictingControlTrack()
|
|
{
|
|
//Detect potential issue with multiple track controlling the same vfx with some scrubbing
|
|
m_ConflictingControlTrack.Clear();
|
|
|
|
var controlTrackByTarget = new Dictionary<VisualEffect, List<VisualEffectControlTrackController>>();
|
|
foreach (var controlTrack in m_RegisteredControlTrack)
|
|
{
|
|
var target = controlTrack.GetTarget();
|
|
if (!controlTrackByTarget.TryGetValue(controlTrack.GetTarget(), out var listOfTrack))
|
|
{
|
|
listOfTrack = new List<VisualEffectControlTrackController>();
|
|
controlTrackByTarget.Add(target, listOfTrack);
|
|
}
|
|
listOfTrack.Add(controlTrack);
|
|
}
|
|
|
|
foreach (var group in controlTrackByTarget)
|
|
{
|
|
if (group.Value.Count > 1)
|
|
{
|
|
foreach (var track in group.Value)
|
|
{
|
|
if (track.GetScrubbing())
|
|
{
|
|
m_ConflictingControlTrack.Add(group.Value.ToArray());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void RegisterControlTrack(VisualEffectControlTrackController controller)
|
|
{
|
|
m_RegisteredControlTrack.Add(controller);
|
|
UpdateConflictingControlTrack();
|
|
}
|
|
|
|
public void UnregisterControlTrack(VisualEffectControlTrackController controller)
|
|
{
|
|
UnregisterScrubbingWarning(controller);
|
|
m_RegisteredControlTrack.RemoveAll(o => o == controller);
|
|
UpdateConflictingControlTrack();
|
|
}
|
|
|
|
public void RegisterScrubbingWarning(VisualEffectControlTrackController controller, float requestedTime, float fixedTimeStep)
|
|
{
|
|
UnregisterScrubbingWarning(controller);
|
|
m_ScrubbingWarnings.Add(new MaxScrubbingWarning()
|
|
{
|
|
controller = controller,
|
|
requestedTime = requestedTime,
|
|
fixedTimeStep = fixedTimeStep
|
|
});
|
|
}
|
|
|
|
public void UnregisterScrubbingWarning(VisualEffectControlTrackController controller)
|
|
{
|
|
m_ScrubbingWarnings.RemoveAll(o => o.controller == controller);
|
|
}
|
|
|
|
public IEnumerable<VisualEffectControlTrackController[]> GetConflictingControlTrack()
|
|
{
|
|
return m_ConflictingControlTrack;
|
|
}
|
|
|
|
public IEnumerable<MaxScrubbingWarning> GetMaxScrubbingWarnings()
|
|
{
|
|
return m_ScrubbingWarnings;
|
|
}
|
|
|
|
public bool AnyError()
|
|
{
|
|
return m_ScrubbingWarnings.Count > 0 || m_ConflictingControlTrack.Count > 0;
|
|
}
|
|
|
|
private static VisualEffectControlErrorHelper m_Instance = new VisualEffectControlErrorHelper();
|
|
public static VisualEffectControlErrorHelper instance => m_Instance;
|
|
List<VisualEffectControlTrackController> m_RegisteredControlTrack = new List<VisualEffectControlTrackController>();
|
|
List<VisualEffectControlTrackController[]> m_ConflictingControlTrack = new List<VisualEffectControlTrackController[]>();
|
|
List<MaxScrubbingWarning> m_ScrubbingWarnings = new List<MaxScrubbingWarning>();
|
|
}
|
|
#endif
|
|
|
|
class VisualEffectControlTrackController
|
|
{
|
|
struct Event
|
|
{
|
|
public int nameId;
|
|
public VFXEventAttribute attribute;
|
|
public double time;
|
|
|
|
public enum ClipType
|
|
{
|
|
None,
|
|
Enter,
|
|
Exit
|
|
}
|
|
public int clipIndex;
|
|
public ClipType clipType;
|
|
}
|
|
|
|
struct Clip
|
|
{
|
|
public int enter;
|
|
public int exit;
|
|
}
|
|
|
|
struct Chunk
|
|
{
|
|
public bool scrubbing;
|
|
public bool reinitEnter;
|
|
public bool reinitExit;
|
|
public uint startSeed;
|
|
|
|
public double begin;
|
|
public double end;
|
|
|
|
public uint prewarmCount;
|
|
public float prewarmDeltaTime;
|
|
public double prewarmOffset;
|
|
public int prewarmEvent;
|
|
|
|
public Event[] events;
|
|
public Clip[] clips;
|
|
}
|
|
|
|
const int kErrorIndex = int.MinValue;
|
|
int m_LastChunk = kErrorIndex;
|
|
int m_LastEvent = kErrorIndex;
|
|
double m_LastPlayableTime = double.MinValue;
|
|
List<int> m_EventListIndexCache = new ();
|
|
|
|
#if UNITY_EDITOR
|
|
bool[] m_ClipState;
|
|
bool m_HasScrubbingWarnings;
|
|
bool m_Scrubbing;
|
|
PlayableDirector m_Director;
|
|
VisualEffectControlTrack m_Track;
|
|
|
|
public bool GetScrubbing()
|
|
{
|
|
return m_Scrubbing;
|
|
}
|
|
|
|
public VisualEffect GetTarget()
|
|
{
|
|
return m_Target;
|
|
}
|
|
|
|
public VisualEffectControlTrack GetTrack()
|
|
{
|
|
return m_Track;
|
|
}
|
|
|
|
public PlayableDirector GetDirector()
|
|
{
|
|
return m_Director;
|
|
}
|
|
#endif
|
|
VisualEffect m_Target;
|
|
bool m_BackupReseedOnPlay;
|
|
uint m_BackupStartSeed;
|
|
Chunk[] m_Chunks;
|
|
|
|
private void OnEnterChunk(int currentChunk)
|
|
{
|
|
var chunk = m_Chunks[currentChunk];
|
|
#if UNITY_EDITOR
|
|
if (!chunk.scrubbing)
|
|
m_ClipState = new bool[chunk.clips.Length];
|
|
#endif
|
|
if (chunk.reinitEnter)
|
|
{
|
|
m_Target.resetSeedOnPlay = false;
|
|
m_Target.startSeed = chunk.startSeed;
|
|
m_Target.Reinit(false);
|
|
if (chunk.prewarmCount != 0u)
|
|
{
|
|
m_Target.SendEvent(chunk.prewarmEvent);
|
|
m_Target.Simulate(chunk.prewarmDeltaTime, chunk.prewarmCount);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnLeaveChunk(int previousChunkIndex, bool leavingGoingBeforeClip)
|
|
{
|
|
var previousChunk = m_Chunks[previousChunkIndex];
|
|
|
|
if (previousChunk.reinitExit)
|
|
{
|
|
m_Target.Reinit(false);
|
|
}
|
|
else
|
|
{
|
|
#if UNITY_EDITOR
|
|
if (previousChunk.scrubbing)
|
|
throw new InvalidOperationException();
|
|
#endif
|
|
//Using infinity as virtual limit to force include events where time is exactly 0 or duration.
|
|
ProcessNoScrubbingEvents(previousChunk, m_LastPlayableTime, leavingGoingBeforeClip ? double.NegativeInfinity : double.PositiveInfinity);
|
|
}
|
|
|
|
RestoreVFXState(previousChunk.scrubbing, previousChunk.reinitEnter);
|
|
|
|
#if UNITY_EDITOR
|
|
m_ClipState = null;
|
|
#endif
|
|
}
|
|
|
|
bool IsTimeInChunk(double time, int index)
|
|
{
|
|
var chunk = m_Chunks[index];
|
|
return chunk.begin <= time && time < chunk.end;
|
|
}
|
|
|
|
private static readonly double kEpsilonEvent = 1e-12;
|
|
|
|
public void Update(double playableTime, float deltaTime)
|
|
{
|
|
#if UNITY_EDITOR
|
|
if (m_HasScrubbingWarnings)
|
|
{
|
|
VisualEffectControlErrorHelper.instance.UnregisterScrubbingWarning(this);
|
|
m_HasScrubbingWarnings = false;
|
|
}
|
|
#endif
|
|
var playableTimeForEvent = playableTime + kEpsilonEvent;
|
|
|
|
var paused = deltaTime == 0.0;
|
|
var currentChunkIndex = kErrorIndex;
|
|
if (m_LastChunk != currentChunkIndex)
|
|
{
|
|
if (IsTimeInChunk(playableTime, m_LastChunk))
|
|
currentChunkIndex = m_LastChunk;
|
|
}
|
|
|
|
if (currentChunkIndex == kErrorIndex)
|
|
{
|
|
var startIndex = m_LastChunk != kErrorIndex ? (uint)m_LastEvent : 0u;
|
|
for (uint i = startIndex; i < startIndex + m_Chunks.Length; i++)
|
|
{
|
|
var actualIndex = (int)(i % m_Chunks.Length);
|
|
if (IsTimeInChunk(playableTime, actualIndex))
|
|
{
|
|
currentChunkIndex = actualIndex;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
var firstFrameOfChunk = false;
|
|
if (m_LastChunk != currentChunkIndex)
|
|
{
|
|
if (m_LastChunk != kErrorIndex)
|
|
{
|
|
var before = playableTime < m_Chunks[m_LastChunk].begin;
|
|
OnLeaveChunk(m_LastChunk, before);
|
|
}
|
|
if (currentChunkIndex != kErrorIndex)
|
|
{
|
|
OnEnterChunk(currentChunkIndex);
|
|
firstFrameOfChunk = true;
|
|
}
|
|
|
|
m_LastChunk = currentChunkIndex;
|
|
m_LastEvent = kErrorIndex;
|
|
}
|
|
|
|
if (currentChunkIndex != kErrorIndex)
|
|
{
|
|
var chunk = m_Chunks[currentChunkIndex];
|
|
if (chunk.scrubbing)
|
|
{
|
|
m_Target.pause = paused;
|
|
var actualCurrentTime = chunk.begin + m_Target.time;
|
|
if (!firstFrameOfChunk)
|
|
actualCurrentTime -= chunk.prewarmOffset;
|
|
|
|
var playingBackward = playableTime < m_LastPlayableTime;
|
|
if (!playingBackward)
|
|
{
|
|
if (Math.Abs(m_LastPlayableTime - actualCurrentTime) < VFXManager.maxDeltaTime)
|
|
{
|
|
//Remove the float part from VFX and only keep double precision
|
|
actualCurrentTime = m_LastPlayableTime;
|
|
}
|
|
else
|
|
{
|
|
//VFX is too late on timeline (or a bit ahead), we will have to launch simulate
|
|
//Warning, in that case, event could have been already sent
|
|
//m_LastEvent status prevents sending twice the same event
|
|
}
|
|
}
|
|
else
|
|
{
|
|
actualCurrentTime = chunk.begin;
|
|
m_LastEvent = kErrorIndex;
|
|
OnEnterChunk(m_LastChunk);
|
|
}
|
|
|
|
double expectedCurrentTime;
|
|
if (paused)
|
|
expectedCurrentTime = playableTime;
|
|
else
|
|
expectedCurrentTime = playableTime - VFXManager.fixedTimeStep;
|
|
|
|
//Sending missed event (in case of VFX ahead)
|
|
if (m_LastPlayableTime < actualCurrentTime)
|
|
{
|
|
var eventList = m_EventListIndexCache;
|
|
GetEventsIndex(chunk, m_LastPlayableTime, actualCurrentTime, m_LastEvent, eventList);
|
|
foreach (var itEvent in eventList)
|
|
ProcessEvent(itEvent, chunk);
|
|
}
|
|
|
|
if (actualCurrentTime < expectedCurrentTime)
|
|
{
|
|
//Process adjustment if actualCurrentTime < expectedCurrentTime (heavy loop with potential Simulate)
|
|
var eventList = m_EventListIndexCache;
|
|
GetEventsIndex(chunk, actualCurrentTime, expectedCurrentTime, m_LastEvent, eventList);
|
|
var eventCount = eventList.Count;
|
|
var nextEvent = 0;
|
|
|
|
var maxScrubTime = VFXManager.maxScrubTime;
|
|
var fixedStep = VFXManager.maxDeltaTime;
|
|
if (expectedCurrentTime - actualCurrentTime > maxScrubTime)
|
|
{
|
|
//Choose a bigger time step to reach the actual expected time
|
|
fixedStep = (float)((expectedCurrentTime - actualCurrentTime) * (double)VFXManager.maxDeltaTime / (double)maxScrubTime);
|
|
#if UNITY_EDITOR
|
|
VisualEffectControlErrorHelper.instance.RegisterScrubbingWarning(this, (float)(expectedCurrentTime - actualCurrentTime), fixedStep);
|
|
m_HasScrubbingWarnings = true;
|
|
#endif
|
|
}
|
|
|
|
while (actualCurrentTime < expectedCurrentTime)
|
|
{
|
|
var currentEventIndex = kErrorIndex;
|
|
uint currentStepCount;
|
|
if (nextEvent < eventCount)
|
|
{
|
|
currentEventIndex = eventList[nextEvent++];
|
|
var currentEvent = chunk.events[currentEventIndex];
|
|
currentStepCount = (uint)((currentEvent.time - actualCurrentTime) / fixedStep);
|
|
}
|
|
else
|
|
{
|
|
currentStepCount = (uint)((expectedCurrentTime - actualCurrentTime) / fixedStep);
|
|
if (currentStepCount == 0)
|
|
{
|
|
//We reached the maximum precision according to the current fixedStep & no more event
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (currentStepCount != 0)
|
|
{
|
|
m_Target.Simulate((float)fixedStep, currentStepCount);
|
|
actualCurrentTime += fixedStep * currentStepCount;
|
|
}
|
|
ProcessEvent(currentEventIndex, chunk);
|
|
}
|
|
}
|
|
|
|
//Sending incoming event (considering an epsilon for current frame events)
|
|
if (actualCurrentTime < playableTimeForEvent)
|
|
{
|
|
var eventList = m_EventListIndexCache;
|
|
GetEventsIndex(chunk, actualCurrentTime, playableTimeForEvent, m_LastEvent, eventList);
|
|
foreach (var itEvent in eventList)
|
|
ProcessEvent(itEvent, chunk);
|
|
}
|
|
}
|
|
else //No scrubbing, only update events
|
|
{
|
|
m_Target.pause = false;
|
|
ProcessNoScrubbingEvents(chunk, m_LastPlayableTime, playableTimeForEvent);
|
|
}
|
|
}
|
|
m_LastPlayableTime = playableTime;
|
|
}
|
|
|
|
void ProcessNoScrubbingEvents(Chunk chunk, double oldTime, double newTime)
|
|
{
|
|
#if UNITY_EDITOR
|
|
if (chunk.scrubbing)
|
|
throw new InvalidOperationException();
|
|
#endif
|
|
if (newTime < oldTime) // == playingBackward
|
|
{
|
|
var eventBehind = m_EventListIndexCache;
|
|
GetEventsIndex(chunk, newTime, oldTime, kErrorIndex, eventBehind);
|
|
if (eventBehind.Count > 0)
|
|
{
|
|
for (int index = eventBehind.Count - 1; index >= 0; index--)
|
|
{
|
|
var itEvent = eventBehind[index];
|
|
var currentEvent = chunk.events[itEvent];
|
|
if (currentEvent.clipType == Event.ClipType.Enter)
|
|
{
|
|
ProcessEvent(chunk.clips[currentEvent.clipIndex].exit, chunk);
|
|
}
|
|
else if (currentEvent.clipType == Event.ClipType.Exit)
|
|
{
|
|
ProcessEvent(chunk.clips[currentEvent.clipIndex].enter, chunk);
|
|
}
|
|
//else: Ignore, we aren't playing single event backward
|
|
}
|
|
|
|
//The last event will be always invalid in case of scrubbing backward
|
|
m_LastEvent = kErrorIndex;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var eventAhead = m_EventListIndexCache;
|
|
GetEventsIndex(chunk, oldTime, newTime, m_LastEvent, eventAhead);
|
|
foreach (var itEvent in eventAhead)
|
|
ProcessEvent(itEvent, chunk);
|
|
}
|
|
}
|
|
|
|
void ProcessEvent(int eventIndex, Chunk currentChunk)
|
|
{
|
|
if (eventIndex == kErrorIndex)
|
|
return;
|
|
|
|
m_LastEvent = eventIndex;
|
|
var currentEvent = currentChunk.events[eventIndex];
|
|
|
|
#if UNITY_EDITOR
|
|
if (currentEvent.clipType == Event.ClipType.Enter)
|
|
{
|
|
if (m_ClipState[currentEvent.clipIndex])
|
|
throw new InvalidOperationException();
|
|
|
|
m_ClipState[currentEvent.clipIndex] = true;
|
|
}
|
|
else if (currentEvent.clipType == Event.ClipType.Exit)
|
|
{
|
|
if (!m_ClipState[currentEvent.clipIndex])
|
|
throw new InvalidOperationException();
|
|
|
|
m_ClipState[currentEvent.clipIndex] = false;
|
|
}
|
|
#endif
|
|
m_Target.SendEvent(currentEvent.nameId, currentEvent.attribute);
|
|
}
|
|
|
|
static void GetEventsIndex(Chunk chunk, double minTime, double maxTime, int lastIndex, List<int> eventListIndex)
|
|
{
|
|
eventListIndex.Clear();
|
|
|
|
var startIndex = lastIndex == kErrorIndex ? 0 : lastIndex + 1;
|
|
for (int i = startIndex; i < chunk.events.Length; ++i)
|
|
{
|
|
var currentEvent = chunk.events[i];
|
|
//We are retrieving events between [minTime, maxTime[
|
|
//N.B.: maxTime could be offset by an epsilon to cover matching event frame (e.g: event sent at frame 0)
|
|
if (currentEvent.time >= maxTime)
|
|
break;
|
|
|
|
if (minTime <= currentEvent.time)
|
|
eventListIndex.Add(i);
|
|
}
|
|
}
|
|
|
|
static VFXEventAttribute ComputeAttribute(VisualEffect vfx, EventAttributes attributes)
|
|
{
|
|
|
|
if (attributes.content == null)
|
|
return null;
|
|
|
|
var vfxAttribute = vfx.CreateVFXEventAttribute();
|
|
bool hasApply = false;
|
|
foreach (var content in attributes.content)
|
|
{
|
|
if (content?.ApplyToVFX(vfxAttribute) == true)
|
|
hasApply = true;
|
|
}
|
|
|
|
//We didn't setup any vfxEventAttribute, ignoring the event payload
|
|
return hasApply ? vfxAttribute : null;
|
|
}
|
|
|
|
static IEnumerable<Event> ComputeRuntimeEvent(VisualEffectControlPlayableBehaviour behavior, VisualEffect vfx)
|
|
{
|
|
var events = VFXTimeSpaceHelper.GetEventNormalizedSpace(PlayableTimeSpace.Absolute, behavior);
|
|
foreach (var itEvent in events)
|
|
{
|
|
//Apply clamping on the fly
|
|
var absoluteTime = Math.Max(behavior.clipStart, Math.Min(behavior.clipEnd, itEvent.time));
|
|
|
|
yield return new Event()
|
|
{
|
|
attribute = ComputeAttribute(vfx, itEvent.eventAttributes),
|
|
nameId = itEvent.name,
|
|
time = absoluteTime,
|
|
clipIndex = -1,
|
|
clipType = Event.ClipType.None
|
|
};
|
|
}
|
|
}
|
|
|
|
class VisualEffectControlPlayableBehaviourComparer : IComparer<VisualEffectControlPlayableBehaviour>
|
|
{
|
|
public int Compare(VisualEffectControlPlayableBehaviour x, VisualEffectControlPlayableBehaviour y)
|
|
{
|
|
return x.clipStart.CompareTo(y.clipStart);
|
|
}
|
|
}
|
|
|
|
public void RestoreVFXState(bool restorePause = true, bool restoreSeedState = true)
|
|
{
|
|
//Target could have been destroyed
|
|
if (m_Target == null)
|
|
return;
|
|
|
|
if (restorePause)
|
|
m_Target.pause = false;
|
|
|
|
if (restoreSeedState)
|
|
{
|
|
m_Target.startSeed = m_BackupStartSeed;
|
|
m_Target.resetSeedOnPlay = m_BackupReseedOnPlay;
|
|
}
|
|
}
|
|
|
|
public void Init(Playable playable, VisualEffect vfx, VisualEffectControlTrack parentTrack)
|
|
{
|
|
m_Target = vfx;
|
|
#if UNITY_EDITOR
|
|
m_Director = playable.GetGraph().GetResolver() as PlayableDirector;
|
|
m_Track = parentTrack;
|
|
#endif
|
|
m_BackupStartSeed = m_Target.startSeed;
|
|
m_BackupReseedOnPlay = m_Target.resetSeedOnPlay;
|
|
|
|
var chunks = new Stack<(Chunk actual, List<Event> tempEvents, List<Clip> tempClips)>();
|
|
int inputCount = playable.GetInputCount();
|
|
|
|
var playableBehaviors = new List<VisualEffectControlPlayableBehaviour>();
|
|
for (int i = 0; i < inputCount; ++i)
|
|
{
|
|
var inputPlayable = playable.GetInput(i);
|
|
if (inputPlayable.GetPlayableType() != typeof(VisualEffectControlPlayableBehaviour))
|
|
continue;
|
|
|
|
var inputVFXPlayable = (ScriptPlayable<VisualEffectControlPlayableBehaviour>)inputPlayable;
|
|
var inputBehavior = inputVFXPlayable.GetBehaviour();
|
|
if (inputBehavior != null)
|
|
playableBehaviors.Add(inputBehavior);
|
|
}
|
|
|
|
playableBehaviors.Sort(new VisualEffectControlPlayableBehaviourComparer());
|
|
foreach (var inputBehavior in playableBehaviors)
|
|
{
|
|
if (chunks.Count == 0
|
|
|| inputBehavior.clipStart > chunks.Peek().actual.end
|
|
|| inputBehavior.scrubbing != chunks.Peek().actual.scrubbing
|
|
|| (!inputBehavior.scrubbing && (inputBehavior.reinitEnter || chunks.Peek().actual.reinitExit))
|
|
|| inputBehavior.startSeed != chunks.Peek().actual.startSeed
|
|
|| inputBehavior.prewarmStepCount != 0u)
|
|
{
|
|
chunks.Push(new ()
|
|
{
|
|
actual = new Chunk()
|
|
{
|
|
begin = inputBehavior.clipStart,
|
|
scrubbing = inputBehavior.scrubbing,
|
|
startSeed = inputBehavior.startSeed,
|
|
reinitEnter = inputBehavior.reinitEnter,
|
|
reinitExit = inputBehavior.reinitExit,
|
|
|
|
prewarmCount = inputBehavior.prewarmStepCount,
|
|
prewarmDeltaTime = inputBehavior.prewarmDeltaTime,
|
|
prewarmEvent = inputBehavior.prewarmEvent ?? 0,
|
|
prewarmOffset = (double)inputBehavior.prewarmStepCount * inputBehavior.prewarmDeltaTime
|
|
|
|
//Event & Clip are null at this stage, using temporary list during initialization
|
|
},
|
|
|
|
tempEvents = new List<Event>(),
|
|
tempClips = new List<Clip>(),
|
|
});
|
|
}
|
|
|
|
var currentChunk = chunks.Pop();
|
|
currentChunk.actual.end = inputBehavior.clipEnd;
|
|
var currentsEvents = new List<Event>(ComputeRuntimeEvent(inputBehavior, vfx));
|
|
|
|
if (!currentChunk.actual.scrubbing)
|
|
{
|
|
var sortedEventWithSourceIndex = new List<(Event evt, int sourceIndex)>();
|
|
for (var sourceIndex = 0; sourceIndex < currentsEvents.Count; sourceIndex++)
|
|
sortedEventWithSourceIndex.Add((currentsEvents[sourceIndex], sourceIndex));
|
|
sortedEventWithSourceIndex.Sort((x, y) => x.evt.time.CompareTo(y.evt.time));
|
|
|
|
var newClips = new Clip[inputBehavior.clipEventsCount];
|
|
var newEvents = new List<Event>();
|
|
for (int actualIndex = 0; actualIndex < sortedEventWithSourceIndex.Count; actualIndex++)
|
|
{
|
|
var newEvent = sortedEventWithSourceIndex[actualIndex].evt;
|
|
var sourceIndex = sortedEventWithSourceIndex[actualIndex].sourceIndex;
|
|
if (sourceIndex < inputBehavior.clipEventsCount * 2)
|
|
{
|
|
var actualSortedClipIndex = currentChunk.tempEvents.Count + actualIndex;
|
|
var localClipIndex = sourceIndex / 2;
|
|
newEvent.clipIndex = localClipIndex + currentChunk.tempClips.Count;
|
|
if (sourceIndex % 2 == 0)
|
|
{
|
|
newEvent.clipType = Event.ClipType.Enter;
|
|
newClips[localClipIndex].enter = actualSortedClipIndex;
|
|
}
|
|
else
|
|
{
|
|
newEvent.clipType = Event.ClipType.Exit;
|
|
newClips[localClipIndex].exit = actualSortedClipIndex;
|
|
}
|
|
newEvents.Add(newEvent);
|
|
}
|
|
else //Not a clip event
|
|
{
|
|
newEvents.Add(newEvent);
|
|
}
|
|
}
|
|
|
|
currentChunk.tempClips.AddRange(newClips);
|
|
currentChunk.tempEvents.AddRange(newEvents);
|
|
}
|
|
else
|
|
{
|
|
#if UNITY_EDITOR
|
|
m_Scrubbing = true;
|
|
#endif
|
|
//No need to compute clip information
|
|
currentsEvents.Sort((x, y) => x.time.CompareTo(y.time));
|
|
currentChunk.tempEvents.AddRange(currentsEvents);
|
|
}
|
|
|
|
chunks.Push(currentChunk);
|
|
}
|
|
|
|
m_Chunks = new Chunk[chunks.Count];
|
|
for (int i = 0; i < m_Chunks.Length; ++i)
|
|
{
|
|
var tempChunk = chunks.Pop();
|
|
m_Chunks[i] = tempChunk.actual;
|
|
m_Chunks[i].clips = tempChunk.tempClips.ToArray();
|
|
m_Chunks[i].events = tempChunk.tempEvents.ToArray();
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
VisualEffectControlErrorHelper.instance.RegisterControlTrack(this);
|
|
#endif
|
|
}
|
|
|
|
public void Release()
|
|
{
|
|
#if UNITY_EDITOR
|
|
VisualEffectControlErrorHelper.instance.UnregisterControlTrack(this);
|
|
#endif
|
|
RestoreVFXState();
|
|
}
|
|
}
|
|
|
|
class VisualEffectControlTrackMixerBehaviour : PlayableBehaviour
|
|
{
|
|
VisualEffectControlTrackController m_ScrubbingCacheHelper;
|
|
#if UNITY_EDITOR
|
|
VisualEffectControlTrack m_ParentTrack;
|
|
#endif
|
|
VisualEffect m_Target;
|
|
bool m_ReinitWithBinding;
|
|
bool m_ReinitWithUnbinding;
|
|
|
|
public void Init(VisualEffectControlTrack parentTrack, bool reinitWithBinding, bool reinitWithUnbinding)
|
|
{
|
|
#if UNITY_EDITOR
|
|
m_ParentTrack = parentTrack;
|
|
#endif
|
|
m_ReinitWithBinding = reinitWithBinding;
|
|
m_ReinitWithUnbinding = reinitWithUnbinding;
|
|
}
|
|
|
|
private void ApplyFrame(Playable playable, FrameData data)
|
|
{
|
|
if (m_Target == null)
|
|
return;
|
|
|
|
if (m_ScrubbingCacheHelper == null)
|
|
{
|
|
m_ScrubbingCacheHelper = new VisualEffectControlTrackController();
|
|
|
|
VisualEffectControlTrack parentTrack = null;
|
|
#if UNITY_EDITOR
|
|
parentTrack = m_ParentTrack;
|
|
#endif
|
|
m_ScrubbingCacheHelper.Init(playable, m_Target, parentTrack);
|
|
}
|
|
|
|
var duration = playable.GetOutput(0).GetDuration();
|
|
var globalTime = playable.GetTime();
|
|
var numberOfFullLoops = (int)(globalTime / duration);
|
|
globalTime -= numberOfFullLoops * duration;
|
|
|
|
var deltaTime = data.deltaTime;
|
|
m_ScrubbingCacheHelper.Update(globalTime, deltaTime);
|
|
}
|
|
|
|
void BindVFX(VisualEffect vfx)
|
|
{
|
|
m_Target = vfx;
|
|
if (m_Target != null && m_ReinitWithBinding)
|
|
{
|
|
m_Target.Reinit(false);
|
|
}
|
|
}
|
|
|
|
void UnbindVFX()
|
|
{
|
|
if (m_Target != null && m_ReinitWithUnbinding)
|
|
{
|
|
m_Target.Reinit(true);
|
|
}
|
|
m_Target = null;
|
|
}
|
|
|
|
public override void PrepareFrame(Playable playable, FrameData data)
|
|
{
|
|
var vfx = data.output.GetUserData() as VisualEffect;
|
|
if (m_Target != vfx)
|
|
{
|
|
UnbindVFX();
|
|
|
|
if (vfx != null)
|
|
{
|
|
if (vfx.visualEffectAsset == null)
|
|
vfx = null;
|
|
else if (!vfx.isActiveAndEnabled)
|
|
vfx = null;
|
|
}
|
|
|
|
BindVFX(vfx);
|
|
InvalidateScrubbingHelper();
|
|
}
|
|
ApplyFrame(playable, data);
|
|
}
|
|
|
|
public override void OnBehaviourPause(Playable playable, FrameData data)
|
|
{
|
|
base.OnBehaviourPause(playable, data);
|
|
ApplyFrame(playable, data);
|
|
}
|
|
|
|
void InvalidateScrubbingHelper()
|
|
{
|
|
if (m_ScrubbingCacheHelper != null)
|
|
{
|
|
m_ScrubbingCacheHelper.Release();
|
|
m_ScrubbingCacheHelper = null;
|
|
}
|
|
}
|
|
|
|
public override void OnPlayableCreate(Playable playable)
|
|
{
|
|
InvalidateScrubbingHelper();
|
|
}
|
|
|
|
public override void OnPlayableDestroy(Playable playable)
|
|
{
|
|
InvalidateScrubbingHelper();
|
|
UnbindVFX();
|
|
}
|
|
}
|
|
}
|
|
#endif
|