diff --git a/Source/Editor/GUI/Timeline/Media.cs b/Source/Editor/GUI/Timeline/Media.cs
index 5f90b6799..b826e5b6e 100644
--- a/Source/Editor/GUI/Timeline/Media.cs
+++ b/Source/Editor/GUI/Timeline/Media.cs
@@ -114,6 +114,11 @@ namespace FlaxEditor.GUI.Timeline
///
public event Action StartFrameChanged;
+ ///
+ /// Gets the end frame of the media (start + duration).
+ ///
+ public int EndFrame => _startFrame + _durationFrames;
+
///
/// Gets or sets the total duration of the media event in the timeline sequence frames amount.
///
@@ -175,6 +180,11 @@ namespace FlaxEditor.GUI.Timeline
///
public object PropertiesEditObject;
+ ///
+ /// Gets a value indicating whether this media can be split.
+ ///
+ public bool CanSplit;
+
///
/// Initializes a new instance of the class.
///
@@ -258,6 +268,22 @@ namespace FlaxEditor.GUI.Timeline
Width = Duration * Timeline.UnitsPerSecond * _timeline.Zoom;
}
+ ///
+ /// Splits the media at the specified frame.
+ ///
+ /// The frame to split at.
+ /// The another media created after this media split.
+ public virtual Media Split(int frame)
+ {
+ var clone = (Media)Activator.CreateInstance(GetType());
+ clone.StartFrame = frame;
+ clone.DurationFrames = EndFrame - frame;
+ DurationFrames = DurationFrames - clone.DurationFrames;
+ Track?.AddMedia(clone);
+ Timeline?.MarkAsEdited();
+ return clone;
+ }
+
///
public override void GetDesireClientArea(out Rectangle rect)
{
diff --git a/Source/Editor/GUI/Timeline/Timeline.Data.cs b/Source/Editor/GUI/Timeline/Timeline.Data.cs
index e1a4b69a7..f5f9bc50b 100644
--- a/Source/Editor/GUI/Timeline/Timeline.Data.cs
+++ b/Source/Editor/GUI/Timeline/Timeline.Data.cs
@@ -104,8 +104,9 @@ namespace FlaxEditor.GUI.Timeline
}
break;
}
- case 2:
- case 3:
+ case 2: // [Deprecated in 2020 expires on 03.09.2023]
+ case 3: // [Deprecated on 03.09.2021 expires on 03.09.2023]
+ case 4:
{
// Load properties
FramesPerSecond = stream.ReadSingle();
diff --git a/Source/Editor/GUI/Timeline/Timeline.cs b/Source/Editor/GUI/Timeline/Timeline.cs
index 2b5349d2c..570caed62 100644
--- a/Source/Editor/GUI/Timeline/Timeline.cs
+++ b/Source/Editor/GUI/Timeline/Timeline.cs
@@ -38,7 +38,7 @@ namespace FlaxEditor.GUI.Timeline
new KeyValuePair(0, "Custom"),
};
- internal const int FormatVersion = 3;
+ internal const int FormatVersion = 4;
///
/// The base class for timeline properties proxy objects.
@@ -1584,6 +1584,39 @@ namespace FlaxEditor.GUI.Timeline
}
}
+ ///
+ /// Splits the media (all or selected only) at the given frame.
+ ///
+ /// The frame to split at.
+ public void Split(int frame)
+ {
+ List actions = null;
+ foreach (var track in _tracks)
+ {
+ byte[] trackData = null;
+ for (int i = track.Media.Count - 1; i >= 0; i--)
+ {
+ if (track.Media.Count >= track.MaxMediaCount)
+ break;
+ var media = track.Media[i];
+ if (media.CanSplit && media.StartFrame < frame && media.EndFrame > frame)
+ {
+ if (Undo != null && Undo.Enabled)
+ trackData = EditTrackAction.CaptureData(track);
+ media.Split(frame);
+ }
+ }
+ if (trackData != null)
+ {
+ if (actions == null)
+ actions = new List();
+ actions.Add(new EditTrackAction(this, track, trackData, EditTrackAction.CaptureData(track)));
+ }
+ }
+ if (actions != null)
+ Undo.AddAction(new MultiUndoAction(actions, "Split"));
+ }
+
///
/// Called once to setup the drag drop handling for the timeline (lazy init on first drag action).
///
@@ -1979,6 +2012,9 @@ namespace FlaxEditor.GUI.Timeline
return true;
}
break;
+ case KeyboardKeys.S:
+ Split(CurrentFrame);
+ return true;
}
return false;
diff --git a/Source/Editor/GUI/Timeline/Track.cs b/Source/Editor/GUI/Timeline/Track.cs
index f61bd87f3..ea1fa6501 100644
--- a/Source/Editor/GUI/Timeline/Track.cs
+++ b/Source/Editor/GUI/Timeline/Track.cs
@@ -171,6 +171,16 @@ namespace FlaxEditor.GUI.Timeline
///
public bool Loop;
+ ///
+ /// The minimum amount of media items for this track.
+ ///
+ public int MinMediaCount = 0;
+
+ ///
+ /// The maximum amount of media items for this track.
+ ///
+ public int MaxMediaCount = 1024;
+
///
/// The track archetype.
///
diff --git a/Source/Editor/GUI/Timeline/Tracks/AudioTrack.cs b/Source/Editor/GUI/Timeline/Tracks/AudioTrack.cs
index 197e889a6..8a1931a14 100644
--- a/Source/Editor/GUI/Timeline/Tracks/AudioTrack.cs
+++ b/Source/Editor/GUI/Timeline/Tracks/AudioTrack.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using FlaxEditor.Utilities;
using FlaxEditor.GUI.Timeline.Undo;
using FlaxEditor.Viewport.Previews;
@@ -15,7 +16,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// The timeline media that represents an audio clip media event.
///
///
- public class AudioMedia : SingleMediaAssetMedia
+ public class AudioMedia : Media
{
///
/// True if loop track, otherwise audio clip will stop on the end.
@@ -25,19 +26,31 @@ namespace FlaxEditor.GUI.Timeline.Tracks
get => Track.Loop;
set
{
- if (Track.Loop != value)
- {
- Track.Loop = value;
- Preview.DrawMode = value ? AudioClipPreview.DrawModes.Looped : AudioClipPreview.DrawModes.Single;
- }
+ if (Loop == value)
+ return;
+ Track.Loop = value;
+ Preview.DrawMode = value ? AudioClipPreview.DrawModes.Looped : AudioClipPreview.DrawModes.Single;
+ Timeline?.MarkAsEdited();
+ }
+ }
+
+ ///
+ /// Playback offset of the audio (in seconds).
+ ///
+ public float Offset
+ {
+ get => Preview.ViewOffset;
+ set
+ {
+ if (Mathf.NearEqual(Preview.ViewOffset, value))
+ return;
+ Preview.ViewOffset = value;
+ Timeline?.MarkAsEdited();
}
}
private sealed class Proxy : ProxyBase
{
- ///
- /// Gets or sets the audio clip to play.
- ///
[EditorDisplay("General"), EditorOrder(10), Tooltip("The audio clip to play.")]
public AudioClip Audio
{
@@ -45,17 +58,21 @@ namespace FlaxEditor.GUI.Timeline.Tracks
set => Track.Asset = value;
}
- ///
- /// Gets or sets the audio clip looping mode.
- ///
[EditorDisplay("General"), EditorOrder(20), Tooltip("If checked, the audio clip will loop when playback exceeds its duration. Otherwise it will stop play.")]
public bool Loop
{
- get => Track.TrackLoop;
- set => Track.TrackLoop = value;
+ get => Media.Loop;
+ set => Media.Loop = value;
+ }
+
+ [EditorDisplay("General"), EditorOrder(30), Tooltip("Playback offset of the audio (in seconds).")]
+ [Limit(0, float.MaxValue, 0.01f)]
+ public float Offset
+ {
+ get => Media.Offset;
+ set => Media.Offset = value;
}
- ///
public Proxy(AudioTrack track, AudioMedia media)
: base(track, media)
{
@@ -70,6 +87,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
///
public AudioMedia()
{
+ CanSplit = true;
Preview = new AudioClipPreview
{
AnchorPreset = AnchorPresets.StretchAll,
@@ -79,12 +97,30 @@ namespace FlaxEditor.GUI.Timeline.Tracks
};
}
+ ///
+ protected override void OnStartFrameChanged()
+ {
+ base.OnStartFrameChanged();
+
+ if (Track != null && Track.SubTracks.Count != 0 && Track.SubTracks[0] is AudioVolumeTrack volumeTrack)
+ volumeTrack.UpdateCurve();
+ }
+
+ ///
+ protected override void OnDurationFramesChanged()
+ {
+ base.OnDurationFramesChanged();
+
+ if (Track != null && Track.SubTracks.Count != 0 && Track.SubTracks[0] is AudioVolumeTrack volumeTrack)
+ volumeTrack.UpdateCurve();
+ }
+
///
public override void OnTimelineChanged(Track track)
{
base.OnTimelineChanged(track);
- PropertiesEditObject = new Proxy(Track as AudioTrack, this);
+ PropertiesEditObject = track != null ? new Proxy((AudioTrack)track, this) : null;
}
///
@@ -94,6 +130,17 @@ namespace FlaxEditor.GUI.Timeline.Tracks
Preview.ViewScale = Timeline.UnitsPerSecond / AudioClipPreview.UnitsPerSecond * Timeline.Zoom;
}
+
+ ///
+ public override Media Split(int frame)
+ {
+ var offset = Offset + ((float)(frame - StartFrame) / DurationFrames) * Duration;
+ var clone = (AudioMedia)base.Split(frame);
+ clone.Preview.ViewOffset = offset;
+ clone.Preview.Asset = Preview.Asset;
+ clone.Preview.DrawMode = Preview.DrawMode;
+ return clone;
+ }
}
///
@@ -123,46 +170,46 @@ namespace FlaxEditor.GUI.Timeline.Tracks
var e = (AudioTrack)track;
Guid id = stream.ReadGuid();
e.Asset = FlaxEngine.Content.LoadAsync(id);
- var m = e.TrackMedia;
- m.StartFrame = stream.ReadInt32();
- m.DurationFrames = stream.ReadInt32();
- m.Preview.DrawMode = track.Loop ? AudioClipPreview.DrawModes.Looped : AudioClipPreview.DrawModes.Single;
+ if (version <= 3)
+ {
+ // [Deprecated on 03.09.2021 expires on 03.09.2023]
+ var m = e.TrackMedia;
+ m.StartFrame = stream.ReadInt32();
+ m.DurationFrames = stream.ReadInt32();
+ m.Preview.ViewOffset = 0.0f;
+ m.Preview.DrawMode = track.Loop ? AudioClipPreview.DrawModes.Looped : AudioClipPreview.DrawModes.Single;
+ }
+ else
+ {
+ var count = stream.ReadInt32();
+ while (e.Media.Count > count)
+ e.RemoveMedia(e.Media.Last());
+ while (e.Media.Count < count)
+ e.AddMedia(new AudioMedia());
+ for (int i = 0; i < count; i++)
+ {
+ var m = (AudioMedia)e.Media[i];
+ m.StartFrame = stream.ReadInt32();
+ m.DurationFrames = stream.ReadInt32();
+ m.Preview.ViewOffset = stream.ReadSingle();
+ m.Preview.DrawMode = track.Loop ? AudioClipPreview.DrawModes.Looped : AudioClipPreview.DrawModes.Single;
+ m.Preview.Asset = e.Asset;
+ }
+ }
}
private static void SaveTrack(Track track, BinaryWriter stream)
{
var e = (AudioTrack)track;
- var assetId = e.Asset?.ID ?? Guid.Empty;
-
- stream.Write(assetId.ToByteArray());
-
- if (e.Media.Count != 0)
+ stream.WriteGuid(ref e.AssetID);
+ var count = e.Media.Count;
+ stream.Write(count);
+ for (int i = 0; i < count; i++)
{
- var m = e.TrackMedia;
+ var m = (AudioMedia)e.Media[i];
stream.Write(m.StartFrame);
stream.Write(m.DurationFrames);
- }
- else
- {
- stream.Write(0);
- stream.Write(track.Timeline.DurationFrames);
- }
- }
-
- ///
- /// Gets or sets the audio clip looping mode.
- ///
- public bool TrackLoop
- {
- get => TrackMedia.Loop;
- set
- {
- AudioMedia media = TrackMedia;
- if (media.Loop == value)
- return;
-
- media.Loop = value;
- Timeline?.MarkAsEdited();
+ stream.Write(m.Offset);
}
}
@@ -172,6 +219,8 @@ namespace FlaxEditor.GUI.Timeline.Tracks
public AudioTrack(ref TrackCreateOptions options)
: base(ref options)
{
+ MinMediaCount = 1;
+
// Add button
const float buttonSize = 14;
_addButton = new Button
@@ -219,7 +268,8 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{
base.OnAssetChanged();
- TrackMedia.Preview.Asset = Asset;
+ foreach (AudioMedia m in Media)
+ m.Preview.Asset = Asset;
}
}
@@ -280,7 +330,6 @@ namespace FlaxEditor.GUI.Timeline.Tracks
}
}
- private AudioMedia _audioMedia;
private const float CollapsedHeight = 20.0f;
private const float ExpandedHeight = 64.0f;
private Label _previewValue;
@@ -381,17 +430,49 @@ namespace FlaxEditor.GUI.Timeline.Tracks
}
}
+ private bool GetRangeFrames(out int startFrame, out int endFrame)
+ {
+ if (ParentTrack != null && ParentTrack.Media.Count != 0)
+ {
+ startFrame = ParentTrack.Media[0].StartFrame;
+ endFrame = ParentTrack.Media[0].EndFrame;
+ for (int i = 1; i < ParentTrack.Media.Count; i++)
+ {
+ endFrame = Mathf.Max(endFrame, ParentTrack.Media[i].EndFrame);
+ }
+ return true;
+ }
+ startFrame = endFrame = 0;
+ return false;
+ }
+
+ private bool GetRangeMedia(out Media startMedia, out Media endMedia)
+ {
+ if (ParentTrack != null && ParentTrack.Media.Count != 0)
+ {
+ startMedia = endMedia = ParentTrack.Media[0];
+ for (int i = 1; i < ParentTrack.Media.Count; i++)
+ {
+ if (ParentTrack.Media[i].EndFrame >= endMedia.EndFrame)
+ endMedia = ParentTrack.Media[i];
+ }
+ return true;
+ }
+ startMedia = endMedia = null;
+ return false;
+ }
+
///
protected override void OnContextMenu(ContextMenu.ContextMenu menu)
{
base.OnContextMenu(menu);
- if (_audioMedia == null || Curve == null)
+ if (!GetRangeFrames(out var startFrame, out _) || Curve == null)
return;
menu.AddSeparator();
menu.AddButton("Copy Preview Value", () =>
{
- var time = (Timeline.CurrentFrame - _audioMedia.StartFrame) / Timeline.FramesPerSecond;
+ var time = (Timeline.CurrentFrame - startFrame) / Timeline.FramesPerSecond;
Curve.Evaluate(out var value, time, false);
Clipboard.Text = FlaxEngine.Utils.RoundTo2DecimalPlaces(Mathf.Saturate(value)).ToString("0.00");
}).LinkTooltip("Copies the current track value to the clipboard").Enabled = Timeline.ShowPreviewValues;
@@ -400,15 +481,15 @@ namespace FlaxEditor.GUI.Timeline.Tracks
///
public override bool GetNextKeyframeFrame(float time, out int result)
{
- if (_audioMedia != null)
+ if (GetRangeFrames(out var startFrame, out var endFrame))
{
- var mediaTime = time - _audioMedia.StartFrame / Timeline.FramesPerSecond;
+ var mediaTime = time - startFrame / Timeline.FramesPerSecond;
for (int i = 0; i < Curve.Keyframes.Count; i++)
{
var k = Curve.Keyframes[i];
if (k.Time > mediaTime)
{
- result = Mathf.FloorToInt(k.Time * Timeline.FramesPerSecond) + _audioMedia.StartFrame;
+ result = Mathf.FloorToInt(k.Time * Timeline.FramesPerSecond) + startFrame;
return true;
}
}
@@ -419,13 +500,13 @@ namespace FlaxEditor.GUI.Timeline.Tracks
private void OnAddKeyClicked(Image image, MouseButton button)
{
var currentFrame = Timeline.CurrentFrame;
- if (button == MouseButton.Left && _audioMedia != null && currentFrame >= _audioMedia.StartFrame && currentFrame < _audioMedia.StartFrame + _audioMedia.DurationFrames)
+ if (button == MouseButton.Left && GetRangeFrames(out var startFrame, out var endFrame) && currentFrame >= startFrame && currentFrame < endFrame)
{
- var time = (currentFrame - _audioMedia.StartFrame) / Timeline.FramesPerSecond;
+ var time = (currentFrame - startFrame) / Timeline.FramesPerSecond;
for (int i = Curve.Keyframes.Count - 1; i >= 0; i--)
{
var k = Curve.Keyframes[i];
- var frame = Mathf.FloorToInt(k.Time * Timeline.FramesPerSecond) + _audioMedia.StartFrame;
+ var frame = Mathf.FloorToInt(k.Time * Timeline.FramesPerSecond) + startFrame;
if (frame == Timeline.CurrentFrame)
{
// Already added
@@ -449,15 +530,15 @@ namespace FlaxEditor.GUI.Timeline.Tracks
///
public override bool GetPreviousKeyframeFrame(float time, out int result)
{
- if (_audioMedia != null)
+ if (GetRangeFrames(out var startFrame, out _))
{
- var mediaTime = time - _audioMedia.StartFrame / Timeline.FramesPerSecond;
+ var mediaTime = time - startFrame / Timeline.FramesPerSecond;
for (int i = Curve.Keyframes.Count - 1; i >= 0; i--)
{
var k = Curve.Keyframes[i];
if (k.Time < mediaTime)
{
- result = Mathf.FloorToInt(k.Time * Timeline.FramesPerSecond) + _audioMedia.StartFrame;
+ result = Mathf.FloorToInt(k.Time * Timeline.FramesPerSecond) + startFrame;
return true;
}
}
@@ -467,17 +548,17 @@ namespace FlaxEditor.GUI.Timeline.Tracks
private void UpdatePreviewValue()
{
- if (_audioMedia == null || Curve == null || Timeline == null)
+ if (!GetRangeFrames(out var startFrame, out _) || Curve == null || Timeline == null)
return;
- var time = (Timeline.CurrentFrame - _audioMedia.StartFrame) / Timeline.FramesPerSecond;
+ var time = (Timeline.CurrentFrame - startFrame) / Timeline.FramesPerSecond;
Curve.Evaluate(out var value, time, false);
_previewValue.Text = FlaxEngine.Utils.RoundTo2DecimalPlaces(Mathf.Saturate(value)).ToString("0.00");
}
- private void UpdateCurve()
+ internal void UpdateCurve()
{
- if (_audioMedia == null || Curve == null || Timeline == null)
+ if (!GetRangeMedia(out var startMedia, out var endMedia) || Curve == null || Timeline == null)
return;
bool wasVisible = Curve.Visible;
Curve.Visible = Visible;
@@ -489,7 +570,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
}
Curve.KeyframesEditorContext = Timeline;
Curve.CustomViewPanning = Timeline.OnKeyframesViewPanning;
- Curve.Bounds = new Rectangle(_audioMedia.X, Y + 1.0f, _audioMedia.Width, Height - 2.0f);
+ Curve.Bounds = new Rectangle(startMedia.X, Y + 1.0f, endMedia.Right - startMedia.Left, Height - 2.0f);
var expanded = IsExpanded;
if (expanded)
{
@@ -542,19 +623,8 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{
base.OnParentTrackChanged(parent);
- if (_audioMedia != null)
+ if (parent != null)
{
- _audioMedia.StartFrameChanged -= UpdateCurve;
- _audioMedia.DurationFramesChanged -= UpdateCurve;
- _audioMedia = null;
- }
-
- if (parent is AudioTrack audioTrack)
- {
- var media = audioTrack.TrackMedia;
- media.StartFrameChanged += UpdateCurve;
- media.DurationFramesChanged += UpdateCurve;
- _audioMedia = media;
UpdateCurve();
UpdatePreviewValue();
}
diff --git a/Source/Editor/GUI/Timeline/Tracks/CameraCutTrack.cs b/Source/Editor/GUI/Timeline/Tracks/CameraCutTrack.cs
index 76c06c41a..68ee84a7c 100644
--- a/Source/Editor/GUI/Timeline/Tracks/CameraCutTrack.cs
+++ b/Source/Editor/GUI/Timeline/Tracks/CameraCutTrack.cs
@@ -4,6 +4,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using FlaxEditor.Utilities;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -33,6 +34,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
public CameraCutMedia()
{
ClipChildren = true;
+ CanSplit = true;
}
///
@@ -292,7 +294,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{
base.OnTimelineChanged(track);
- PropertiesEditObject = new Proxy(Track as CameraCutTrack, this);
+ PropertiesEditObject = track != null ? new Proxy((CameraCutTrack)track, this) : null;
UpdateThumbnails();
}
@@ -673,26 +675,41 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{
var e = (CameraCutTrack)track;
e.ActorID = stream.ReadGuid();
- var m = e.TrackMedia;
- m.StartFrame = stream.ReadInt32();
- m.DurationFrames = stream.ReadInt32();
+ if (version <= 3)
+ {
+ // [Deprecated on 03.09.2021 expires on 03.09.2023]
+ var m = e.TrackMedia;
+ m.StartFrame = stream.ReadInt32();
+ m.DurationFrames = stream.ReadInt32();
+ }
+ else
+ {
+ var count = stream.ReadInt32();
+ while (e.Media.Count > count)
+ e.RemoveMedia(e.Media.Last());
+ while (e.Media.Count < count)
+ e.AddMedia(new CameraCutMedia());
+ for (int i = 0; i < count; i++)
+ {
+ var m = (CameraCutMedia)e.Media[i];
+ m.StartFrame = stream.ReadInt32();
+ m.DurationFrames = stream.ReadInt32();
+ }
+ }
}
private static void SaveTrack(Track track, BinaryWriter stream)
{
var e = (CameraCutTrack)track;
- stream.Write(e.ActorID.ToByteArray());
- if (e.Media.Count != 0)
+ stream.WriteGuid(ref e.ActorID);
+ var count = e.Media.Count;
+ stream.Write(count);
+ for (int i = 0; i < count; i++)
{
- var m = e.TrackMedia;
+ var m = e.Media[i];
stream.Write(m.StartFrame);
stream.Write(m.DurationFrames);
}
- else
- {
- stream.Write(0);
- stream.Write(track.Timeline.DurationFrames);
- }
}
private Image _pilotCamera;
@@ -702,10 +719,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
///
public Camera Camera => Actor as Camera;
- ///
- /// Gets the camera track media.
- ///
- public CameraCutMedia TrackMedia
+ private CameraCutMedia TrackMedia
{
get
{
@@ -731,6 +745,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
public CameraCutTrack(ref TrackCreateOptions options)
: base(ref options, false)
{
+ MinMediaCount = 1;
Height = CameraCutThumbnailRenderer.Height + 8;
// Pilot Camera button
@@ -794,12 +809,18 @@ namespace FlaxEditor.GUI.Timeline.Tracks
}
}
+ private void UpdateThumbnails()
+ {
+ foreach (CameraCutMedia media in Media)
+ media.UpdateThumbnails();
+ }
+
///
protected override void OnObjectExistenceChanged(object obj)
{
base.OnObjectExistenceChanged(obj);
- TrackMedia.UpdateThumbnails();
+ UpdateThumbnails();
}
///
diff --git a/Source/Editor/GUI/Timeline/Tracks/NestedSceneAnimationTrack.cs b/Source/Editor/GUI/Timeline/Tracks/NestedSceneAnimationTrack.cs
index df24b0d5c..916d738c2 100644
--- a/Source/Editor/GUI/Timeline/Tracks/NestedSceneAnimationTrack.cs
+++ b/Source/Editor/GUI/Timeline/Tracks/NestedSceneAnimationTrack.cs
@@ -12,7 +12,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// The timeline media that represents a nested scene animation media event.
///
///
- public class NestedSceneAnimationMedia : SingleMediaAssetMedia
+ public class NestedSceneAnimationMedia : Media
{
private sealed class Proxy : ProxyBase
{
@@ -48,7 +48,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{
base.OnTimelineChanged(track);
- PropertiesEditObject = new Proxy(Track as NestedSceneAnimationTrack, this);
+ PropertiesEditObject = track != null ? new Proxy((NestedSceneAnimationTrack)track, this) : null;
}
}
@@ -87,10 +87,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
private static void SaveTrack(Track track, BinaryWriter stream)
{
var e = (NestedSceneAnimationTrack)track;
- var assetId = e.Asset?.ID ?? Guid.Empty;
-
- stream.Write(assetId.ToByteArray());
-
+ stream.WriteGuid(ref e.AssetID);
if (e.Media.Count != 0)
{
var m = e.TrackMedia;
@@ -123,6 +120,8 @@ namespace FlaxEditor.GUI.Timeline.Tracks
public NestedSceneAnimationTrack(ref TrackCreateOptions options)
: base(ref options)
{
+ MinMediaCount = 1;
+ MaxMediaCount = 1;
}
///
diff --git a/Source/Editor/GUI/Timeline/Tracks/ParticleEmitterTrack.cs b/Source/Editor/GUI/Timeline/Tracks/ParticleEmitterTrack.cs
index 1a26555ac..d6184e50c 100644
--- a/Source/Editor/GUI/Timeline/Tracks/ParticleEmitterTrack.cs
+++ b/Source/Editor/GUI/Timeline/Tracks/ParticleEmitterTrack.cs
@@ -12,7 +12,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// The timeline media that represents a particle miter playback media event.
///
///
- public class ParticleEmitterMedia : SingleMediaAssetMedia
+ public class ParticleEmitterMedia : Media
{
private sealed class Proxy : ProxyBase
{
@@ -38,7 +38,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{
base.OnTimelineChanged(track);
- PropertiesEditObject = new Proxy(Track as ParticleEmitterTrack, this);
+ PropertiesEditObject = track != null ? new Proxy((ParticleEmitterTrack)track, this) : null;
}
}
@@ -78,11 +78,8 @@ namespace FlaxEditor.GUI.Timeline.Tracks
private static void SaveTrack(Track track, BinaryWriter stream)
{
var e = (ParticleEmitterTrack)track;
- var emitterId = e.Asset?.ID ?? Guid.Empty;
-
- stream.Write(emitterId.ToByteArray());
+ stream.WriteGuid(ref e.AssetID);
stream.Write(((ParticleSystemTimeline)track.Timeline).Emitters.IndexOf(e));
-
if (e.Media.Count != 0)
{
var m = e.TrackMedia;
@@ -108,6 +105,8 @@ namespace FlaxEditor.GUI.Timeline.Tracks
public ParticleEmitterTrack(ref TrackCreateOptions options)
: base(ref options)
{
+ MinMediaCount = 1;
+ MaxMediaCount = 1;
}
///
diff --git a/Source/Editor/GUI/Timeline/Tracks/PostProcessMaterialTrack.cs b/Source/Editor/GUI/Timeline/Tracks/PostProcessMaterialTrack.cs
index 201a553a6..4310ac577 100644
--- a/Source/Editor/GUI/Timeline/Tracks/PostProcessMaterialTrack.cs
+++ b/Source/Editor/GUI/Timeline/Tracks/PostProcessMaterialTrack.cs
@@ -2,6 +2,7 @@
using System;
using System.IO;
+using System.Linq;
using FlaxEditor.Utilities;
using FlaxEngine;
@@ -11,13 +12,10 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// The timeline media that represents a post-process material media event.
///
///
- public class PostProcessMaterialMedia : SingleMediaAssetMedia
+ public class PostProcessMaterialMedia : Media
{
private sealed class Proxy : ProxyBase
{
- ///
- /// Gets or sets the post process material to show.
- ///
[EditorDisplay("General"), EditorOrder(10), Tooltip("The post process material to show.")]
public MaterialBase PostProcessMaterial
{
@@ -32,12 +30,20 @@ namespace FlaxEditor.GUI.Timeline.Tracks
}
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PostProcessMaterialMedia()
+ {
+ CanSplit = true;
+ }
+
///
public override void OnTimelineChanged(Track track)
{
base.OnTimelineChanged(track);
- PropertiesEditObject = new Proxy(Track as PostProcessMaterialTrack, this);
+ PropertiesEditObject = track != null ? new Proxy((PostProcessMaterialTrack)track, this) : null;
}
}
@@ -68,29 +74,41 @@ namespace FlaxEditor.GUI.Timeline.Tracks
var e = (PostProcessMaterialTrack)track;
Guid id = stream.ReadGuid();
e.Asset = FlaxEngine.Content.LoadAsync(id);
- var m = e.TrackMedia;
- m.StartFrame = stream.ReadInt32();
- m.DurationFrames = stream.ReadInt32();
+ if (version <= 3)
+ {
+ // [Deprecated on 03.09.2021 expires on 03.09.2023]
+ var m = e.TrackMedia;
+ m.StartFrame = stream.ReadInt32();
+ m.DurationFrames = stream.ReadInt32();
+ }
+ else
+ {
+ var count = stream.ReadInt32();
+ while (e.Media.Count > count)
+ e.RemoveMedia(e.Media.Last());
+ while (e.Media.Count < count)
+ e.AddMedia(new PostProcessMaterialMedia());
+ for (int i = 0; i < count; i++)
+ {
+ var m = e.Media[i];
+ m.StartFrame = stream.ReadInt32();
+ m.DurationFrames = stream.ReadInt32();
+ }
+ }
}
private static void SaveTrack(Track track, BinaryWriter stream)
{
var e = (PostProcessMaterialTrack)track;
- var materialId = e.Asset?.ID ?? Guid.Empty;
-
- stream.Write(materialId.ToByteArray());
-
- if (e.Media.Count != 0)
+ stream.WriteGuid(ref e.AssetID);
+ var count = e.Media.Count;
+ stream.Write(count);
+ for (int i = 0; i < count; i++)
{
- var m = e.TrackMedia;
+ var m = e.Media[i];
stream.Write(m.StartFrame);
stream.Write(m.DurationFrames);
}
- else
- {
- stream.Write(0);
- stream.Write(track.Timeline.DurationFrames);
- }
}
///
@@ -100,6 +118,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
public PostProcessMaterialTrack(ref TrackCreateOptions options)
: base(ref options)
{
+ MinMediaCount = 1;
}
}
}
diff --git a/Source/Editor/GUI/Timeline/Tracks/ScreenFadeTrack.cs b/Source/Editor/GUI/Timeline/Tracks/ScreenFadeTrack.cs
index fee15d39c..13b5c6fbc 100644
--- a/Source/Editor/GUI/Timeline/Tracks/ScreenFadeTrack.cs
+++ b/Source/Editor/GUI/Timeline/Tracks/ScreenFadeTrack.cs
@@ -81,7 +81,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{
base.OnTimelineChanged(track);
- PropertiesEditObject = new Proxy(Track as ScreenFadeTrack, this);
+ PropertiesEditObject = track != null ? new Proxy((ScreenFadeTrack)track, this) : null;
}
///
diff --git a/Source/Editor/GUI/Timeline/Tracks/SingleMediaAssetTrack.cs b/Source/Editor/GUI/Timeline/Tracks/SingleMediaAssetTrack.cs
index adaf354ca..694323e62 100644
--- a/Source/Editor/GUI/Timeline/Tracks/SingleMediaAssetTrack.cs
+++ b/Source/Editor/GUI/Timeline/Tracks/SingleMediaAssetTrack.cs
@@ -8,18 +8,6 @@ using FlaxEngine.GUI;
namespace FlaxEditor.GUI.Timeline.Tracks
{
- ///
- /// The timeline media that represents a media event with an asset reference.
- ///
- ///
- public class SingleMediaAssetMedia : Media
- {
- ///
- /// The asset id.
- ///
- public Guid Asset;
- }
-
///
/// The base class for timeline tracks that use single media with an asset reference.
///
@@ -28,26 +16,29 @@ namespace FlaxEditor.GUI.Timeline.Tracks
///
public abstract class SingleMediaAssetTrack : SingleMediaTrack
where TAsset : Asset
- where TMedia : SingleMediaAssetMedia, new()
+ where TMedia : Media, new()
{
///
/// The asset reference picker control.
///
protected readonly AssetPicker _picker;
+ ///
+ /// The asset id.
+ ///
+ public Guid AssetID;
+
///
/// Gets or sets the asset.
///
public TAsset Asset
{
- get => FlaxEngine.Content.LoadAsync(TrackMedia.Asset);
+ get => FlaxEngine.Content.LoadAsync(AssetID);
set
{
- TMedia media = TrackMedia;
- if (media.Asset == value?.ID)
+ if (AssetID == value?.ID)
return;
-
- media.Asset = value?.ID ?? Guid.Empty;
+ AssetID = value?.ID ?? Guid.Empty;
_picker.SelectedAsset = value;
OnAssetChanged();
Timeline?.MarkAsEdited();
diff --git a/Source/Editor/GUI/Timeline/Tracks/SingleMediaTrack.cs b/Source/Editor/GUI/Timeline/Tracks/SingleMediaTrack.cs
index 6ed700954..eb250bff9 100644
--- a/Source/Editor/GUI/Timeline/Tracks/SingleMediaTrack.cs
+++ b/Source/Editor/GUI/Timeline/Tracks/SingleMediaTrack.cs
@@ -44,9 +44,16 @@ namespace FlaxEditor.GUI.Timeline.Tracks
///
public override void OnSpawned()
{
- // Ensure to have valid media added
- // ReSharper disable once UnusedVariable
- var media = TrackMedia;
+ // Ensure to have minimum valid media count
+ for (int i = Media.Count; i < MinMediaCount; i++)
+ {
+ var m = new TMedia
+ {
+ StartFrame = 0,
+ DurationFrames = Timeline?.DurationFrames ?? 60,
+ };
+ AddMedia(m);
+ }
base.OnSpawned();
}
diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp b/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp
index 9518bef8e..9fd1e6810 100644
--- a/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp
+++ b/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp
@@ -22,6 +22,11 @@ SceneAnimation::SceneAnimation(const SpawnParams& params, const AssetInfo* info)
{
}
+float SceneAnimation::GetDuration() const
+{
+ return (float)DurationFrames / FramesPerSecond;
+}
+
const BytesContainer& SceneAnimation::LoadTimeline()
{
WaitForLoaded();
@@ -110,8 +115,9 @@ Asset::LoadResult SceneAnimation::load()
stream.ReadInt32(&version);
switch (version)
{
- case 2:
- case 3:
+ case 2: // [Deprecated in 2020 expires on 03.09.2023]
+ case 3: // [Deprecated on 03.09.2021 expires on 03.09.2023]
+ case 4:
{
stream.ReadFloat(&FramesPerSecond);
stream.ReadInt32(&DurationFrames);
@@ -143,14 +149,25 @@ Asset::LoadResult SceneAnimation::load()
switch (track.Type)
{
case Track::Types::Folder:
- {
break;
- }
case Track::Types::PostProcessMaterial:
{
const auto trackData = stream.Read();
track.Data = trackData;
track.Asset = Content::LoadAsync(trackData->AssetID);
+ const auto trackRuntime = _runtimeData.Move();
+ track.RuntimeData = trackRuntime;
+ if (version <= 3)
+ {
+ // [Deprecated on 03.09.2021 expires on 03.09.2023]
+ trackRuntime->Count = 1;
+ trackRuntime->Media = stream.Read();
+ }
+ else
+ {
+ stream.ReadInt32(&trackRuntime->Count);
+ trackRuntime->Media = stream.Read(trackRuntime->Count);
+ }
if (trackData->AssetID.IsValid() && !track.Asset)
{
LOG(Warning, "Missing material for track {0} in {1}.", track.Name, ToString());
@@ -197,6 +214,20 @@ Asset::LoadResult SceneAnimation::load()
track.RuntimeData = trackRuntime;
track.TrackStateIndex = TrackStatesCount++;
trackRuntime->VolumeTrackIndex = -1;
+ if (version <= 3)
+ {
+ // [Deprecated on 03.09.2021 expires on 03.09.2023]
+ trackRuntime->Count = 1;
+ trackRuntime->Media = _runtimeData.Move();
+ stream.ReadInt32(&trackRuntime->Media->StartFrame);
+ stream.ReadInt32(&trackRuntime->Media->DurationFrames);
+ trackRuntime->Media->Offset = 0.0f;
+ }
+ else
+ {
+ stream.ReadInt32(&trackRuntime->Count);
+ trackRuntime->Media = stream.Read(trackRuntime->Count);
+ }
break;
}
case Track::Types::AudioVolume:
@@ -211,7 +242,7 @@ Asset::LoadResult SceneAnimation::load()
{
if (Tracks[track.ParentIndex].Type == Track::Types::Audio)
{
- ((AudioTrack::Runtime*)((byte*)_runtimeData.GetHandle() + (intptr)Tracks[track.ParentIndex].RuntimeData))->VolumeTrackIndex = i;
+ ((AudioTrack::Runtime*)(_runtimeData.GetHandle() + (intptr)Tracks[track.ParentIndex].RuntimeData))->VolumeTrackIndex = i;
}
else
{
@@ -365,6 +396,17 @@ Asset::LoadResult SceneAnimation::load()
const auto trackRuntime = _runtimeData.Move();
track.RuntimeData = trackRuntime;
track.TrackStateIndex = TrackStatesCount++;
+ if (version <= 3)
+ {
+ // [Deprecated on 03.09.2021 expires on 03.09.2023]
+ trackRuntime->Count = 1;
+ trackRuntime->Media = stream.Read();
+ }
+ else
+ {
+ stream.ReadInt32(&trackRuntime->Count);
+ trackRuntime->Media = stream.Read(trackRuntime->Count);
+ }
break;
}
default:
diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimation.h b/Source/Engine/Animations/SceneAnimations/SceneAnimation.h
index 3961edf79..538e35cf3 100644
--- a/Source/Engine/Animations/SceneAnimations/SceneAnimation.h
+++ b/Source/Engine/Animations/SceneAnimations/SceneAnimation.h
@@ -129,24 +129,23 @@ public:
}
};
+ struct Media
+ {
+ int32 StartFrame;
+ int32 DurationFrames;
+ };
+
struct PostProcessMaterialTrack
{
struct Data
{
- ///
- /// The PostFx material asset ID.
- ///
Guid AssetID;
+ };
- ///
- /// The start frame of the media play begin.
- ///
- int32 StartFrame;
-
- ///
- /// The total duration of the media playback in the timeline sequence frames amount.
- ///
- int32 DurationFrames;
+ struct Runtime
+ {
+ int32 Count;
+ Media* Media;
};
};
@@ -154,19 +153,8 @@ public:
{
struct Data
{
- ///
- /// The scene animation asset ID.
- ///
Guid AssetID;
-
- ///
- /// The start frame of the media play begin.
- ///
int32 StartFrame;
-
- ///
- /// The total duration of the media playback in the timeline sequence frames amount.
- ///
int32 DurationFrames;
};
@@ -185,57 +173,36 @@ public:
struct Data
{
- ///
- /// The start frame of the media play begin.
- ///
int32 StartFrame;
-
- ///
- /// The total duration of the media playback in the timeline sequence frames amount.
- ///
int32 DurationFrames;
-
- ///
- /// The gradient stops count.
- ///
int32 GradientStopsCount;
};
struct Runtime
{
- ///
- /// The pointer to the gradient stops array (items count is GradientStopsCount).
- ///
GradientStop* GradientStops;
};
};
struct AudioTrack
{
+ struct Media
+ {
+ int32 StartFrame;
+ int32 DurationFrames;
+ float Offset;
+ };
+
struct Data
{
- ///
- /// The audio clip asset ID.
- ///
Guid AssetID;
-
- ///
- /// The start frame of the media play begin.
- ///
- int32 StartFrame;
-
- ///
- /// The total duration of the media playback in the timeline sequence frames amount.
- ///
- int32 DurationFrames;
};
struct Runtime
{
- ///
- /// The index of the volume track. If not used then value is -1. Assigned on load.
- ///
int32 VolumeTrackIndex;
+ int32 Count;
+ Media* Media;
};
};
@@ -245,22 +212,12 @@ public:
struct Data
{
- ///
- /// The keyframes count.
- ///
int32 KeyframesCount;
};
struct Runtime
{
- ///
- /// The keyframes count.
- ///
int32 KeyframesCount;
-
- ///
- /// The keyframes array (items count is KeyframesCount).
- ///
BezierCurveKeyframe* Keyframes;
};
};
@@ -269,9 +226,6 @@ public:
{
struct Data
{
- ///
- /// The object ID.
- ///
Guid ID;
};
@@ -306,37 +260,15 @@ public:
{
struct Data
{
- ///
- /// The property value data size (in bytes).
- ///
int32 ValueSize;
-
- ///
- /// The name length.
- ///
int32 PropertyNameLength;
-
- ///
- /// The typename length.
- ///
int32 PropertyTypeNameLength;
};
struct Runtime
{
- ///
- /// The property value data size (in bytes).
- ///
int32 ValueSize;
-
- ///
- /// The name of the property (just a member name).
- ///
char* PropertyName;
-
- ///
- /// The typename of the property value (fullname including namespace but not assembly).
- ///
char* PropertyTypeName;
};
};
@@ -345,17 +277,11 @@ public:
{
struct Data : PropertyTrack::Data
{
- ///
- /// The keyframes count.
- ///
int32 KeyframesCount;
};
struct Runtime : PropertyTrack::Runtime
{
- ///
- /// The keyframes count.
- ///
int32 KeyframesCount;
///
@@ -382,22 +308,12 @@ public:
struct Data : PropertyTrack::Data
{
- ///
- /// The keyframes count.
- ///
int32 KeyframesCount;
};
struct Runtime : PropertyTrack::Runtime
{
- ///
- /// The cached curve data type.
- ///
DataTypes DataType;
-
- ///
- /// The keyframes count.
- ///
int32 KeyframesCount;
///
@@ -411,17 +327,11 @@ public:
{
struct Data : PropertyTrack::Data
{
- ///
- /// The keyframes count.
- ///
int32 KeyframesCount;
};
struct Runtime : PropertyTrack::Runtime
{
- ///
- /// The keyframes count.
- ///
int32 KeyframesCount;
// ..followed by the keyframes times and the values arrays (separate)
@@ -486,19 +396,12 @@ public:
{
struct Data : ObjectTrack::Data
{
- ///
- /// The start frame of the media play begin.
- ///
- int32 StartFrame;
-
- ///
- /// The total duration of the media playback in the timeline sequence frames amount.
- ///
- int32 DurationFrames;
};
struct Runtime : ObjectTrack::Runtime
{
+ int32 Count;
+ Media* Media;
};
};
@@ -534,11 +437,7 @@ public:
///
/// Gets the animation duration (in seconds).
///
- /// The animation duration (in seconds).
- API_PROPERTY() FORCE_INLINE float GetDuration() const
- {
- return DurationFrames / FramesPerSecond;
- }
+ API_PROPERTY() float GetDuration() const;
public:
diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp
index 776d34dbd..1890b568d 100644
--- a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp
+++ b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp
@@ -540,13 +540,18 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
{
case SceneAnimation::Track::Types::PostProcessMaterial:
{
- const auto trackData = track.GetData();
- const float startTime = trackData->StartFrame / fps;
- const float durationTime = trackData->DurationFrames / fps;
- const bool isActive = Math::IsInRange(time, startTime, startTime + durationTime);
- if (isActive && _postFxSettings.PostFxMaterials.Materials.Count() < POST_PROCESS_SETTINGS_MAX_MATERIALS)
+ const auto runtimeData = track.GetRuntimeData();
+ for (int32 k = 0; k < runtimeData->Count; k++)
{
- _postFxSettings.PostFxMaterials.Materials.Add(track.Asset.As());
+ const auto& media = runtimeData->Media[k];
+ const float startTime = media.StartFrame / fps;
+ const float durationTime = media.DurationFrames / fps;
+ const bool isActive = Math::IsInRange(time, startTime, startTime + durationTime);
+ if (isActive && _postFxSettings.PostFxMaterials.Materials.Count() < POST_PROCESS_SETTINGS_MAX_MATERIALS)
+ {
+ _postFxSettings.PostFxMaterials.Materials.Add(track.Asset.As());
+ break;
+ }
}
break;
}
@@ -648,29 +653,40 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
const auto clip = track.Asset.As();
if (!clip || !clip->IsLoaded())
break;
- const auto trackData = track.GetData();
const auto runtimeData = track.GetRuntimeData();
- const float startTime = trackData->StartFrame / fps;
- const float durationTime = trackData->DurationFrames / fps;
- const bool loop = ((int32)track.Flag & (int32)SceneAnimation::Track::Flags::Loop) == (int32)SceneAnimation::Track::Flags::Loop;
- float mediaTime = time - startTime;
- auto& state = _tracks[stateIndexOffset + track.TrackStateIndex];
- auto audioSource = state.Object.As();
- if (!audioSource)
+ float mediaTime = -1, mediaDuration, playTime;
+ for (int32 k = 0; k < runtimeData->Count; k++)
{
- // Spawn audio source to play the clip
- audioSource = New();
- audioSource->SetStaticFlags(StaticFlags::None);
- audioSource->HideFlags = HideFlags::FullyHidden;
- audioSource->Clip = clip;
- audioSource->SetIsLooping(loop);
- audioSource->SetParent(this, false, false);
- _subActors.Add(audioSource);
- state.Object = audioSource;
+ const auto& media = runtimeData->Media[k];
+ const float startTime = media.StartFrame / fps;
+ const float durationTime = media.DurationFrames / fps;
+ if (Math::IsInRange(time, startTime, startTime + durationTime))
+ {
+ mediaTime = time - startTime;
+ playTime = mediaTime + media.Offset;
+ mediaDuration = durationTime;
+ break;
+ }
}
- if (mediaTime >= 0.0f && mediaTime <= durationTime)
+ auto& state = _tracks[stateIndexOffset + track.TrackStateIndex];
+ auto audioSource = state.Object.As();
+ if (mediaTime >= 0.0f && mediaTime <= mediaDuration)
{
+ const bool loop = ((int32)track.Flag & (int32)SceneAnimation::Track::Flags::Loop) == (int32)SceneAnimation::Track::Flags::Loop;
+ if (!audioSource)
+ {
+ // Spawn audio source to play the clip
+ audioSource = New();
+ audioSource->SetStaticFlags(StaticFlags::None);
+ audioSource->HideFlags = HideFlags::FullyHidden;
+ audioSource->Clip = clip;
+ audioSource->SetIsLooping(loop);
+ audioSource->SetParent(this, false, false);
+ _subActors.Add(audioSource);
+ state.Object = audioSource;
+ }
+
// Sample volume track
float volume = 1.0f;
if (runtimeData->VolumeTrackIndex != -1)
@@ -680,7 +696,9 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
if (volumeTrackRuntimeData)
{
SceneAnimation::AudioVolumeTrack::CurveType::KeyFrameData data(volumeTrackRuntimeData->Keyframes, volumeTrackRuntimeData->KeyframesCount);
- volumeCurve.Evaluate(data, volume, mediaTime, false);
+ const auto& firstMedia = runtimeData->Media[0];
+ auto firstMediaTime = time - firstMedia.StartFrame / fps;
+ volumeCurve.Evaluate(data, volume, firstMediaTime, false);
}
}
@@ -688,9 +706,9 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
if (loop)
{
// Loop position
- mediaTime = Math::Mod(mediaTime, clipLength);
+ playTime = Math::Mod(playTime, clipLength);
}
- else if (mediaTime >= clipLength)
+ else if (playTime >= clipLength)
{
// Stop updating after end
break;
@@ -708,19 +726,19 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
// Synchronize playback position
const float maxAudioLag = 0.3f;
const auto audioTime = audioSource->GetTime();
- //LOG(Info, "Audio: {0}, Media : {1}", audioTime, mediaTime);
- if (Math::Abs(audioTime - mediaTime) > maxAudioLag &&
- Math::Abs(audioTime + clipLength - mediaTime) > maxAudioLag &&
- Math::Abs(mediaTime + clipLength - audioTime) > maxAudioLag)
+ //LOG(Info, "Audio: {0}, Media : {1}", audioTime, playTime);
+ if (Math::Abs(audioTime - playTime) > maxAudioLag &&
+ Math::Abs(audioTime + clipLength - playTime) > maxAudioLag &&
+ Math::Abs(playTime + clipLength - audioTime) > maxAudioLag)
{
- audioSource->SetTime(mediaTime);
+ audioSource->SetTime(playTime);
//LOG(Info, "Set Time (current audio time: {0})", audioSource->GetTime());
}
// Keep playing
audioSource->Play();
}
- else
+ else if (audioSource)
{
// End playback
audioSource->Stop();
@@ -998,6 +1016,27 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
}
case SceneAnimation::Track::Types::CameraCut:
{
+ // Check if any camera cut media on a track is active
+ bool isActive = false;
+ const auto runtimeData = track.GetRuntimeData();
+ for (int32 k = 0; k < runtimeData->Count; k++)
+ {
+ const auto& media = runtimeData->Media[k];
+ const float startTime = media.StartFrame / fps;
+ const float durationTime = media.DurationFrames / fps;
+ if (Math::IsInRange(time, startTime, startTime + durationTime))
+ {
+ isActive = true;
+ break;
+ }
+ }
+ if (!isActive)
+ {
+ // Skip updating child tracks if the current position is outside the media clip range
+ j += track.ChildrenCount;
+ break;
+ }
+
// Cache actor to animate
const auto trackData = track.GetData();
auto& state = _tracks[stateIndexOffset + track.TrackStateIndex];
@@ -1017,23 +1056,11 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
}
state.ManagedObject = state.Object.GetOrCreateManagedInstance();
- // Use camera
- _isUsingCameraCuts = true;
- const float startTime = trackData->StartFrame / fps;
- const float durationTime = trackData->DurationFrames / fps;
- float mediaTime = time - startTime;
- if (mediaTime >= 0.0f && mediaTime <= durationTime)
+ // Override camera
+ if (_cameraCutCam == nullptr)
{
- if (_cameraCutCam == nullptr)
- {
- // Override camera
- _cameraCutCam = (Camera*)state.Object.Get();
- }
- }
- else
- {
- // Skip updating child tracks if the current position is outside the media clip range
- j += track.ChildrenCount;
+ _cameraCutCam = (Camera*)state.Object.Get();
+ _isUsingCameraCuts = true;
}
break;
}
diff --git a/Source/Engine/Particles/ParticleSystem.cpp b/Source/Engine/Particles/ParticleSystem.cpp
index 64b34c718..7d8ffe863 100644
--- a/Source/Engine/Particles/ParticleSystem.cpp
+++ b/Source/Engine/Particles/ParticleSystem.cpp
@@ -51,7 +51,7 @@ BytesContainer ParticleSystem::LoadTimeline()
MemoryWriteStream stream(512);
{
// Save properties
- stream.WriteInt32(3);
+ stream.WriteInt32(4);
stream.WriteFloat(FramesPerSecond);
stream.WriteInt32(DurationFrames);
@@ -368,7 +368,8 @@ Asset::LoadResult ParticleSystem::load()
break;
}
- case 3:
+ case 3: // [Deprecated on 03.09.2021 expires on 03.09.2023]
+ case 4:
{
// Load properties
stream.ReadFloat(&FramesPerSecond);