Add support for multiple media events on audio, postfx and camera tracks in Scene Animations
#519
This commit is contained in:
@@ -114,6 +114,11 @@ namespace FlaxEditor.GUI.Timeline
|
||||
/// </summary>
|
||||
public event Action StartFrameChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the end frame of the media (start + duration).
|
||||
/// </summary>
|
||||
public int EndFrame => _startFrame + _durationFrames;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the total duration of the media event in the timeline sequence frames amount.
|
||||
/// </summary>
|
||||
@@ -175,6 +180,11 @@ namespace FlaxEditor.GUI.Timeline
|
||||
/// </summary>
|
||||
public object PropertiesEditObject;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this media can be split.
|
||||
/// </summary>
|
||||
public bool CanSplit;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Media"/> class.
|
||||
/// </summary>
|
||||
@@ -258,6 +268,22 @@ namespace FlaxEditor.GUI.Timeline
|
||||
Width = Duration * Timeline.UnitsPerSecond * _timeline.Zoom;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits the media at the specified frame.
|
||||
/// </summary>
|
||||
/// <param name="frame">The frame to split at.</param>
|
||||
/// <returns>The another media created after this media split.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void GetDesireClientArea(out Rectangle rect)
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace FlaxEditor.GUI.Timeline
|
||||
new KeyValuePair<float, string>(0, "Custom"),
|
||||
};
|
||||
|
||||
internal const int FormatVersion = 3;
|
||||
internal const int FormatVersion = 4;
|
||||
|
||||
/// <summary>
|
||||
/// The base class for timeline properties proxy objects.
|
||||
@@ -1584,6 +1584,39 @@ namespace FlaxEditor.GUI.Timeline
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits the media (all or selected only) at the given frame.
|
||||
/// </summary>
|
||||
/// <param name="frame">The frame to split at.</param>
|
||||
public void Split(int frame)
|
||||
{
|
||||
List<IUndoAction> 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<IUndoAction>();
|
||||
actions.Add(new EditTrackAction(this, track, trackData, EditTrackAction.CaptureData(track)));
|
||||
}
|
||||
}
|
||||
if (actions != null)
|
||||
Undo.AddAction(new MultiUndoAction(actions, "Split"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called once to setup the drag drop handling for the timeline (lazy init on first drag action).
|
||||
/// </summary>
|
||||
@@ -1979,6 +2012,9 @@ namespace FlaxEditor.GUI.Timeline
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case KeyboardKeys.S:
|
||||
Split(CurrentFrame);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -171,6 +171,16 @@ namespace FlaxEditor.GUI.Timeline
|
||||
/// </summary>
|
||||
public bool Loop;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum amount of media items for this track.
|
||||
/// </summary>
|
||||
public int MinMediaCount = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum amount of media items for this track.
|
||||
/// </summary>
|
||||
public int MaxMediaCount = 1024;
|
||||
|
||||
/// <summary>
|
||||
/// The track archetype.
|
||||
/// </summary>
|
||||
|
||||
@@ -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.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.GUI.Timeline.Media" />
|
||||
public class AudioMedia : SingleMediaAssetMedia
|
||||
public class AudioMedia : Media
|
||||
{
|
||||
/// <summary>
|
||||
/// 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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Playback offset of the audio (in seconds).
|
||||
/// </summary>
|
||||
public float Offset
|
||||
{
|
||||
get => Preview.ViewOffset;
|
||||
set
|
||||
{
|
||||
if (Mathf.NearEqual(Preview.ViewOffset, value))
|
||||
return;
|
||||
Preview.ViewOffset = value;
|
||||
Timeline?.MarkAsEdited();
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class Proxy : ProxyBase<AudioTrack, AudioMedia>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the audio clip to play.
|
||||
/// </summary>
|
||||
[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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the audio clip looping mode.
|
||||
/// </summary>
|
||||
[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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Proxy(AudioTrack track, AudioMedia media)
|
||||
: base(track, media)
|
||||
{
|
||||
@@ -70,6 +87,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
/// <inheritdoc />
|
||||
public AudioMedia()
|
||||
{
|
||||
CanSplit = true;
|
||||
Preview = new AudioClipPreview
|
||||
{
|
||||
AnchorPreset = AnchorPresets.StretchAll,
|
||||
@@ -79,12 +97,30 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnStartFrameChanged()
|
||||
{
|
||||
base.OnStartFrameChanged();
|
||||
|
||||
if (Track != null && Track.SubTracks.Count != 0 && Track.SubTracks[0] is AudioVolumeTrack volumeTrack)
|
||||
volumeTrack.UpdateCurve();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnDurationFramesChanged()
|
||||
{
|
||||
base.OnDurationFramesChanged();
|
||||
|
||||
if (Track != null && Track.SubTracks.Count != 0 && Track.SubTracks[0] is AudioVolumeTrack volumeTrack)
|
||||
volumeTrack.UpdateCurve();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -94,6 +130,17 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
|
||||
Preview.ViewScale = Timeline.UnitsPerSecond / AudioClipPreview.UnitsPerSecond * Timeline.Zoom;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -123,46 +170,46 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
var e = (AudioTrack)track;
|
||||
Guid id = stream.ReadGuid();
|
||||
e.Asset = FlaxEngine.Content.LoadAsync<AudioClip>(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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the audio clip looping mode.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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
|
||||
/// <inheritdoc />
|
||||
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
|
||||
/// <inheritdoc />
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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
|
||||
/// </summary>
|
||||
public Camera Camera => Actor as Camera;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the camera track media.
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnObjectExistenceChanged(object obj)
|
||||
{
|
||||
base.OnObjectExistenceChanged(obj);
|
||||
|
||||
TrackMedia.UpdateThumbnails();
|
||||
UpdateThumbnails();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
/// The timeline media that represents a nested scene animation media event.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.GUI.Timeline.Media" />
|
||||
public class NestedSceneAnimationMedia : SingleMediaAssetMedia
|
||||
public class NestedSceneAnimationMedia : Media
|
||||
{
|
||||
private sealed class Proxy : ProxyBase<NestedSceneAnimationTrack, NestedSceneAnimationMedia>
|
||||
{
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
/// The timeline media that represents a particle miter playback media event.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.GUI.Timeline.Media" />
|
||||
public class ParticleEmitterMedia : SingleMediaAssetMedia
|
||||
public class ParticleEmitterMedia : Media
|
||||
{
|
||||
private sealed class Proxy : ProxyBase<ParticleEmitterTrack, ParticleEmitterMedia>
|
||||
{
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -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.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.GUI.Timeline.Media" />
|
||||
public class PostProcessMaterialMedia : SingleMediaAssetMedia
|
||||
public class PostProcessMaterialMedia : Media
|
||||
{
|
||||
private sealed class Proxy : ProxyBase<PostProcessMaterialTrack, PostProcessMaterialMedia>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the post process material to show.
|
||||
/// </summary>
|
||||
[EditorDisplay("General"), EditorOrder(10), Tooltip("The post process material to show.")]
|
||||
public MaterialBase PostProcessMaterial
|
||||
{
|
||||
@@ -32,12 +30,20 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PostProcessMaterialMedia"/> class.
|
||||
/// </summary>
|
||||
public PostProcessMaterialMedia()
|
||||
{
|
||||
CanSplit = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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<MaterialBase>(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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -100,6 +118,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
public PostProcessMaterialTrack(ref TrackCreateOptions options)
|
||||
: base(ref options)
|
||||
{
|
||||
MinMediaCount = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -8,18 +8,6 @@ using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
{
|
||||
/// <summary>
|
||||
/// The timeline media that represents a media event with an asset reference.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.GUI.Timeline.Media" />
|
||||
public class SingleMediaAssetMedia : Media
|
||||
{
|
||||
/// <summary>
|
||||
/// The asset id.
|
||||
/// </summary>
|
||||
public Guid Asset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The base class for timeline tracks that use single media with an asset reference.
|
||||
/// </summary>
|
||||
@@ -28,26 +16,29 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
/// <seealso cref="FlaxEditor.GUI.Timeline.Track" />
|
||||
public abstract class SingleMediaAssetTrack<TAsset, TMedia> : SingleMediaTrack<TMedia>
|
||||
where TAsset : Asset
|
||||
where TMedia : SingleMediaAssetMedia, new()
|
||||
where TMedia : Media, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// The asset reference picker control.
|
||||
/// </summary>
|
||||
protected readonly AssetPicker _picker;
|
||||
|
||||
/// <summary>
|
||||
/// The asset id.
|
||||
/// </summary>
|
||||
public Guid AssetID;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the asset.
|
||||
/// </summary>
|
||||
public TAsset Asset
|
||||
{
|
||||
get => FlaxEngine.Content.LoadAsync<TAsset>(TrackMedia.Asset);
|
||||
get => FlaxEngine.Content.LoadAsync<TAsset>(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();
|
||||
|
||||
@@ -44,9 +44,16 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
/// <inheritdoc />
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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<PostProcessMaterialTrack::Data>();
|
||||
track.Data = trackData;
|
||||
track.Asset = Content::LoadAsync<MaterialBase>(trackData->AssetID);
|
||||
const auto trackRuntime = _runtimeData.Move<PostProcessMaterialTrack::Runtime>();
|
||||
track.RuntimeData = trackRuntime;
|
||||
if (version <= 3)
|
||||
{
|
||||
// [Deprecated on 03.09.2021 expires on 03.09.2023]
|
||||
trackRuntime->Count = 1;
|
||||
trackRuntime->Media = stream.Read<Media>();
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.ReadInt32(&trackRuntime->Count);
|
||||
trackRuntime->Media = stream.Read<Media>(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<AudioTrack::Media>();
|
||||
stream.ReadInt32(&trackRuntime->Media->StartFrame);
|
||||
stream.ReadInt32(&trackRuntime->Media->DurationFrames);
|
||||
trackRuntime->Media->Offset = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.ReadInt32(&trackRuntime->Count);
|
||||
trackRuntime->Media = stream.Read<AudioTrack::Media>(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<CameraCutTrack::Runtime>();
|
||||
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<Media>();
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.ReadInt32(&trackRuntime->Count);
|
||||
trackRuntime->Media = stream.Read<Media>(trackRuntime->Count);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -129,24 +129,23 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
struct Media
|
||||
{
|
||||
int32 StartFrame;
|
||||
int32 DurationFrames;
|
||||
};
|
||||
|
||||
struct PostProcessMaterialTrack
|
||||
{
|
||||
struct Data
|
||||
{
|
||||
/// <summary>
|
||||
/// The PostFx material asset ID.
|
||||
/// </summary>
|
||||
Guid AssetID;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The start frame of the media play begin.
|
||||
/// </summary>
|
||||
int32 StartFrame;
|
||||
|
||||
/// <summary>
|
||||
/// The total duration of the media playback in the timeline sequence frames amount.
|
||||
/// </summary>
|
||||
int32 DurationFrames;
|
||||
struct Runtime
|
||||
{
|
||||
int32 Count;
|
||||
Media* Media;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -154,19 +153,8 @@ public:
|
||||
{
|
||||
struct Data
|
||||
{
|
||||
/// <summary>
|
||||
/// The scene animation asset ID.
|
||||
/// </summary>
|
||||
Guid AssetID;
|
||||
|
||||
/// <summary>
|
||||
/// The start frame of the media play begin.
|
||||
/// </summary>
|
||||
int32 StartFrame;
|
||||
|
||||
/// <summary>
|
||||
/// The total duration of the media playback in the timeline sequence frames amount.
|
||||
/// </summary>
|
||||
int32 DurationFrames;
|
||||
};
|
||||
|
||||
@@ -185,57 +173,36 @@ public:
|
||||
|
||||
struct Data
|
||||
{
|
||||
/// <summary>
|
||||
/// The start frame of the media play begin.
|
||||
/// </summary>
|
||||
int32 StartFrame;
|
||||
|
||||
/// <summary>
|
||||
/// The total duration of the media playback in the timeline sequence frames amount.
|
||||
/// </summary>
|
||||
int32 DurationFrames;
|
||||
|
||||
/// <summary>
|
||||
/// The gradient stops count.
|
||||
/// </summary>
|
||||
int32 GradientStopsCount;
|
||||
};
|
||||
|
||||
struct Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// The pointer to the gradient stops array (items count is GradientStopsCount).
|
||||
/// </summary>
|
||||
GradientStop* GradientStops;
|
||||
};
|
||||
};
|
||||
|
||||
struct AudioTrack
|
||||
{
|
||||
struct Media
|
||||
{
|
||||
int32 StartFrame;
|
||||
int32 DurationFrames;
|
||||
float Offset;
|
||||
};
|
||||
|
||||
struct Data
|
||||
{
|
||||
/// <summary>
|
||||
/// The audio clip asset ID.
|
||||
/// </summary>
|
||||
Guid AssetID;
|
||||
|
||||
/// <summary>
|
||||
/// The start frame of the media play begin.
|
||||
/// </summary>
|
||||
int32 StartFrame;
|
||||
|
||||
/// <summary>
|
||||
/// The total duration of the media playback in the timeline sequence frames amount.
|
||||
/// </summary>
|
||||
int32 DurationFrames;
|
||||
};
|
||||
|
||||
struct Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// The index of the volume track. If not used then value is -1. Assigned on load.
|
||||
/// </summary>
|
||||
int32 VolumeTrackIndex;
|
||||
int32 Count;
|
||||
Media* Media;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -245,22 +212,12 @@ public:
|
||||
|
||||
struct Data
|
||||
{
|
||||
/// <summary>
|
||||
/// The keyframes count.
|
||||
/// </summary>
|
||||
int32 KeyframesCount;
|
||||
};
|
||||
|
||||
struct Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// The keyframes count.
|
||||
/// </summary>
|
||||
int32 KeyframesCount;
|
||||
|
||||
/// <summary>
|
||||
/// The keyframes array (items count is KeyframesCount).
|
||||
/// </summary>
|
||||
BezierCurveKeyframe<float>* Keyframes;
|
||||
};
|
||||
};
|
||||
@@ -269,9 +226,6 @@ public:
|
||||
{
|
||||
struct Data
|
||||
{
|
||||
/// <summary>
|
||||
/// The object ID.
|
||||
/// </summary>
|
||||
Guid ID;
|
||||
};
|
||||
|
||||
@@ -306,37 +260,15 @@ public:
|
||||
{
|
||||
struct Data
|
||||
{
|
||||
/// <summary>
|
||||
/// The property value data size (in bytes).
|
||||
/// </summary>
|
||||
int32 ValueSize;
|
||||
|
||||
/// <summary>
|
||||
/// The name length.
|
||||
/// </summary>
|
||||
int32 PropertyNameLength;
|
||||
|
||||
/// <summary>
|
||||
/// The typename length.
|
||||
/// </summary>
|
||||
int32 PropertyTypeNameLength;
|
||||
};
|
||||
|
||||
struct Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// The property value data size (in bytes).
|
||||
/// </summary>
|
||||
int32 ValueSize;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the property (just a member name).
|
||||
/// </summary>
|
||||
char* PropertyName;
|
||||
|
||||
/// <summary>
|
||||
/// The typename of the property value (fullname including namespace but not assembly).
|
||||
/// </summary>
|
||||
char* PropertyTypeName;
|
||||
};
|
||||
};
|
||||
@@ -345,17 +277,11 @@ public:
|
||||
{
|
||||
struct Data : PropertyTrack::Data
|
||||
{
|
||||
/// <summary>
|
||||
/// The keyframes count.
|
||||
/// </summary>
|
||||
int32 KeyframesCount;
|
||||
};
|
||||
|
||||
struct Runtime : PropertyTrack::Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// The keyframes count.
|
||||
/// </summary>
|
||||
int32 KeyframesCount;
|
||||
|
||||
/// <summary>
|
||||
@@ -382,22 +308,12 @@ public:
|
||||
|
||||
struct Data : PropertyTrack::Data
|
||||
{
|
||||
/// <summary>
|
||||
/// The keyframes count.
|
||||
/// </summary>
|
||||
int32 KeyframesCount;
|
||||
};
|
||||
|
||||
struct Runtime : PropertyTrack::Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// The cached curve data type.
|
||||
/// </summary>
|
||||
DataTypes DataType;
|
||||
|
||||
/// <summary>
|
||||
/// The keyframes count.
|
||||
/// </summary>
|
||||
int32 KeyframesCount;
|
||||
|
||||
/// <summary>
|
||||
@@ -411,17 +327,11 @@ public:
|
||||
{
|
||||
struct Data : PropertyTrack::Data
|
||||
{
|
||||
/// <summary>
|
||||
/// The keyframes count.
|
||||
/// </summary>
|
||||
int32 KeyframesCount;
|
||||
};
|
||||
|
||||
struct Runtime : PropertyTrack::Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// The keyframes count.
|
||||
/// </summary>
|
||||
int32 KeyframesCount;
|
||||
|
||||
// ..followed by the keyframes times and the values arrays (separate)
|
||||
@@ -486,19 +396,12 @@ public:
|
||||
{
|
||||
struct Data : ObjectTrack::Data
|
||||
{
|
||||
/// <summary>
|
||||
/// The start frame of the media play begin.
|
||||
/// </summary>
|
||||
int32 StartFrame;
|
||||
|
||||
/// <summary>
|
||||
/// The total duration of the media playback in the timeline sequence frames amount.
|
||||
/// </summary>
|
||||
int32 DurationFrames;
|
||||
};
|
||||
|
||||
struct Runtime : ObjectTrack::Runtime
|
||||
{
|
||||
int32 Count;
|
||||
Media* Media;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -534,11 +437,7 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the animation duration (in seconds).
|
||||
/// </summary>
|
||||
/// <returns>The animation duration (in seconds).</returns>
|
||||
API_PROPERTY() FORCE_INLINE float GetDuration() const
|
||||
{
|
||||
return DurationFrames / FramesPerSecond;
|
||||
}
|
||||
API_PROPERTY() float GetDuration() const;
|
||||
|
||||
public:
|
||||
|
||||
|
||||
@@ -540,13 +540,18 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
|
||||
{
|
||||
case SceneAnimation::Track::Types::PostProcessMaterial:
|
||||
{
|
||||
const auto trackData = track.GetData<SceneAnimation::PostProcessMaterialTrack::Data>();
|
||||
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<SceneAnimation::PostProcessMaterialTrack::Runtime>();
|
||||
for (int32 k = 0; k < runtimeData->Count; k++)
|
||||
{
|
||||
_postFxSettings.PostFxMaterials.Materials.Add(track.Asset.As<MaterialBase>());
|
||||
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<MaterialBase>());
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -648,29 +653,40 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
|
||||
const auto clip = track.Asset.As<AudioClip>();
|
||||
if (!clip || !clip->IsLoaded())
|
||||
break;
|
||||
const auto trackData = track.GetData<SceneAnimation::AudioTrack::Data>();
|
||||
const auto runtimeData = track.GetRuntimeData<SceneAnimation::AudioTrack::Runtime>();
|
||||
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<AudioSource>();
|
||||
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>();
|
||||
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<AudioSource>();
|
||||
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>();
|
||||
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<SceneAnimation::CameraCutTrack::Runtime>();
|
||||
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<SceneAnimation::CameraCutTrack::Data>();
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user