Merge branch 'master' into mac
This commit is contained in:
@@ -137,7 +137,7 @@ extern FLAXENGINE_API const Char* ToString(const BuildConfiguration configuratio
|
||||
/// <summary>
|
||||
/// Game cooking temporary data.
|
||||
/// </summary>
|
||||
API_CLASS(Sealed, Namespace="FlaxEditor") class FLAXENGINE_API CookingData : public PersistentScriptingObject
|
||||
API_CLASS(Sealed, Namespace="FlaxEditor") class FLAXENGINE_API CookingData : public ScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE(CookingData);
|
||||
public:
|
||||
|
||||
@@ -171,7 +171,7 @@ CookingData::Statistics::Statistics()
|
||||
}
|
||||
|
||||
CookingData::CookingData(const SpawnParams& params)
|
||||
: PersistentScriptingObject(params)
|
||||
: ScriptingObject(params)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -179,12 +179,12 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
|
||||
if (file)
|
||||
{
|
||||
auto now = DateTime::Now();
|
||||
file->WriteTextFormatted(
|
||||
file->WriteText(StringAnsi::Format(
|
||||
fileTemplate.Get()
|
||||
, gameSettings->ProductName.ToStringAnsi()
|
||||
, gameSettings->CompanyName.ToStringAnsi()
|
||||
, now.GetYear()
|
||||
);
|
||||
));
|
||||
hasError = file->HasError();
|
||||
Delete(file);
|
||||
}
|
||||
@@ -210,10 +210,10 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
|
||||
bool hasError = true;
|
||||
if (file)
|
||||
{
|
||||
file->WriteTextFormatted(
|
||||
file->WriteText(StringAnsi::Format(
|
||||
fileTemplate.Get()
|
||||
, defaultNamespace.ToStringAnsi() // {0} Default Namespace
|
||||
);
|
||||
));
|
||||
hasError = file->HasError();
|
||||
Delete(file);
|
||||
}
|
||||
@@ -264,11 +264,11 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
|
||||
bool hasError = true;
|
||||
if (file)
|
||||
{
|
||||
file->WriteTextFormatted(
|
||||
file->WriteText(StringAnsi::Format(
|
||||
fileTemplate.Get()
|
||||
, autoRotationPreferences.Get()
|
||||
, preferredLaunchWindowingMode.Get()
|
||||
);
|
||||
));
|
||||
hasError = file->HasError();
|
||||
Delete(file);
|
||||
}
|
||||
@@ -296,12 +296,12 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
|
||||
bool hasError = true;
|
||||
if (file)
|
||||
{
|
||||
file->WriteTextFormatted(
|
||||
file->WriteText(StringAnsi::Format(
|
||||
fileTemplate.Get()
|
||||
, projectName.ToStringAnsi() // {0} Project Name
|
||||
, mode // {1} Platform Mode
|
||||
, projectGuid.ToStringAnsi() // {2} Project ID
|
||||
);
|
||||
));
|
||||
hasError = file->HasError();
|
||||
Delete(file);
|
||||
}
|
||||
@@ -344,14 +344,14 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
|
||||
bool hasError = true;
|
||||
if (file)
|
||||
{
|
||||
file->WriteTextFormatted(
|
||||
file->WriteText(StringAnsi::Format(
|
||||
fileTemplate.Get()
|
||||
, projectName.ToStringAnsi() // {0} Project Name
|
||||
, mode // {1} Platform Mode
|
||||
, projectGuid.Get() // {2} Project ID
|
||||
, filesInclude.ToString().ToStringAnsi() // {3} Files to include
|
||||
, defaultNamespace.ToStringAnsi() // {4} Default Namespace
|
||||
);
|
||||
));
|
||||
hasError = file->HasError();
|
||||
Delete(file);
|
||||
}
|
||||
@@ -394,13 +394,13 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
|
||||
bool hasError = true;
|
||||
if (file)
|
||||
{
|
||||
file->WriteTextFormatted(
|
||||
file->WriteText(StringAnsi::Format(
|
||||
fileTemplate.Get()
|
||||
, projectName.ToStringAnsi() // {0} Display Name
|
||||
, gameSettings->CompanyName.ToStringAnsi() // {1} Company Name
|
||||
, productId.ToStringAnsi() // {2} Product ID
|
||||
, defaultNamespace.ToStringAnsi() // {3} Default Namespace
|
||||
);
|
||||
));
|
||||
hasError = file->HasError();
|
||||
Delete(file);
|
||||
}
|
||||
|
||||
@@ -102,6 +102,16 @@ namespace FlaxEditor.CustomEditors
|
||||
/// </summary>
|
||||
protected virtual void OnModified()
|
||||
{
|
||||
var parent = ParentEditor;
|
||||
while (parent != null)
|
||||
{
|
||||
if (parent is SyncPointEditor syncPointEditor)
|
||||
{
|
||||
syncPointEditor.OnModified();
|
||||
break;
|
||||
}
|
||||
parent = parent.ParentEditor;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace FlaxEditor.GUI.Timeline
|
||||
/// </summary>
|
||||
/// <param name="undo">The undo/redo to use for the history actions recording. Optional, can be null to disable undo support.</param>
|
||||
public AnimationTimeline(FlaxEditor.Undo undo)
|
||||
: base(PlaybackButtons.Play | PlaybackButtons.Stop, undo, false, false)
|
||||
: base(PlaybackButtons.Play | PlaybackButtons.Stop, undo, false, true)
|
||||
{
|
||||
PlaybackState = PlaybackStates.Seeking;
|
||||
ShowPreviewValues = false;
|
||||
@@ -61,6 +61,7 @@ namespace FlaxEditor.GUI.Timeline
|
||||
// Setup track types
|
||||
TrackArchetypes.Add(AnimationChannelTrack.GetArchetype());
|
||||
TrackArchetypes.Add(AnimationChannelDataTrack.GetArchetype());
|
||||
TrackArchetypes.Add(AnimationEventTrack.GetArchetype());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -16,8 +16,8 @@ namespace FlaxEditor.GUI.Timeline.GUI
|
||||
private readonly Timeline _timeline;
|
||||
private float[] _tickSteps;
|
||||
private float[] _tickStrengths;
|
||||
private bool _leftMouseDown;
|
||||
private Vector2 _leftMouseDownPos = Vector2.Minimum;
|
||||
private bool _isSelecting;
|
||||
private Vector2 _selectingStartPos = Vector2.Minimum;
|
||||
private Vector2 _mousePos = Vector2.Minimum;
|
||||
|
||||
/// <summary>
|
||||
@@ -33,7 +33,7 @@ namespace FlaxEditor.GUI.Timeline.GUI
|
||||
|
||||
private void UpdateSelectionRectangle()
|
||||
{
|
||||
var selectionRect = Rectangle.FromPoints(_leftMouseDownPos, _mousePos);
|
||||
var selectionRect = Rectangle.FromPoints(_selectingStartPos, _mousePos);
|
||||
_timeline.OnKeyframesSelection(null, this, selectionRect);
|
||||
}
|
||||
|
||||
@@ -41,20 +41,17 @@ namespace FlaxEditor.GUI.Timeline.GUI
|
||||
public override bool OnMouseDown(Vector2 location, MouseButton button)
|
||||
{
|
||||
if (base.OnMouseDown(location, button))
|
||||
{
|
||||
_leftMouseDown = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
_mousePos = location;
|
||||
if (button == MouseButton.Left)
|
||||
{
|
||||
// Start selecting
|
||||
_leftMouseDown = true;
|
||||
_leftMouseDownPos = location;
|
||||
StartMouseCapture();
|
||||
_isSelecting = true;
|
||||
_selectingStartPos = location;
|
||||
_timeline.OnKeyframesDeselect(null);
|
||||
Focus();
|
||||
StartMouseCapture();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -65,19 +62,16 @@ namespace FlaxEditor.GUI.Timeline.GUI
|
||||
public override bool OnMouseUp(Vector2 location, MouseButton button)
|
||||
{
|
||||
_mousePos = location;
|
||||
|
||||
if (_leftMouseDown && button == MouseButton.Left)
|
||||
if (_isSelecting && button == MouseButton.Left)
|
||||
{
|
||||
// End selecting
|
||||
_leftMouseDown = false;
|
||||
_isSelecting = false;
|
||||
EndMouseCapture();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (base.OnMouseUp(location, button))
|
||||
{
|
||||
_leftMouseDown = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -88,7 +82,7 @@ namespace FlaxEditor.GUI.Timeline.GUI
|
||||
_mousePos = location;
|
||||
|
||||
// Selecting
|
||||
if (_leftMouseDown)
|
||||
if (_isSelecting)
|
||||
{
|
||||
UpdateSelectionRectangle();
|
||||
return;
|
||||
@@ -100,11 +94,24 @@ namespace FlaxEditor.GUI.Timeline.GUI
|
||||
/// <inheritdoc />
|
||||
public override void OnLostFocus()
|
||||
{
|
||||
_leftMouseDown = false;
|
||||
if (_isSelecting)
|
||||
{
|
||||
_isSelecting = false;
|
||||
EndMouseCapture();
|
||||
}
|
||||
|
||||
base.OnLostFocus();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnEndMouseCapture()
|
||||
{
|
||||
_isSelecting = false;
|
||||
EndMouseCapture();
|
||||
|
||||
base.OnEndMouseCapture();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IntersectsContent(ref Vector2 locationParent, out Vector2 location)
|
||||
{
|
||||
@@ -217,9 +224,9 @@ namespace FlaxEditor.GUI.Timeline.GUI
|
||||
}
|
||||
|
||||
// Draw selection rectangle
|
||||
if (_leftMouseDown)
|
||||
if (_isSelecting)
|
||||
{
|
||||
var selectionRect = Rectangle.FromPoints(_leftMouseDownPos, _mousePos);
|
||||
var selectionRect = Rectangle.FromPoints(_selectingStartPos, _mousePos);
|
||||
Render2D.FillRectangle(selectionRect, Color.Orange * 0.4f);
|
||||
Render2D.DrawRectangle(selectionRect, Color.Orange);
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ namespace FlaxEditor.GUI.Timeline
|
||||
/// <param name="media">The media.</param>
|
||||
protected ProxyBase(TTrack track, TMedia media)
|
||||
{
|
||||
Track = track ?? throw new ArgumentNullException(nameof(track));
|
||||
Track = track;
|
||||
Media = media ?? throw new ArgumentNullException(nameof(media));
|
||||
}
|
||||
}
|
||||
@@ -215,7 +215,7 @@ namespace FlaxEditor.GUI.Timeline
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this media can be resized (duration changed).
|
||||
/// </summary>
|
||||
public bool CanResize;
|
||||
public bool CanResize = true;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Media"/> class.
|
||||
@@ -229,8 +229,9 @@ namespace FlaxEditor.GUI.Timeline
|
||||
/// Called when showing timeline context menu to the user. Can be used to add custom buttons.
|
||||
/// </summary>
|
||||
/// <param name="menu">The menu.</param>
|
||||
/// <param name="time">The time (in seconds) at which context menu is shown (user clicked on a timeline).</param>
|
||||
/// <param name="controlUnderMouse">The found control under the mouse cursor.</param>
|
||||
public virtual void OnTimelineShowContextMenu(ContextMenu.ContextMenu menu, Control controlUnderMouse)
|
||||
public virtual void OnTimelineContextMenu(ContextMenu.ContextMenu menu, float time, Control controlUnderMouse)
|
||||
{
|
||||
if (CanDelete && Track.Media.Count > Track.MinMediaCount)
|
||||
menu.AddButton("Delete media", Delete);
|
||||
@@ -341,14 +342,15 @@ namespace FlaxEditor.GUI.Timeline
|
||||
var style = Style.Current;
|
||||
var bounds = new Rectangle(Vector2.Zero, Size);
|
||||
|
||||
var fillColor = style.Background * 1.5f;
|
||||
var fillColor = BackgroundColor.A > 0.0f ? BackgroundColor : style.Background * 1.5f;
|
||||
Render2D.FillRectangle(bounds, fillColor);
|
||||
|
||||
var isMovingWholeMedia = _isMoving && !_startMoveRightEdge && !_startMoveLeftEdge;
|
||||
var borderHighlightColor = style.BorderHighlighted;
|
||||
var moveColor = style.ProgressNormal;
|
||||
var selectedColor = style.BackgroundSelected;
|
||||
var moveThickness = 2.0f;
|
||||
var borderColor = isMovingWholeMedia ? moveColor : (IsMouseOver ? borderHighlightColor : style.BorderNormal);
|
||||
var borderColor = isMovingWholeMedia ? moveColor : (Timeline.SelectedMedia.Contains(this) ? selectedColor : (IsMouseOver ? borderHighlightColor : style.BorderNormal));
|
||||
Render2D.DrawRectangle(bounds, borderColor, isMovingWholeMedia ? moveThickness : 1.0f);
|
||||
if (_startMoveLeftEdge)
|
||||
{
|
||||
@@ -384,9 +386,26 @@ namespace FlaxEditor.GUI.Timeline
|
||||
_startMoveDuration = DurationFrames;
|
||||
_startMoveLeftEdge = MoveLeftEdgeRect.Contains(ref location) && CanResize;
|
||||
_startMoveRightEdge = MoveRightEdgeRect.Contains(ref location) && CanResize;
|
||||
|
||||
StartMouseCapture(true);
|
||||
if (_startMoveLeftEdge || _startMoveRightEdge)
|
||||
return true;
|
||||
|
||||
if (Root.GetKey(KeyboardKeys.Control))
|
||||
{
|
||||
// Add/Remove selection
|
||||
if (_timeline.SelectedMedia.Contains(this))
|
||||
_timeline.Deselect(this);
|
||||
else
|
||||
_timeline.Select(this, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Select
|
||||
if (!_timeline.SelectedMedia.Contains(this))
|
||||
_timeline.Select(this);
|
||||
}
|
||||
|
||||
_timeline.OnKeyframesMove(null, this, location, true, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -417,7 +436,8 @@ namespace FlaxEditor.GUI.Timeline
|
||||
}
|
||||
else
|
||||
{
|
||||
StartFrame = _startMoveStartFrame + moveDelta;
|
||||
// Move with global timeline selection
|
||||
_timeline.OnKeyframesMove(null, this, location, false, false);
|
||||
}
|
||||
|
||||
if (StartFrame != startFrame || DurationFrames != durationFrames)
|
||||
@@ -436,6 +456,15 @@ namespace FlaxEditor.GUI.Timeline
|
||||
{
|
||||
if (button == MouseButton.Left && _isMoving)
|
||||
{
|
||||
if (!_startMoveLeftEdge && !_startMoveRightEdge && !Root.GetKey(KeyboardKeys.Control))
|
||||
{
|
||||
var moveLocationDelta = Root.MousePosition - _startMoveLocation;
|
||||
if (moveLocationDelta.Length < 4.0f)
|
||||
{
|
||||
// No move so just select itself
|
||||
_timeline.Select(this);
|
||||
}
|
||||
}
|
||||
EndMoving();
|
||||
return true;
|
||||
}
|
||||
@@ -443,6 +472,20 @@ namespace FlaxEditor.GUI.Timeline
|
||||
return base.OnMouseUp(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDoubleClick(Vector2 location, MouseButton button)
|
||||
{
|
||||
if (base.OnMouseDoubleClick(location, button))
|
||||
return true;
|
||||
|
||||
if (PropertiesEditObject != null)
|
||||
{
|
||||
Timeline.ShowEditPopup(PropertiesEditObject, PointToParent(Timeline, location), Track);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnEndMouseCapture()
|
||||
{
|
||||
@@ -484,22 +527,30 @@ namespace FlaxEditor.GUI.Timeline
|
||||
private void EndMoving()
|
||||
{
|
||||
_isMoving = false;
|
||||
_startMoveLeftEdge = false;
|
||||
_startMoveRightEdge = false;
|
||||
|
||||
// Re-assign the media start/duration inside the undo recording block
|
||||
if (_startMoveStartFrame != _startFrame || _startMoveDuration != _durationFrames)
|
||||
if (_startMoveLeftEdge || _startMoveRightEdge)
|
||||
{
|
||||
var endMoveStartFrame = _startFrame;
|
||||
var endMoveDuration = _durationFrames;
|
||||
_startFrame = _startMoveStartFrame;
|
||||
_durationFrames = _startMoveDuration;
|
||||
using (new TrackUndoBlock(_tack))
|
||||
_startMoveLeftEdge = false;
|
||||
_startMoveRightEdge = false;
|
||||
|
||||
// Re-assign the media start/duration inside the undo recording block
|
||||
if (_startMoveStartFrame != _startFrame || _startMoveDuration != _durationFrames)
|
||||
{
|
||||
_startFrame = endMoveStartFrame;
|
||||
_durationFrames = endMoveDuration;
|
||||
var endMoveStartFrame = _startFrame;
|
||||
var endMoveDuration = _durationFrames;
|
||||
_startFrame = _startMoveStartFrame;
|
||||
_durationFrames = _startMoveDuration;
|
||||
using (new TrackUndoBlock(_tack))
|
||||
{
|
||||
_startFrame = endMoveStartFrame;
|
||||
_durationFrames = endMoveDuration;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Global timeline selection moving end
|
||||
_timeline.OnKeyframesMove(null, this, _mouseLocation, false, true);
|
||||
}
|
||||
|
||||
EndMouseCapture();
|
||||
}
|
||||
|
||||
@@ -219,6 +219,10 @@ namespace FlaxEditor.GUI.Timeline
|
||||
private bool _isRightMouseButtonDown;
|
||||
private Vector2 _rightMouseButtonDownPos;
|
||||
private Vector2 _rightMouseButtonMovePos;
|
||||
private Vector2 _mediaMoveStartPos;
|
||||
private int[] _mediaMoveStartFrames;
|
||||
private List<Track> _mediaMoveStartTracks;
|
||||
private byte[][] _mediaMoveStartData;
|
||||
private float _zoom = 1.0f;
|
||||
private bool _isMovingPositionHandle;
|
||||
private bool _canPlayPause = true, _canStop = true;
|
||||
@@ -1375,7 +1379,7 @@ namespace FlaxEditor.GUI.Timeline
|
||||
if (!addToSelection)
|
||||
{
|
||||
SelectedTracks.Clear();
|
||||
SelectedMedia.Clear();
|
||||
OnKeyframesDeselect(null);
|
||||
}
|
||||
SelectedMedia.Add(media);
|
||||
OnSelectionChanged();
|
||||
@@ -1425,47 +1429,40 @@ namespace FlaxEditor.GUI.Timeline
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the selected tracks/media events.
|
||||
/// Deletes the selected tracks.
|
||||
/// </summary>
|
||||
/// <param name="withUndo">True if use undo/redo action for track removing.</param>
|
||||
public void DeleteSelection(bool withUndo = true)
|
||||
public void DeleteSelectedTracks(bool withUndo = true)
|
||||
{
|
||||
if (SelectedMedia.Count > 0)
|
||||
if (SelectedTracks.Count == 0)
|
||||
return;
|
||||
var tracks = new List<Track>(SelectedTracks.Count);
|
||||
for (int i = 0; i < SelectedTracks.Count; i++)
|
||||
{
|
||||
throw new NotImplementedException("TODO: removing selected media events");
|
||||
GetTracks(SelectedTracks[i], tracks);
|
||||
}
|
||||
|
||||
if (SelectedTracks.Count > 0)
|
||||
SelectedTracks.Clear();
|
||||
if (withUndo && Undo != null && Undo.Enabled)
|
||||
{
|
||||
// Delete selected tracks
|
||||
var tracks = new List<Track>(SelectedTracks.Count);
|
||||
for (int i = 0; i < SelectedTracks.Count; i++)
|
||||
if (tracks.Count == 1)
|
||||
{
|
||||
GetTracks(SelectedTracks[i], tracks);
|
||||
Undo.AddAction(new AddRemoveTrackAction(this, tracks[0], false));
|
||||
}
|
||||
SelectedTracks.Clear();
|
||||
if (withUndo && Undo != null && Undo.Enabled)
|
||||
else
|
||||
{
|
||||
if (tracks.Count == 1)
|
||||
{
|
||||
Undo.AddAction(new AddRemoveTrackAction(this, tracks[0], false));
|
||||
}
|
||||
else
|
||||
{
|
||||
var actions = new List<IUndoAction>();
|
||||
for (int i = tracks.Count - 1; i >= 0; i--)
|
||||
actions.Add(new AddRemoveTrackAction(this, tracks[i], false));
|
||||
Undo.AddAction(new MultiUndoAction(actions, "Remove tracks"));
|
||||
}
|
||||
var actions = new List<IUndoAction>();
|
||||
for (int i = tracks.Count - 1; i >= 0; i--)
|
||||
actions.Add(new AddRemoveTrackAction(this, tracks[i], false));
|
||||
Undo.AddAction(new MultiUndoAction(actions, "Remove tracks"));
|
||||
}
|
||||
for (int i = tracks.Count - 1; i >= 0; i--)
|
||||
{
|
||||
tracks[i].ParentTrack = null;
|
||||
OnDeleteTrack(tracks[i]);
|
||||
}
|
||||
OnTracksChanged();
|
||||
MarkAsEdited();
|
||||
}
|
||||
for (int i = tracks.Count - 1; i >= 0; i--)
|
||||
{
|
||||
tracks[i].ParentTrack = null;
|
||||
OnDeleteTrack(tracks[i]);
|
||||
}
|
||||
OnTracksChanged();
|
||||
MarkAsEdited();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1539,6 +1536,33 @@ namespace FlaxEditor.GUI.Timeline
|
||||
MarkAsEdited();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the media.
|
||||
/// </summary>
|
||||
/// <param name="track">The track to add media to.</param>
|
||||
/// <param name="media">The media to add.</param>
|
||||
/// <param name="withUndo">True if use undo/redo action for media adding.</param>
|
||||
public void AddMedia(Track track, Media media, bool withUndo = true)
|
||||
{
|
||||
if (track == null || media == null)
|
||||
throw new ArgumentNullException();
|
||||
if (media.Track != null)
|
||||
throw new InvalidOperationException();
|
||||
if (withUndo && Undo != null && Undo.Enabled)
|
||||
{
|
||||
var before = EditTrackAction.CaptureData(track);
|
||||
track.AddMedia(media);
|
||||
var after = EditTrackAction.CaptureData(track);
|
||||
Undo.AddAction(new EditTrackAction(this, track, before, after));
|
||||
}
|
||||
else
|
||||
{
|
||||
track.AddMedia(media);
|
||||
}
|
||||
MarkAsEdited();
|
||||
Select(media);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called to delete media.
|
||||
/// </summary>
|
||||
@@ -1551,89 +1575,82 @@ namespace FlaxEditor.GUI.Timeline
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Duplicates the selected tracks/media events.
|
||||
/// Duplicates the selected tracks.
|
||||
/// </summary>
|
||||
/// <param name="withUndo">True if use undo/redo action for track duplication.</param>
|
||||
public void DuplicateSelection(bool withUndo = true)
|
||||
public void DuplicateSelectedTracks(bool withUndo = true)
|
||||
{
|
||||
if (SelectedMedia.Count > 0)
|
||||
if (SelectedTracks.Count == 0)
|
||||
return;
|
||||
var tracks = new List<Track>(SelectedTracks.Count);
|
||||
for (int i = 0; i < SelectedTracks.Count; i++)
|
||||
{
|
||||
throw new NotImplementedException("TODO: duplicating selected media events");
|
||||
GetTracks(SelectedTracks[i], tracks);
|
||||
}
|
||||
|
||||
if (SelectedTracks.Count > 0)
|
||||
var clones = new Track[tracks.Count];
|
||||
for (int i = 0; i < tracks.Count; i++)
|
||||
{
|
||||
// Duplicate selected tracks
|
||||
var tracks = new List<Track>(SelectedTracks.Count);
|
||||
for (int i = 0; i < SelectedTracks.Count; i++)
|
||||
var track = tracks[i];
|
||||
var options = new TrackCreateOptions
|
||||
{
|
||||
GetTracks(SelectedTracks[i], tracks);
|
||||
Archetype = track.Archetype,
|
||||
Flags = track.Flags,
|
||||
};
|
||||
var clone = options.Archetype.Create(options);
|
||||
clone.Name = track.CanRename ? GetValidTrackName(track.Name) : track.Name;
|
||||
clone.Color = track.Color;
|
||||
clone.IsExpanded = track.IsExpanded;
|
||||
byte[] data;
|
||||
using (var memory = new MemoryStream(512))
|
||||
using (var stream = new BinaryWriter(memory))
|
||||
{
|
||||
// TODO: reuse memory stream to improve tracks duplication performance
|
||||
options.Archetype.Save(track, stream);
|
||||
data = memory.ToArray();
|
||||
}
|
||||
var clones = new Track[tracks.Count];
|
||||
for (int i = 0; i < tracks.Count; i++)
|
||||
using (var memory = new MemoryStream(data))
|
||||
using (var stream = new BinaryReader(memory))
|
||||
{
|
||||
var track = tracks[i];
|
||||
var options = new TrackCreateOptions
|
||||
track.Archetype.Load(Timeline.FormatVersion, clone, stream);
|
||||
}
|
||||
var trackParent = track.ParentTrack;
|
||||
var trackIndex = track.TrackIndex + 1;
|
||||
if (trackParent != null && tracks.Contains(trackParent))
|
||||
{
|
||||
for (int j = 0; j < i; j++)
|
||||
{
|
||||
Archetype = track.Archetype,
|
||||
Flags = track.Flags,
|
||||
};
|
||||
var clone = options.Archetype.Create(options);
|
||||
clone.Name = track.CanRename ? GetValidTrackName(track.Name) : track.Name;
|
||||
clone.Color = track.Color;
|
||||
clone.IsExpanded = track.IsExpanded;
|
||||
byte[] data;
|
||||
using (var memory = new MemoryStream(512))
|
||||
using (var stream = new BinaryWriter(memory))
|
||||
{
|
||||
// TODO: reuse memory stream to improve tracks duplication performance
|
||||
options.Archetype.Save(track, stream);
|
||||
data = memory.ToArray();
|
||||
}
|
||||
using (var memory = new MemoryStream(data))
|
||||
using (var stream = new BinaryReader(memory))
|
||||
{
|
||||
track.Archetype.Load(Timeline.FormatVersion, clone, stream);
|
||||
}
|
||||
var trackParent = track.ParentTrack;
|
||||
var trackIndex = track.TrackIndex + 1;
|
||||
if (trackParent != null && tracks.Contains(trackParent))
|
||||
{
|
||||
for (int j = 0; j < i; j++)
|
||||
if (tracks[j] == trackParent)
|
||||
{
|
||||
if (tracks[j] == trackParent)
|
||||
{
|
||||
trackParent = clones[j];
|
||||
break;
|
||||
}
|
||||
trackParent = clones[j];
|
||||
break;
|
||||
}
|
||||
trackIndex--;
|
||||
}
|
||||
clone.ParentTrack = trackParent;
|
||||
clone.TrackIndex = trackIndex;
|
||||
track.OnDuplicated(clone);
|
||||
AddTrack(clone, false);
|
||||
clones[i] = clone;
|
||||
trackIndex--;
|
||||
}
|
||||
OnTracksOrderChanged();
|
||||
if (withUndo && Undo != null && Undo.Enabled)
|
||||
{
|
||||
if (clones.Length == 1)
|
||||
{
|
||||
Undo.AddAction(new AddRemoveTrackAction(this, clones[0], true));
|
||||
}
|
||||
else
|
||||
{
|
||||
var actions = new List<IUndoAction>();
|
||||
for (int i = 0; i < clones.Length; i++)
|
||||
actions.Add(new AddRemoveTrackAction(this, clones[i], true));
|
||||
Undo.AddAction(new MultiUndoAction(actions, "Remove tracks"));
|
||||
}
|
||||
}
|
||||
OnTracksChanged();
|
||||
MarkAsEdited();
|
||||
SelectedTracks[0].Focus();
|
||||
clone.ParentTrack = trackParent;
|
||||
clone.TrackIndex = trackIndex;
|
||||
track.OnDuplicated(clone);
|
||||
AddTrack(clone, false);
|
||||
clones[i] = clone;
|
||||
}
|
||||
OnTracksOrderChanged();
|
||||
if (withUndo && Undo != null && Undo.Enabled)
|
||||
{
|
||||
if (clones.Length == 1)
|
||||
{
|
||||
Undo.AddAction(new AddRemoveTrackAction(this, clones[0], true));
|
||||
}
|
||||
else
|
||||
{
|
||||
var actions = new List<IUndoAction>();
|
||||
for (int i = 0; i < clones.Length; i++)
|
||||
actions.Add(new AddRemoveTrackAction(this, clones[i], true));
|
||||
Undo.AddAction(new MultiUndoAction(actions, "Remove tracks"));
|
||||
}
|
||||
}
|
||||
OnTracksChanged();
|
||||
MarkAsEdited();
|
||||
SelectedTracks[0].Focus();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1885,6 +1902,9 @@ namespace FlaxEditor.GUI.Timeline
|
||||
if (!ContainsFocus)
|
||||
Focus();
|
||||
|
||||
var timelinePos = MediaPanel.PointFromParent(this, location);
|
||||
var time = (timelinePos.X - StartOffset) / (UnitsPerSecond * Zoom);
|
||||
|
||||
var controlUnderMouse = GetChildAtRecursive(location);
|
||||
var mediaUnderMouse = controlUnderMouse;
|
||||
while (mediaUnderMouse != null && !(mediaUnderMouse is Media))
|
||||
@@ -1895,19 +1915,31 @@ namespace FlaxEditor.GUI.Timeline
|
||||
var menu = new ContextMenu.ContextMenu();
|
||||
if (mediaUnderMouse is Media media)
|
||||
{
|
||||
media.OnTimelineShowContextMenu(menu, controlUnderMouse);
|
||||
media.OnTimelineContextMenu(menu, time, controlUnderMouse);
|
||||
if (media.PropertiesEditObject != null)
|
||||
{
|
||||
menu.AddButton("Edit media", () => ShowEditPopup(media.PropertiesEditObject, ref location, media.Track));
|
||||
menu.AddButton("Edit media", () => ShowEditPopup(media.PropertiesEditObject, location, media.Track));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
OnKeyframesDeselect(null);
|
||||
foreach (var track in _tracks)
|
||||
{
|
||||
if (Mathf.IsInRange(timelinePos.Y, track.Top, track.Bottom))
|
||||
{
|
||||
track.OnTimelineContextMenu(menu, time);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (PropertiesEditObject != null)
|
||||
{
|
||||
menu.AddButton("Edit timeline", () => ShowEditPopup(PropertiesEditObject, ref location, this));
|
||||
menu.AddButton("Edit timeline", () => ShowEditPopup(PropertiesEditObject, location, this));
|
||||
}
|
||||
if (_tracks.Count > 1)
|
||||
{
|
||||
menu.AddButton("Sort tracks", SortTracks).TooltipText = "Sorts sub tracks alphabetically";
|
||||
menu.AddButton("Sort tracks", SortTracks).TooltipText = "Sorts tracks alphabetically";
|
||||
}
|
||||
menu.AddSeparator();
|
||||
menu.AddButton("Reset zoom", () => Zoom = 1.0f);
|
||||
@@ -2067,6 +2099,9 @@ namespace FlaxEditor.GUI.Timeline
|
||||
case KeyboardKeys.S:
|
||||
Split(CurrentFrame);
|
||||
return true;
|
||||
case KeyboardKeys.Delete:
|
||||
OnKeyframesDelete(null);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -2089,7 +2124,7 @@ namespace FlaxEditor.GUI.Timeline
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <param name="location">The show location (in timeline space).</param>
|
||||
/// <param name="undoContext">The undo context object.</param>
|
||||
protected virtual void ShowEditPopup(object obj, ref Vector2 location, object undoContext = null)
|
||||
public virtual void ShowEditPopup(object obj, Vector2 location, object undoContext = null)
|
||||
{
|
||||
var popup = new PropertiesEditPopup(this, obj, undoContext);
|
||||
popup.Show(this, location);
|
||||
@@ -2145,6 +2180,11 @@ namespace FlaxEditor.GUI.Timeline
|
||||
if (_tracks[i] is IKeyframesEditorContext trackContext)
|
||||
trackContext.OnKeyframesDeselect(editor);
|
||||
}
|
||||
if (SelectedMedia.Count != 0)
|
||||
{
|
||||
SelectedMedia.Clear();
|
||||
OnSelectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -2152,17 +2192,38 @@ namespace FlaxEditor.GUI.Timeline
|
||||
{
|
||||
var globalControl = _backgroundArea;
|
||||
var globalRect = Rectangle.FromPoints(control.PointToParent(globalControl, selection.UpperLeft), control.PointToParent(globalControl, selection.BottomRight));
|
||||
var mediaControl = MediaPanel;
|
||||
var mediaRect = Rectangle.FromPoints(mediaControl.PointFromParent(globalRect.UpperLeft), mediaControl.PointFromParent(globalRect.BottomRight));
|
||||
var selectionChanged = false;
|
||||
if (SelectedMedia.Count != 0)
|
||||
{
|
||||
SelectedMedia.Clear();
|
||||
selectionChanged = true;
|
||||
}
|
||||
for (int i = 0; i < _tracks.Count; i++)
|
||||
{
|
||||
if (_tracks[i] is IKeyframesEditorContext trackContext)
|
||||
trackContext.OnKeyframesSelection(editor, globalControl, globalRect);
|
||||
|
||||
foreach (var media in _tracks[i].Media)
|
||||
{
|
||||
if (media.Bounds.Intersects(ref mediaRect))
|
||||
{
|
||||
SelectedMedia.Add(media);
|
||||
selectionChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (selectionChanged)
|
||||
{
|
||||
OnSelectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int OnKeyframesSelectionCount()
|
||||
{
|
||||
int result = 0;
|
||||
int result = SelectedMedia.Count;
|
||||
for (int i = 0; i < _tracks.Count; i++)
|
||||
{
|
||||
if (_tracks[i] is IKeyframesEditorContext trackContext)
|
||||
@@ -2179,6 +2240,45 @@ namespace FlaxEditor.GUI.Timeline
|
||||
if (_tracks[i] is IKeyframesEditorContext trackContext)
|
||||
trackContext.OnKeyframesDelete(editor);
|
||||
}
|
||||
|
||||
// Delete selected media events
|
||||
if (SelectedMedia.Count != 0)
|
||||
{
|
||||
if (Undo != null && Undo.Enabled)
|
||||
{
|
||||
// Undo per-track
|
||||
if (_mediaMoveStartTracks == null)
|
||||
_mediaMoveStartTracks = new List<Track>();
|
||||
else
|
||||
_mediaMoveStartTracks.Clear();
|
||||
for (var i = 0; i < SelectedMedia.Count; i++)
|
||||
{
|
||||
var media = SelectedMedia[i];
|
||||
if (!_mediaMoveStartTracks.Contains(media.Track))
|
||||
_mediaMoveStartTracks.Add(media.Track);
|
||||
}
|
||||
_mediaMoveStartData = new byte[_mediaMoveStartTracks.Count][];
|
||||
for (int i = 0; i < _mediaMoveStartData.Length; i++)
|
||||
_mediaMoveStartData[i] = EditTrackAction.CaptureData(_mediaMoveStartTracks[i]);
|
||||
}
|
||||
|
||||
foreach (var media in SelectedMedia.ToArray())
|
||||
OnDeleteMedia(media);
|
||||
|
||||
if (Undo != null && Undo.Enabled)
|
||||
{
|
||||
for (int i = 0; i < _mediaMoveStartData.Length; i++)
|
||||
{
|
||||
var track = _mediaMoveStartTracks[i];
|
||||
var before = _mediaMoveStartData[i];
|
||||
var after = EditTrackAction.CaptureData(track);
|
||||
if (!Utils.ArraysEqual(before, after))
|
||||
AddBatchedUndoAction(new EditTrackAction(this, track, before, after));
|
||||
}
|
||||
}
|
||||
|
||||
MarkAsEdited();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -2190,6 +2290,60 @@ namespace FlaxEditor.GUI.Timeline
|
||||
if (_tracks[i] is IKeyframesEditorContext trackContext)
|
||||
trackContext.OnKeyframesMove(editor, _backgroundArea, location, start, end);
|
||||
}
|
||||
if (SelectedMedia.Count != 0)
|
||||
{
|
||||
location = MediaPanel.PointFromParent(location);
|
||||
if (start)
|
||||
{
|
||||
// Start moving selected media events
|
||||
_mediaMoveStartPos = location;
|
||||
_mediaMoveStartFrames = new int[SelectedMedia.Count];
|
||||
if (_mediaMoveStartTracks == null)
|
||||
_mediaMoveStartTracks = new List<Track>();
|
||||
else
|
||||
_mediaMoveStartTracks.Clear();
|
||||
for (var i = 0; i < SelectedMedia.Count; i++)
|
||||
{
|
||||
var media = SelectedMedia[i];
|
||||
_mediaMoveStartFrames[i] = media.StartFrame;
|
||||
if (!_mediaMoveStartTracks.Contains(media.Track))
|
||||
_mediaMoveStartTracks.Add(media.Track);
|
||||
}
|
||||
if (Undo != null && Undo.Enabled)
|
||||
{
|
||||
// Undo per-track
|
||||
_mediaMoveStartData = new byte[_mediaMoveStartTracks.Count][];
|
||||
for (int i = 0; i < _mediaMoveStartData.Length; i++)
|
||||
_mediaMoveStartData[i] = EditTrackAction.CaptureData(_mediaMoveStartTracks[i]);
|
||||
}
|
||||
}
|
||||
else if (end)
|
||||
{
|
||||
// End moving selected media events
|
||||
if (_mediaMoveStartData != null)
|
||||
{
|
||||
for (int i = 0; i < _mediaMoveStartData.Length; i++)
|
||||
{
|
||||
var track = _mediaMoveStartTracks[i];
|
||||
var before = _mediaMoveStartData[i];
|
||||
var after = EditTrackAction.CaptureData(track);
|
||||
if (!Utils.ArraysEqual(before, after))
|
||||
AddBatchedUndoAction(new EditTrackAction(this, track, before, after));
|
||||
}
|
||||
}
|
||||
MarkAsEdited();
|
||||
_mediaMoveStartTracks.Clear();
|
||||
_mediaMoveStartFrames = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Move selected media events
|
||||
var moveLocationDelta = location - _mediaMoveStartPos;
|
||||
var moveDelta = (int)(moveLocationDelta.X / (UnitsPerSecond * Zoom) * FramesPerSecond);
|
||||
for (var i = 0; i < SelectedMedia.Count; i++)
|
||||
SelectedMedia[i].StartFrame = _mediaMoveStartFrames[i] + moveDelta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -1028,6 +1028,15 @@ namespace FlaxEditor.GUI.Timeline
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when showing timeline context menu to the user. Can be used to add custom buttons.
|
||||
/// </summary>
|
||||
/// <param name="menu">The menu.</param>
|
||||
/// <param name="time">The time (in seconds) at which context menu is shown (user clicked on a timeline).</param>
|
||||
public virtual void OnTimelineContextMenu(ContextMenu.ContextMenu menu, float time)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when context menu is being prepared to show. Can be used to add custom options.
|
||||
/// </summary>
|
||||
@@ -1051,7 +1060,7 @@ namespace FlaxEditor.GUI.Timeline
|
||||
if (CanRename)
|
||||
menu.AddButton("Rename", "F2", StartRenaming);
|
||||
if (CanCopyPaste)
|
||||
menu.AddButton("Duplicate", "Ctrl+D", () => Timeline.DuplicateSelection());
|
||||
menu.AddButton("Duplicate", "Ctrl+D", () => Timeline.DuplicateSelectedTracks());
|
||||
menu.AddButton("Delete", "Del", Delete);
|
||||
if (CanExpand)
|
||||
{
|
||||
@@ -1292,12 +1301,12 @@ namespace FlaxEditor.GUI.Timeline
|
||||
StartRenaming();
|
||||
return true;
|
||||
case KeyboardKeys.Delete:
|
||||
_timeline.DeleteSelection();
|
||||
_timeline.DeleteSelectedTracks();
|
||||
return true;
|
||||
case KeyboardKeys.D:
|
||||
if (Root.GetKey(KeyboardKeys.Control) && CanCopyPaste)
|
||||
{
|
||||
_timeline.DuplicateSelection();
|
||||
_timeline.DuplicateSelectedTracks();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
283
Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs
Normal file
283
Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs
Normal file
@@ -0,0 +1,283 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using FlaxEditor.CustomEditors;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEditor.Utilities;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using Object = FlaxEngine.Object;
|
||||
|
||||
namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
{
|
||||
/// <summary>
|
||||
/// The timeline media for <see cref="AnimEvent"/> and <see cref="AnimContinuousEvent"/>.
|
||||
/// </summary>
|
||||
public sealed class AnimationEventMedia : Media
|
||||
{
|
||||
private sealed class ProxyEditor : SyncPointEditor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<object> UndoObjects => Values;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
base.Initialize(layout);
|
||||
|
||||
var instance = (AnimEvent)Values[0];
|
||||
var scriptType = TypeUtils.GetObjectType(instance);
|
||||
var editor = CustomEditorsUtil.CreateEditor(scriptType, false);
|
||||
layout.Object(Values, editor);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class Proxy : ProxyBase<AnimationEventTrack, AnimationEventMedia>
|
||||
{
|
||||
[EditorDisplay("General", EditorDisplayAttribute.InlineStyle), CustomEditor(typeof(ProxyEditor))]
|
||||
public AnimEvent Event
|
||||
{
|
||||
get => Media.Instance;
|
||||
set => Media.Instance = value;
|
||||
}
|
||||
|
||||
public Proxy(AnimationEventTrack track, AnimationEventMedia media)
|
||||
: base(track, media)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isRegisteredForScriptsReload;
|
||||
private string _instanceTypeName;
|
||||
private byte[] _instanceData;
|
||||
|
||||
/// <summary>
|
||||
/// The event type.
|
||||
/// </summary>
|
||||
public ScriptType Type;
|
||||
|
||||
/// <summary>
|
||||
/// The event instance.
|
||||
/// </summary>
|
||||
public AnimEvent Instance;
|
||||
|
||||
/// <summary>
|
||||
/// True if event is continuous (with duration), not a single frame.
|
||||
/// </summary>
|
||||
public bool IsContinuous;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AnimationEventMedia"/> class.
|
||||
/// </summary>
|
||||
public AnimationEventMedia()
|
||||
{
|
||||
PropertiesEditObject = new Proxy(null, this);
|
||||
}
|
||||
|
||||
private void OnScriptsReloadBegin()
|
||||
{
|
||||
if (Instance)
|
||||
{
|
||||
_instanceTypeName = Type.TypeName;
|
||||
Type = ScriptType.Null;
|
||||
_instanceData = FlaxEngine.Json.JsonSerializer.SaveToBytes(Instance);
|
||||
Object.Destroy(ref Instance);
|
||||
ScriptsBuilder.ScriptsReloadEnd += OnScriptsReloadEnd;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnScriptsReloadEnd()
|
||||
{
|
||||
ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd;
|
||||
Type = TypeUtils.GetType(_instanceTypeName);
|
||||
if (Type == ScriptType.Null)
|
||||
{
|
||||
Editor.LogError("Missing anim event type " + _instanceTypeName);
|
||||
return;
|
||||
}
|
||||
Instance = (AnimEvent)Type.CreateInstance();
|
||||
FlaxEngine.Json.JsonSerializer.LoadFromBytes(Instance, _instanceData, Globals.EngineBuildNumber);
|
||||
_instanceData = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes track for the specified type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
public void Init(ScriptType type)
|
||||
{
|
||||
Type = type;
|
||||
IsContinuous = new ScriptType(typeof(AnimContinuousEvent)).IsAssignableFrom(type);
|
||||
CanDelete = true;
|
||||
CanSplit = IsContinuous;
|
||||
CanResize = IsContinuous;
|
||||
TooltipText = Surface.SurfaceUtils.GetVisualScriptTypeDescription(type);
|
||||
Instance = (AnimEvent)type.CreateInstance();
|
||||
BackgroundColor = Instance.Color;
|
||||
if (!_isRegisteredForScriptsReload)
|
||||
{
|
||||
_isRegisteredForScriptsReload = true;
|
||||
ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnDurationFramesChanged()
|
||||
{
|
||||
if (Type != ScriptType.Null)
|
||||
DurationFrames = IsContinuous ? Mathf.Max(DurationFrames, 2) : 1;
|
||||
|
||||
base.OnDurationFramesChanged();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
Type = ScriptType.Null;
|
||||
Object.Destroy(ref Instance);
|
||||
if (_isRegisteredForScriptsReload)
|
||||
{
|
||||
_isRegisteredForScriptsReload = false;
|
||||
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
|
||||
}
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The timeline track for <see cref="AnimEvent"/> and <see cref="AnimContinuousEvent"/>.
|
||||
/// </summary>
|
||||
public sealed class AnimationEventTrack : Track
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the archetype.
|
||||
/// </summary>
|
||||
/// <returns>The archetype.</returns>
|
||||
public static TrackArchetype GetArchetype()
|
||||
{
|
||||
return new TrackArchetype
|
||||
{
|
||||
TypeId = 19,
|
||||
Name = "Animation Event",
|
||||
Create = options => new AnimationEventTrack(ref options),
|
||||
Load = LoadTrack,
|
||||
Save = SaveTrack,
|
||||
};
|
||||
}
|
||||
|
||||
private static void LoadTrack(int version, Track track, BinaryReader stream)
|
||||
{
|
||||
var e = (AnimationEventTrack)track;
|
||||
var count = stream.ReadInt32();
|
||||
while (e.Media.Count > count)
|
||||
e.RemoveMedia(e.Media.Last());
|
||||
while (e.Media.Count < count)
|
||||
e.AddMedia(new AnimationEventMedia());
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var m = (AnimationEventMedia)e.Media[i];
|
||||
m.StartFrame = (int)stream.ReadSingle();
|
||||
m.DurationFrames = (int)stream.ReadSingle();
|
||||
var typeName = stream.ReadStrAnsi(13);
|
||||
var type = TypeUtils.GetType(typeName);
|
||||
if (type == ScriptType.Null)
|
||||
throw new Exception($"Unknown type {typeName}.");
|
||||
m.Init(type);
|
||||
stream.ReadJson(m.Instance);
|
||||
}
|
||||
}
|
||||
|
||||
private static void SaveTrack(Track track, BinaryWriter stream)
|
||||
{
|
||||
var e = (AnimationEventTrack)track;
|
||||
var count = e.Media.Count;
|
||||
stream.Write(count);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var m = (AnimationEventMedia)e.Media[i];
|
||||
stream.Write((float)m.StartFrame);
|
||||
stream.Write((float)m.DurationFrames);
|
||||
stream.WriteStrAnsi(m.Type.TypeName, 13);
|
||||
stream.WriteJson(m.Instance);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public AnimationEventTrack(ref TrackCreateOptions options)
|
||||
: base(ref options)
|
||||
{
|
||||
// Add button
|
||||
const float buttonSize = 14;
|
||||
var addButton = new Button
|
||||
{
|
||||
Text = "+",
|
||||
TooltipText = "Add events",
|
||||
AutoFocus = true,
|
||||
AnchorPreset = AnchorPresets.MiddleRight,
|
||||
IsScrollable = false,
|
||||
Offsets = new Margin(-buttonSize - 2 + _muteCheckbox.Offsets.Left, buttonSize, buttonSize * -0.5f, buttonSize),
|
||||
Parent = this,
|
||||
};
|
||||
addButton.ButtonClicked += OnAddButtonClicked;
|
||||
}
|
||||
|
||||
private void OnAddButtonClicked(Button button)
|
||||
{
|
||||
var cm = new ContextMenu.ContextMenu();
|
||||
OnContextMenu(cm);
|
||||
cm.Show(button.Parent, button.BottomLeft);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnTimelineContextMenu(ContextMenu.ContextMenu menu, float time)
|
||||
{
|
||||
base.OnTimelineContextMenu(menu, time);
|
||||
|
||||
AddContextMenu(menu, time);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnContextMenu(ContextMenu.ContextMenu menu)
|
||||
{
|
||||
base.OnContextMenu(menu);
|
||||
|
||||
menu.AddSeparator();
|
||||
AddContextMenu(menu, 0.0f);
|
||||
}
|
||||
|
||||
private void AddContextMenu(ContextMenu.ContextMenu menu, float time)
|
||||
{
|
||||
var addEvent = menu.AddChildMenu("Add Anim Event");
|
||||
var addContinuousEvent = menu.AddChildMenu("Add Anim Continuous Event");
|
||||
var animEventTypes = Editor.Instance.CodeEditing.All.Get().Where(x => new ScriptType(typeof(AnimEvent)).IsAssignableFrom(x));
|
||||
foreach (var type in animEventTypes)
|
||||
{
|
||||
if (type.IsAbstract || !type.CanCreateInstance)
|
||||
continue;
|
||||
var add = new ScriptType(typeof(AnimContinuousEvent)).IsAssignableFrom(type) ? addContinuousEvent : addEvent;
|
||||
var b = add.ContextMenu.AddButton(type.Name);
|
||||
b.TooltipText = Surface.SurfaceUtils.GetVisualScriptTypeDescription(type);
|
||||
b.Tag = type;
|
||||
b.Parent.Tag = time;
|
||||
b.ButtonClicked += OnAddAnimEvent;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void OnAddAnimEvent(ContextMenuButton button)
|
||||
{
|
||||
var type = (ScriptType)button.Tag;
|
||||
var time = (float)button.Parent.Tag;
|
||||
var media = new AnimationEventMedia();
|
||||
media.Init(type);
|
||||
media.StartFrame = (int)(time * Timeline.FramesPerSecond);
|
||||
media.DurationFrames = media.IsContinuous ? Mathf.Max(Timeline.DurationFrames / 10, 10) : 1;
|
||||
Timeline.AddMedia(this, media);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -324,12 +324,12 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnTimelineShowContextMenu(ContextMenu.ContextMenu menu, Control controlUnderMouse)
|
||||
public override void OnTimelineContextMenu(ContextMenu.ContextMenu menu, float time, Control controlUnderMouse)
|
||||
{
|
||||
if (((CameraCutTrack)Track).Camera)
|
||||
menu.AddButton("Refresh thumbnails", () => UpdateThumbnails());
|
||||
|
||||
base.OnTimelineShowContextMenu(menu, controlUnderMouse);
|
||||
base.OnTimelineContextMenu(menu, time, controlUnderMouse);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -101,9 +101,9 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnTimelineShowContextMenu(ContextMenu.ContextMenu menu, Control controlUnderMouse)
|
||||
public override void OnTimelineContextMenu(ContextMenu.ContextMenu menu, float time, Control controlUnderMouse)
|
||||
{
|
||||
base.OnTimelineShowContextMenu(menu, controlUnderMouse);
|
||||
base.OnTimelineContextMenu(menu, time, controlUnderMouse);
|
||||
|
||||
if (controlUnderMouse is GradientEditor.StopControl stop)
|
||||
{
|
||||
|
||||
@@ -140,7 +140,7 @@ void OnVisualScriptingDebugFlow()
|
||||
void OnLogMessage(LogType type, const StringView& msg);
|
||||
|
||||
ManagedEditor::ManagedEditor()
|
||||
: PersistentScriptingObject(SpawnParams(ObjectID, ManagedEditor::TypeInitializer))
|
||||
: ScriptingObject(SpawnParams(ObjectID, ManagedEditor::TypeInitializer))
|
||||
{
|
||||
// Link events
|
||||
auto editor = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly;
|
||||
@@ -476,11 +476,6 @@ void ManagedEditor::OnEditorAssemblyLoaded(MAssembly* assembly)
|
||||
CreateManaged();
|
||||
}
|
||||
|
||||
String ManagedEditor::ToString() const
|
||||
{
|
||||
return TEXT("ManagedEditor");
|
||||
}
|
||||
|
||||
void ManagedEditor::DestroyManaged()
|
||||
{
|
||||
// Ensure to cleanup managed stuff
|
||||
@@ -499,5 +494,5 @@ void ManagedEditor::DestroyManaged()
|
||||
Internal_OnVisualScriptingDebugFlow = nullptr;
|
||||
|
||||
// Base
|
||||
PersistentScriptingObject::DestroyManaged();
|
||||
ScriptingObject::DestroyManaged();
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace CSG
|
||||
/// <summary>
|
||||
/// Managed Editor root object
|
||||
/// </summary>
|
||||
class ManagedEditor : public PersistentScriptingObject
|
||||
class ManagedEditor : public ScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_NO_SPAWN(ManagedEditor);
|
||||
|
||||
@@ -144,7 +144,6 @@ private:
|
||||
|
||||
public:
|
||||
|
||||
// [PersistentScriptingObject]
|
||||
String ToString() const override;
|
||||
// [ScriptingObject]
|
||||
void DestroyManaged() override;
|
||||
};
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#include "Engine/Core/Delegate.h"
|
||||
#include "Engine/Core/Types/String.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Scripting/ScriptingType.h"
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -163,6 +163,7 @@ namespace FlaxEditor.Viewport.Previews
|
||||
: base(useWidgets)
|
||||
{
|
||||
Task.Begin += OnBegin;
|
||||
ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin;
|
||||
|
||||
// Setup preview scene
|
||||
_previewModel = new AnimatedModel
|
||||
@@ -275,6 +276,12 @@ namespace FlaxEditor.Viewport.Previews
|
||||
}
|
||||
}
|
||||
|
||||
private void OnScriptsReloadBegin()
|
||||
{
|
||||
// Prevent any crashes due to dangling references to anim events
|
||||
_previewModel.ResetAnimation();
|
||||
}
|
||||
|
||||
private int ComputeLODIndex(SkinnedModel model)
|
||||
{
|
||||
if (PreviewActor.ForcedLOD != -1)
|
||||
@@ -428,6 +435,7 @@ namespace FlaxEditor.Viewport.Previews
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
|
||||
Object.Destroy(ref _floorModel);
|
||||
Object.Destroy(ref _previewModel);
|
||||
NodesMask = null;
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace FlaxEditor.Viewport.Previews
|
||||
// Playback Speed
|
||||
{
|
||||
var playbackSpeed = ViewWidgetButtonMenu.AddButton("Playback Speed");
|
||||
var playbackSpeedValue = new FloatValueBox(-1, 90, 2, 70.0f, 0.0f, 10000.0f, 0.001f)
|
||||
var playbackSpeedValue = new FloatValueBox(-1, 90, 2, 70.0f, -10000.0f, 10000.0f, 0.001f)
|
||||
{
|
||||
Parent = playbackSpeed
|
||||
};
|
||||
|
||||
@@ -254,6 +254,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
Enabled = false
|
||||
};
|
||||
_timeline.Modified += MarkAsEdited;
|
||||
_timeline.SetNoTracksText("Loading...");
|
||||
|
||||
// Asset properties
|
||||
_propertiesPresenter = new CustomEditorPresenter(null);
|
||||
@@ -361,6 +362,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
_timeline.Load(_asset);
|
||||
_undo.Clear();
|
||||
_timeline.Enabled = true;
|
||||
_timeline.SetNoTracksText(null);
|
||||
ClearEditedFlag();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,6 +347,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
};
|
||||
_timeline.Modified += OnTimelineModified;
|
||||
_timeline.SelectionChanged += OnTimelineSelectionChanged;
|
||||
_timeline.SetNoTracksText("Loading...");
|
||||
|
||||
// Properties editor
|
||||
var propertiesEditor = new CustomEditorPresenter(_undo, string.Empty);
|
||||
@@ -527,6 +528,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
// Setup
|
||||
_undo.Clear();
|
||||
_timeline.Enabled = true;
|
||||
_timeline.SetNoTracksText(null);
|
||||
_propertiesEditor.Select(new GeneralProxy(this));
|
||||
ClearEditedFlag();
|
||||
}
|
||||
|
||||
70
Source/Engine/Animations/AnimEvent.cpp
Normal file
70
Source/Engine/Animations/AnimEvent.cpp
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "AnimEvent.h"
|
||||
#include "Engine/Scripting/BinaryModule.h"
|
||||
#include "Engine/Scripting/ManagedSerialization.h"
|
||||
#include "Engine/Serialization/SerializationFwd.h"
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
|
||||
AnimEvent::AnimEvent(const SpawnParams& params)
|
||||
: ScriptingObject(params)
|
||||
{
|
||||
}
|
||||
|
||||
void AnimEvent::Serialize(SerializeStream& stream, const void* otherObj)
|
||||
{
|
||||
SERIALIZE_GET_OTHER_OBJ(AnimEvent);
|
||||
|
||||
#if !COMPILE_WITHOUT_CSHARP
|
||||
// Handle C# objects data serialization
|
||||
if (Flags & ObjectFlags::IsManagedType)
|
||||
{
|
||||
stream.JKEY("V");
|
||||
if (other)
|
||||
{
|
||||
ManagedSerialization::SerializeDiff(stream, GetOrCreateManagedInstance(), other->GetOrCreateManagedInstance());
|
||||
}
|
||||
else
|
||||
{
|
||||
ManagedSerialization::Serialize(stream, GetOrCreateManagedInstance());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Handle custom scripting objects data serialization
|
||||
if (Flags & ObjectFlags::IsCustomScriptingType)
|
||||
{
|
||||
stream.JKEY("D");
|
||||
_type.Module->SerializeObject(stream, this, other);
|
||||
}
|
||||
}
|
||||
|
||||
void AnimEvent::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
|
||||
{
|
||||
#if !COMPILE_WITHOUT_CSHARP
|
||||
// Handle C# objects data serialization
|
||||
if (Flags & ObjectFlags::IsManagedType)
|
||||
{
|
||||
auto* const v = SERIALIZE_FIND_MEMBER(stream, "V");
|
||||
if (v != stream.MemberEnd() && v->value.IsObject() && v->value.MemberCount() != 0)
|
||||
{
|
||||
ManagedSerialization::Deserialize(v->value, GetOrCreateManagedInstance());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Handle custom scripting objects data serialization
|
||||
if (Flags & ObjectFlags::IsCustomScriptingType)
|
||||
{
|
||||
auto* const v = SERIALIZE_FIND_MEMBER(stream, "D");
|
||||
if (v != stream.MemberEnd() && v->value.IsObject() && v->value.MemberCount() != 0)
|
||||
{
|
||||
_type.Module->DeserializeObject(v->value, this, modifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AnimContinuousEvent::AnimContinuousEvent(const SpawnParams& params)
|
||||
: AnimEvent(params)
|
||||
{
|
||||
}
|
||||
72
Source/Engine/Animations/AnimEvent.h
Normal file
72
Source/Engine/Animations/AnimEvent.h
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Scripting/ScriptingObject.h"
|
||||
#include "Engine/Core/ISerializable.h"
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Core/Math/Color.h"
|
||||
#endif
|
||||
|
||||
class AnimatedModel;
|
||||
class Animation;
|
||||
|
||||
/// <summary>
|
||||
/// The animation notification event triggered during animation playback.
|
||||
/// </summary>
|
||||
API_CLASS(Abstract) class FLAXENGINE_API AnimEvent : public ScriptingObject, public ISerializable
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE(AnimEvent);
|
||||
|
||||
#if USE_EDITOR
|
||||
/// <summary>
|
||||
/// Event display color in the Editor.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="HideInEditor, NoSerialize") Color Color = Color::White;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Animation event notification.
|
||||
/// </summary>
|
||||
/// <param name="actor">The animated model actor instance.</param>
|
||||
/// <param name="anim">The source animation.</param>
|
||||
/// <param name="time">The current animation time (in seconds).</param>
|
||||
/// <param name="deltaTime">The current animation tick delta time (in seconds).</param>
|
||||
API_FUNCTION() virtual void OnEvent(AnimatedModel* actor, Animation* anim, float time, float deltaTime)
|
||||
{
|
||||
}
|
||||
|
||||
// [ISerializable]
|
||||
void Serialize(SerializeStream& stream, const void* otherObj) override;
|
||||
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The animation notification event (with duration) triggered during animation playback that contains begin and end (event notification is received as a tick).
|
||||
/// </summary>
|
||||
API_CLASS(Abstract) class FLAXENGINE_API AnimContinuousEvent : public AnimEvent
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE(AnimContinuousEvent);
|
||||
|
||||
/// <summary>
|
||||
/// Animation notification called before the first event.
|
||||
/// </summary>
|
||||
/// <param name="actor">The animated model actor instance.</param>
|
||||
/// <param name="anim">The source animation.</param>
|
||||
/// <param name="time">The current animation time (in seconds).</param>
|
||||
/// <param name="deltaTime">The current animation tick delta time (in seconds).</param>
|
||||
API_FUNCTION() virtual void OnBegin(AnimatedModel* actor, Animation* anim, float time, float deltaTime)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Animation notification called after the last event (guaranteed to be always called).
|
||||
/// </summary>
|
||||
/// <param name="actor">The animated model actor instance.</param>
|
||||
/// <param name="anim">The source animation.</param>
|
||||
/// <param name="time">The current animation time (in seconds).</param>
|
||||
/// <param name="deltaTime">The current animation tick delta time (in seconds).</param>
|
||||
API_FUNCTION() virtual void OnEnd(AnimatedModel* actor, Animation* anim, float time, float deltaTime)
|
||||
{
|
||||
}
|
||||
};
|
||||
@@ -14,7 +14,13 @@ namespace AnimationUtils
|
||||
template<class T>
|
||||
FORCE_INLINE static T GetZero()
|
||||
{
|
||||
return 0.0f;
|
||||
return T();
|
||||
}
|
||||
|
||||
template<>
|
||||
FORCE_INLINE int32 GetZero<int32>()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<>
|
||||
|
||||
@@ -769,6 +769,12 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// An animation spline represented by a set of keyframes, each representing a value point.
|
||||
/// </summary>
|
||||
template<typename T>
|
||||
using StepCurve = Curve<T, StepCurveKeyframe<T>>;
|
||||
|
||||
/// <summary>
|
||||
/// An animation spline represented by a set of keyframes, each representing an endpoint of a linear curve.
|
||||
/// </summary>
|
||||
|
||||
@@ -186,6 +186,7 @@ namespace Serialization
|
||||
|
||||
// Raw keyframes data
|
||||
stream.WriteInt32(keyframes.Count());
|
||||
static_assert(TIsPODType<T>::Value, "Raw bytes serialization only for raw POD types.");
|
||||
stream.WriteBytes(keyframes.Get(), keyframes.Count() * sizeof(KeyFrame));
|
||||
}
|
||||
|
||||
@@ -209,6 +210,7 @@ namespace Serialization
|
||||
int32 keyframesCount;
|
||||
stream.ReadInt32(&keyframesCount);
|
||||
keyframes.Resize(keyframesCount, false);
|
||||
static_assert(TIsPODType<T>::Value, "Raw bytes serialization only for raw POD types.");
|
||||
stream.ReadBytes(keyframes.Get(), keyframes.Count() * sizeof(KeyFrame));
|
||||
|
||||
return false;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "AnimGraph.h"
|
||||
#include "Engine/Animations/Animations.h"
|
||||
#include "Engine/Animations/AnimEvent.h"
|
||||
#include "Engine/Content/Assets/SkinnedModel.h"
|
||||
#include "Engine/Graphics/Models/SkeletonData.h"
|
||||
#include "Engine/Scripting/Scripting.h"
|
||||
@@ -91,6 +92,9 @@ void AnimGraphInstanceData::Clear()
|
||||
State.Resize(0);
|
||||
NodesPose.Resize(0);
|
||||
Slots.Resize(0);
|
||||
for (const auto& e : Events)
|
||||
((AnimContinuousEvent*)e.Instance)->OnEnd((AnimatedModel*)Object, e.Anim, 0.0f, 0.0f);
|
||||
Events.Resize(0);
|
||||
}
|
||||
|
||||
void AnimGraphInstanceData::ClearState()
|
||||
@@ -103,6 +107,9 @@ void AnimGraphInstanceData::ClearState()
|
||||
State.Resize(0);
|
||||
NodesPose.Resize(0);
|
||||
Slots.Clear();
|
||||
for (const auto& e : Events)
|
||||
((AnimContinuousEvent*)e.Instance)->OnEnd((AnimatedModel*)Object, e.Anim, 0.0f, 0.0f);
|
||||
Events.Clear();
|
||||
}
|
||||
|
||||
void AnimGraphInstanceData::Invalidate()
|
||||
@@ -246,6 +253,8 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
|
||||
// Initialize buckets
|
||||
ResetBuckets(context, &_graph);
|
||||
}
|
||||
for (auto& e : data.Events)
|
||||
e.Hit = false;
|
||||
|
||||
// Init empty nodes data
|
||||
context.EmptyNodes.RootMotion = RootMotionData::Identity;
|
||||
@@ -279,6 +288,19 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
|
||||
if (animResult == nullptr)
|
||||
animResult = GetEmptyNodes();
|
||||
}
|
||||
if (data.Events.Count() != 0)
|
||||
{
|
||||
ANIM_GRAPH_PROFILE_EVENT("Events");
|
||||
for (int32 i = data.Events.Count() - 1; i >= 0; i--)
|
||||
{
|
||||
const auto& e = data.Events[i];
|
||||
if (!e.Hit)
|
||||
{
|
||||
((AnimContinuousEvent*)e.Instance)->OnEnd((AnimatedModel*)context.Data->Object, e.Anim, 0.0f, 0.0f);
|
||||
data.Events.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allow for external override of the local pose (eg. by the ragdoll)
|
||||
data.LocalPoseOverride(animResult);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#define ANIM_GRAPH_MULTI_BLEND_2D_MAX_TRIS 32
|
||||
#define ANIM_GRAPH_MAX_STATE_TRANSITIONS 64
|
||||
#define ANIM_GRAPH_MAX_CALL_STACK 100
|
||||
#define ANIM_GRAPH_MAX_EVENTS 64
|
||||
|
||||
class AnimGraph;
|
||||
class AnimSubGraph;
|
||||
@@ -267,6 +268,7 @@ struct FLAXENGINE_API AnimGraphSlot
|
||||
/// </summary>
|
||||
class FLAXENGINE_API AnimGraphInstanceData
|
||||
{
|
||||
friend AnimGraphExecutor;
|
||||
public:
|
||||
|
||||
// ---- Quick documentation ----
|
||||
@@ -384,7 +386,7 @@ public:
|
||||
/// <summary>
|
||||
/// The slots animations.
|
||||
/// </summary>
|
||||
Array<AnimGraphSlot> Slots;
|
||||
Array<AnimGraphSlot, InlinedAllocation<4>> Slots;
|
||||
|
||||
public:
|
||||
|
||||
@@ -402,6 +404,18 @@ public:
|
||||
/// Invalidates the update timer.
|
||||
/// </summary>
|
||||
void Invalidate();
|
||||
|
||||
private:
|
||||
|
||||
struct Event
|
||||
{
|
||||
AnimEvent* Instance;
|
||||
Animation* Anim;
|
||||
AnimGraphNode* Node;
|
||||
bool Hit;
|
||||
};
|
||||
|
||||
Array<Event, InlinedAllocation<8>> Events;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
#include "Engine/Content/Assets/SkeletonMask.h"
|
||||
#include "Engine/Content/Assets/AnimationGraphFunction.h"
|
||||
#include "Engine/Animations/AlphaBlend.h"
|
||||
#include "Engine/Animations/AnimEvent.h"
|
||||
#include "Engine/Animations/InverseKinematics.h"
|
||||
#include "Engine/Level/Actors/AnimatedModel.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -146,6 +148,7 @@ Variant AnimGraphExecutor::SampleAnimation(AnimGraphNode* node, bool loop, float
|
||||
if (anim == nullptr || !anim->IsLoaded())
|
||||
return Value::Null;
|
||||
PROFILE_CPU_ASSET(anim);
|
||||
const float oldTimePos = prevTimePos;
|
||||
|
||||
// Calculate actual time position within the animation node (defined by length and loop mode)
|
||||
const float pos = GetAnimPos(newTimePos, startTimePos, loop, length);
|
||||
@@ -180,6 +183,81 @@ Variant AnimGraphExecutor::SampleAnimation(AnimGraphNode* node, bool loop, float
|
||||
ExtractRootMotion(mapping, rootNodeIndex, anim, animPos, animPrevPos, nodes->Nodes[rootNodeIndex], nodes->RootMotion);
|
||||
}
|
||||
|
||||
// Collect events
|
||||
if (anim->Events.Count() != 0)
|
||||
{
|
||||
ANIM_GRAPH_PROFILE_EVENT("Events");
|
||||
auto& context = Context.Get();
|
||||
float eventTimeMin = animPrevPos;
|
||||
float eventTimeMax = animPos;
|
||||
if (loop)
|
||||
{
|
||||
// Check if animation looped
|
||||
const float posNotLooped = startTimePos + oldTimePos;
|
||||
if (posNotLooped < 0.0f || posNotLooped > length)
|
||||
{
|
||||
if (context.DeltaTime * speed < 0)
|
||||
{
|
||||
// Playback backwards
|
||||
Swap(eventTimeMin, eventTimeMax);
|
||||
}
|
||||
}
|
||||
}
|
||||
const float eventTime = animPos / static_cast<float>(anim->Data.FramesPerSecond);
|
||||
const float eventDeltaTime = (animPos - animPrevPos) / static_cast<float>(anim->Data.FramesPerSecond);
|
||||
for (const auto& track : anim->Events)
|
||||
{
|
||||
for (const auto& k : track.Second.GetKeyframes())
|
||||
{
|
||||
if (!k.Value.Instance)
|
||||
continue;
|
||||
const float duration = k.Value.Duration > 1 ? k.Value.Duration : 0.0f;
|
||||
if (k.Time <= eventTimeMax && eventTimeMin <= k.Time + duration)
|
||||
{
|
||||
int32 stateIndex = -1;
|
||||
if (duration > 1)
|
||||
{
|
||||
// Begin for continuous event
|
||||
for (stateIndex = 0; stateIndex < context.Data->Events.Count(); stateIndex++)
|
||||
{
|
||||
const auto& e = context.Data->Events[stateIndex];
|
||||
if (e.Instance == k.Value.Instance && e.Node == node)
|
||||
break;
|
||||
}
|
||||
if (stateIndex == context.Data->Events.Count())
|
||||
{
|
||||
auto& e = context.Data->Events.AddOne();
|
||||
e.Instance = k.Value.Instance;
|
||||
e.Anim = anim;
|
||||
e.Node = node;
|
||||
ASSERT(k.Value.Instance->Is<AnimContinuousEvent>());
|
||||
((AnimContinuousEvent*)k.Value.Instance)->OnBegin((AnimatedModel*)context.Data->Object, anim, eventTime, eventDeltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
// Event
|
||||
k.Value.Instance->OnEvent((AnimatedModel*)context.Data->Object, anim, eventTime, eventDeltaTime);
|
||||
if (stateIndex != -1)
|
||||
context.Data->Events[stateIndex].Hit = true;
|
||||
}
|
||||
else if (duration > 1)
|
||||
{
|
||||
// End for continuous event
|
||||
for (int32 i = 0; i < context.Data->Events.Count(); i++)
|
||||
{
|
||||
const auto& e = context.Data->Events[i];
|
||||
if (e.Instance == k.Value.Instance && e.Node == node)
|
||||
{
|
||||
((AnimContinuousEvent*)k.Value.Instance)->OnEnd((AnimatedModel*)context.Data->Object, anim, eventTime, eventDeltaTime);
|
||||
context.Data->Events.RemoveAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ public:
|
||||
CameraCut = 16,
|
||||
//AnimationChannel = 17,
|
||||
//AnimationChannelData = 18,
|
||||
//AnimationEvent = 19,
|
||||
};
|
||||
|
||||
enum class Flags
|
||||
|
||||
@@ -7,17 +7,17 @@
|
||||
/// <summary>
|
||||
/// Represents a single audio device.
|
||||
/// </summary>
|
||||
API_CLASS(NoSpawn) class AudioDevice : public PersistentScriptingObject
|
||||
API_CLASS(NoSpawn) class AudioDevice : public ScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_NO_SPAWN(AudioDevice);
|
||||
|
||||
explicit AudioDevice()
|
||||
: PersistentScriptingObject(SpawnParams(Guid::New(), TypeInitializer))
|
||||
: ScriptingObject(SpawnParams(Guid::New(), TypeInitializer))
|
||||
{
|
||||
}
|
||||
|
||||
AudioDevice(const AudioDevice& other)
|
||||
: PersistentScriptingObject(SpawnParams(Guid::New(), TypeInitializer))
|
||||
: ScriptingObject(SpawnParams(Guid::New(), TypeInitializer))
|
||||
{
|
||||
Name = other.Name;
|
||||
InternalName = other.InternalName;
|
||||
|
||||
@@ -6,10 +6,13 @@
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Content/Factories/BinaryAssetFactory.h"
|
||||
#include "Engine/Animations/CurveSerialization.h"
|
||||
#include "Engine/Animations/AnimEvent.h"
|
||||
#include "Engine/Scripting/Scripting.h"
|
||||
#include "Engine/Threading/Threading.h"
|
||||
#include "Engine/Serialization/MemoryReadStream.h"
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Serialization/MemoryWriteStream.h"
|
||||
#include "Engine/Level/Level.h"
|
||||
#endif
|
||||
|
||||
REGISTER_BINARY_ASSET(Animation, "FlaxEngine.Animation", false);
|
||||
@@ -19,6 +22,21 @@ Animation::Animation(const SpawnParams& params, const AssetInfo* info)
|
||||
{
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
void Animation::OnScriptsReloadStart()
|
||||
{
|
||||
for (auto& e : Events)
|
||||
{
|
||||
for (auto& k : e.Second.GetKeyframes())
|
||||
{
|
||||
Level::ScriptsReloadRegisterObject((ScriptingObject*&)k.Value.Instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
Animation::InfoData Animation::GetInfo() const
|
||||
{
|
||||
ScopeLock lock(Locker);
|
||||
@@ -127,7 +145,7 @@ void Animation::LoadTimeline(BytesContainer& result) const
|
||||
const float fpsInv = 1.0f / fps;
|
||||
stream.WriteFloat(fps);
|
||||
stream.WriteInt32((int32)Data.Duration);
|
||||
int32 tracksCount = Data.Channels.Count();
|
||||
int32 tracksCount = Data.Channels.Count() + Events.Count();
|
||||
for (auto& channel : Data.Channels)
|
||||
tracksCount +=
|
||||
(channel.Position.GetKeyframes().HasItems() ? 1 : 0) +
|
||||
@@ -214,6 +232,24 @@ void Animation::LoadTimeline(BytesContainer& result) const
|
||||
trackIndex++;
|
||||
}
|
||||
}
|
||||
for (auto& e : Events)
|
||||
{
|
||||
// Animation Event track
|
||||
stream.WriteByte(19); // Track Type
|
||||
stream.WriteByte(0); // Track Flags
|
||||
stream.WriteInt32(-1); // Parent Index
|
||||
stream.WriteInt32(0); // Children Count
|
||||
stream.WriteString(e.First, -13); // Name
|
||||
stream.Write(&Color32::White); // Color
|
||||
stream.WriteInt32(e.Second.GetKeyframes().Count()); // Events Count
|
||||
for (const auto& k : e.Second.GetKeyframes())
|
||||
{
|
||||
stream.WriteFloat(k.Time);
|
||||
stream.WriteFloat(k.Value.Duration);
|
||||
stream.WriteStringAnsi(k.Value.TypeName, 13);
|
||||
stream.WriteJson(k.Value.Instance);
|
||||
}
|
||||
}
|
||||
|
||||
result.Copy(stream.GetHandle(), stream.GetPosition());
|
||||
}
|
||||
@@ -253,6 +289,7 @@ bool Animation::SaveTimeline(BytesContainer& data)
|
||||
|
||||
// Tracks
|
||||
Data.Channels.Clear();
|
||||
Events.Clear();
|
||||
Dictionary<int32, int32> animationChannelTrackIndexToChannelIndex;
|
||||
animationChannelTrackIndexToChannelIndex.EnsureCapacity(tracksCount * 3);
|
||||
for (int32 trackIndex = 0; trackIndex < tracksCount; trackIndex++)
|
||||
@@ -325,6 +362,36 @@ bool Animation::SaveTimeline(BytesContainer& data)
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 19:
|
||||
{
|
||||
// Animation Event
|
||||
int32 count;
|
||||
stream.ReadInt32(&count);
|
||||
auto& eventTrack = Events.AddOne();
|
||||
eventTrack.First = name;
|
||||
eventTrack.Second.Resize(count);
|
||||
for (int32 i = 0; i < count; i++)
|
||||
{
|
||||
auto& k = eventTrack.Second.GetKeyframes()[i];
|
||||
stream.ReadFloat(&k.Time);
|
||||
stream.ReadFloat(&k.Value.Duration);
|
||||
stream.ReadStringAnsi(&k.Value.TypeName, 13);
|
||||
const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(k.Value.TypeName);
|
||||
k.Value.Instance = NewObject<AnimEvent>(typeHandle);
|
||||
stream.ReadJson(k.Value.Instance);
|
||||
if (!k.Value.Instance)
|
||||
{
|
||||
LOG(Error, "Failed to spawn object of type {0}.", String(k.Value.TypeName));
|
||||
continue;
|
||||
}
|
||||
if (!_registeredForScriptingReload)
|
||||
{
|
||||
_registeredForScriptingReload = true;
|
||||
Level::ScriptsReloadStart.Bind<Animation, &Animation::OnScriptsReloadStart>(this);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG(Error, "Unsupported track type {0} for animation.", trackType);
|
||||
return true;
|
||||
@@ -364,7 +431,7 @@ bool Animation::Save(const StringView& path)
|
||||
MemoryWriteStream stream(4096);
|
||||
|
||||
// Info
|
||||
stream.WriteInt32(100);
|
||||
stream.WriteInt32(101);
|
||||
stream.WriteDouble(Data.Duration);
|
||||
stream.WriteDouble(Data.FramesPerSecond);
|
||||
stream.WriteBool(Data.EnableRootMotion);
|
||||
@@ -381,6 +448,22 @@ bool Animation::Save(const StringView& path)
|
||||
Serialization::Serialize(stream, anim.Scale);
|
||||
}
|
||||
|
||||
// Animation events
|
||||
stream.WriteInt32(Events.Count());
|
||||
for (int32 i = 0; i < Events.Count(); i++)
|
||||
{
|
||||
auto& e = Events[i];
|
||||
stream.WriteString(e.First, 172);
|
||||
stream.WriteInt32(e.Second.GetKeyframes().Count());
|
||||
for (const auto& k : e.Second.GetKeyframes())
|
||||
{
|
||||
stream.WriteFloat(k.Time);
|
||||
stream.WriteFloat(k.Value.Duration);
|
||||
stream.WriteStringAnsi(k.Value.TypeName, 17);
|
||||
stream.WriteJson(k.Value.Instance);
|
||||
}
|
||||
}
|
||||
|
||||
// Set data to the chunk asset
|
||||
auto chunk0 = GetOrCreateChunk(0);
|
||||
ASSERT(chunk0 != nullptr);
|
||||
@@ -419,6 +502,24 @@ void Animation::OnSkinnedModelUnloaded(Asset* obj)
|
||||
MappingCache.Remove(i);
|
||||
}
|
||||
|
||||
void Animation::OnScriptingDispose()
|
||||
{
|
||||
// Dispose any events to prevent crashes (scripting is released before content)
|
||||
for (auto& e : Events)
|
||||
{
|
||||
for (auto& k : e.Second.GetKeyframes())
|
||||
{
|
||||
if (k.Value.Instance)
|
||||
{
|
||||
Delete(k.Value.Instance);
|
||||
k.Value.Instance = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BinaryAsset::OnScriptingDispose();
|
||||
}
|
||||
|
||||
Asset::LoadResult Animation::load()
|
||||
{
|
||||
// Get stream with animations data
|
||||
@@ -432,6 +533,7 @@ Asset::LoadResult Animation::load()
|
||||
switch (headerVersion)
|
||||
{
|
||||
case 100:
|
||||
case 101:
|
||||
{
|
||||
stream.ReadInt32(&headerVersion);
|
||||
stream.ReadDouble(&Data.Duration);
|
||||
@@ -471,13 +573,72 @@ Asset::LoadResult Animation::load()
|
||||
}
|
||||
}
|
||||
|
||||
// Animation events
|
||||
if (headerVersion >= 101)
|
||||
{
|
||||
int32 eventTracksCount;
|
||||
stream.ReadInt32(&eventTracksCount);
|
||||
Events.Resize(eventTracksCount, false);
|
||||
#if !USE_EDITOR
|
||||
StringAnsi typeName;
|
||||
#endif
|
||||
for (int32 i = 0; i < eventTracksCount; i++)
|
||||
{
|
||||
auto& e = Events[i];
|
||||
stream.ReadString(&e.First, 172);
|
||||
int32 eventsCount;
|
||||
stream.ReadInt32(&eventsCount);
|
||||
e.Second.GetKeyframes().Resize(eventsCount);
|
||||
for (auto& k : e.Second.GetKeyframes())
|
||||
{
|
||||
stream.ReadFloat(&k.Time);
|
||||
stream.ReadFloat(&k.Value.Duration);
|
||||
#if USE_EDITOR
|
||||
StringAnsi& typeName = k.Value.TypeName;
|
||||
#endif
|
||||
stream.ReadStringAnsi(&typeName, 17);
|
||||
const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(typeName);
|
||||
k.Value.Instance = NewObject<AnimEvent>(typeHandle);
|
||||
stream.ReadJson(k.Value.Instance);
|
||||
if (!k.Value.Instance)
|
||||
{
|
||||
LOG(Error, "Failed to spawn object of type {0}.", String(typeName));
|
||||
continue;
|
||||
}
|
||||
#if USE_EDITOR
|
||||
if (!_registeredForScriptingReload)
|
||||
{
|
||||
_registeredForScriptingReload = true;
|
||||
Level::ScriptsReloadStart.Bind<Animation, &Animation::OnScriptsReloadStart>(this);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return LoadResult::Ok;
|
||||
}
|
||||
|
||||
void Animation::unload(bool isReloading)
|
||||
{
|
||||
#if USE_EDITOR
|
||||
if (_registeredForScriptingReload)
|
||||
{
|
||||
_registeredForScriptingReload = false;
|
||||
Level::ScriptsReloadStart.Unbind<Animation, &Animation::OnScriptsReloadStart>(this);
|
||||
}
|
||||
#endif
|
||||
ClearCache();
|
||||
Data.Dispose();
|
||||
for (const auto& e : Events)
|
||||
{
|
||||
for (const auto& k : e.Second.GetKeyframes())
|
||||
{
|
||||
if (k.Value.Instance)
|
||||
Delete(k.Value.Instance);
|
||||
}
|
||||
}
|
||||
Events.Clear();
|
||||
}
|
||||
|
||||
AssetChunksFlag Animation::getChunksToPreload() const
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "Engine/Animations/AnimationData.h"
|
||||
|
||||
class SkinnedModel;
|
||||
class AnimEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Asset that contains an animation spline represented by a set of keyframes, each representing an endpoint of a linear curve.
|
||||
@@ -18,7 +19,7 @@ DECLARE_BINARY_ASSET_HEADER(Animation, 1);
|
||||
/// <summary>
|
||||
/// Contains basic information about the animation asset contents.
|
||||
/// </summary>
|
||||
API_STRUCT() struct InfoData
|
||||
API_STRUCT() struct FLAXENGINE_API InfoData
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_NO_SPAWN(InfoData);
|
||||
|
||||
@@ -48,6 +49,25 @@ DECLARE_BINARY_ASSET_HEADER(Animation, 1);
|
||||
API_FIELD() int32 MemoryUsage;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Contains <see cref="AnimEvent"/> instance.
|
||||
/// </summary>
|
||||
struct FLAXENGINE_API AnimEventData
|
||||
{
|
||||
float Duration = 0.0f;
|
||||
AnimEvent* Instance = nullptr;
|
||||
#if USE_EDITOR
|
||||
StringAnsi TypeName;
|
||||
#endif
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
#if USE_EDITOR
|
||||
bool _registeredForScriptingReload = false;
|
||||
void OnScriptsReloadStart();
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
@@ -55,6 +75,11 @@ public:
|
||||
/// </summary>
|
||||
AnimationData Data;
|
||||
|
||||
/// <summary>
|
||||
/// The animation events (keyframes per named track).
|
||||
/// </summary>
|
||||
Array<Pair<String, StepCurve<AnimEventData>>> Events;
|
||||
|
||||
/// <summary>
|
||||
/// Contains the mapping for every skeleton node to the animation data channels.
|
||||
/// Can be used for a simple lookup or to check if a given node is animated (unused nodes are using -1 index).
|
||||
@@ -138,6 +163,11 @@ private:
|
||||
|
||||
void OnSkinnedModelUnloaded(Asset* obj);
|
||||
|
||||
public:
|
||||
|
||||
// [BinaryAsset]
|
||||
void OnScriptingDispose() override;
|
||||
|
||||
protected:
|
||||
|
||||
// [BinaryAsset]
|
||||
|
||||
@@ -624,7 +624,9 @@ void VisualScriptExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value&
|
||||
{
|
||||
// Evaluate method parameter value from the current scope
|
||||
auto& scope = ThreadStacks.Get().Stack->Scope;
|
||||
value = scope->Parameters[boxBase->ID - 1];
|
||||
int32 index = boxBase->ID - 1;
|
||||
if (index < scope->Parameters.Length())
|
||||
value = scope->Parameters.Get()[index];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "Engine/Core/Delegate.h"
|
||||
#include "Engine/Core/Types/String.h"
|
||||
#include "Engine/Core/Types/DateTime.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Platform/CriticalSection.h"
|
||||
#include "Engine/Serialization/FileReadStream.h"
|
||||
#include "Engine/Threading/ThreadLocal.h"
|
||||
|
||||
@@ -34,7 +34,7 @@ ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context)
|
||||
DeleteMe<FileWriteStream> outputDeleteMe(output);
|
||||
|
||||
const auto name = StringUtils::GetFileNameWithoutExtension(asset->GetPath()).ToStringAnsi();
|
||||
output->WriteTextFormatted("# Exported model {0}\n", name.Get());
|
||||
output->WriteText(StringAnsi::Format("# Exported model {0}\n", name.Get()));
|
||||
|
||||
// Extract all meshes
|
||||
const auto& lod = asset->LODs[lodIndex];
|
||||
@@ -63,12 +63,12 @@ ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context)
|
||||
}
|
||||
auto ib = stream.Read<byte>(indicesCount * ibStride);
|
||||
|
||||
output->WriteTextFormatted("# Mesh {0}\n", meshIndex);
|
||||
output->WriteText(StringAnsi::Format("# Mesh {0}\n", meshIndex));
|
||||
|
||||
for (uint32 i = 0; i < vertices; i++)
|
||||
{
|
||||
auto v = vb0[i].Position;
|
||||
output->WriteTextFormatted("v {0} {1} {2}\n", v.X, v.Y, v.Z);
|
||||
output->WriteText(StringAnsi::Format("v {0} {1} {2}\n", v.X, v.Y, v.Z));
|
||||
}
|
||||
|
||||
output->WriteChar('\n');
|
||||
@@ -76,7 +76,7 @@ ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context)
|
||||
for (uint32 i = 0; i < vertices; i++)
|
||||
{
|
||||
auto v = vb1[i].TexCoord;
|
||||
output->WriteTextFormatted("vt {0} {1}\n", Float16Compressor::Decompress(v.X), Float16Compressor::Decompress(v.Y));
|
||||
output->WriteText(StringAnsi::Format("vt {0} {1}\n", Float16Compressor::Decompress(v.X), Float16Compressor::Decompress(v.Y)));
|
||||
}
|
||||
|
||||
output->WriteChar('\n');
|
||||
@@ -84,7 +84,7 @@ ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context)
|
||||
for (uint32 i = 0; i < vertices; i++)
|
||||
{
|
||||
auto v = vb1[i].Normal.ToVector3() * 2.0f - 1.0f;
|
||||
output->WriteTextFormatted("vn {0} {1} {2}\n", v.X, v.Y, v.Z);
|
||||
output->WriteText(StringAnsi::Format("vn {0} {1} {2}\n", v.X, v.Y, v.Z));
|
||||
}
|
||||
|
||||
output->WriteChar('\n');
|
||||
@@ -97,7 +97,7 @@ ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context)
|
||||
auto i0 = vertexStart + *t++;
|
||||
auto i1 = vertexStart + *t++;
|
||||
auto i2 = vertexStart + *t++;
|
||||
output->WriteTextFormatted("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n", i0, i1, i2);
|
||||
output->WriteText(StringAnsi::Format("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n", i0, i1, i2));
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -108,7 +108,7 @@ ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context)
|
||||
auto i0 = vertexStart + *t++;
|
||||
auto i1 = vertexStart + *t++;
|
||||
auto i2 = vertexStart + *t++;
|
||||
output->WriteTextFormatted("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n", i0, i1, i2);
|
||||
output->WriteText(StringAnsi::Format("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n", i0, i1, i2));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ ExportAssetResult AssetExporters::ExportSkinnedModel(ExportAssetContext& context
|
||||
DeleteMe<FileWriteStream> outputDeleteMe(output);
|
||||
|
||||
const auto name = StringUtils::GetFileNameWithoutExtension(asset->GetPath()).ToStringAnsi();
|
||||
output->WriteTextFormatted("# Exported model {0}\n", name.Get());
|
||||
output->WriteText(StringAnsi::Format("# Exported model {0}\n", name.Get()));
|
||||
|
||||
// Extract all meshes
|
||||
const auto& lod = asset->LODs[lodIndex];
|
||||
@@ -168,12 +168,12 @@ ExportAssetResult AssetExporters::ExportSkinnedModel(ExportAssetContext& context
|
||||
auto vb0 = stream.Read<VB0SkinnedElementType>(vertices);
|
||||
auto ib = stream.Read<byte>(indicesCount * ibStride);
|
||||
|
||||
output->WriteTextFormatted("# Mesh {0}\n", meshIndex);
|
||||
output->WriteText(StringAnsi::Format("# Mesh {0}\n", meshIndex));
|
||||
|
||||
for (uint32 i = 0; i < vertices; i++)
|
||||
{
|
||||
auto v = vb0[i].Position;
|
||||
output->WriteTextFormatted("v {0} {1} {2}\n", v.X, v.Y, v.Z);
|
||||
output->WriteText(StringAnsi::Format("v {0} {1} {2}\n", v.X, v.Y, v.Z));
|
||||
}
|
||||
|
||||
output->WriteChar('\n');
|
||||
@@ -181,7 +181,7 @@ ExportAssetResult AssetExporters::ExportSkinnedModel(ExportAssetContext& context
|
||||
for (uint32 i = 0; i < vertices; i++)
|
||||
{
|
||||
auto v = vb0[i].TexCoord;
|
||||
output->WriteTextFormatted("vt {0} {1}\n", Float16Compressor::Decompress(v.X), Float16Compressor::Decompress(v.Y));
|
||||
output->WriteText(StringAnsi::Format("vt {0} {1}\n", Float16Compressor::Decompress(v.X), Float16Compressor::Decompress(v.Y)));
|
||||
}
|
||||
|
||||
output->WriteChar('\n');
|
||||
@@ -189,7 +189,7 @@ ExportAssetResult AssetExporters::ExportSkinnedModel(ExportAssetContext& context
|
||||
for (uint32 i = 0; i < vertices; i++)
|
||||
{
|
||||
auto v = vb0[i].Normal.ToVector3() * 2.0f - 1.0f;
|
||||
output->WriteTextFormatted("vn {0} {1} {2}\n", v.X, v.Y, v.Z);
|
||||
output->WriteText(StringAnsi::Format("vn {0} {1} {2}\n", v.X, v.Y, v.Z));
|
||||
}
|
||||
|
||||
output->WriteChar('\n');
|
||||
@@ -202,7 +202,7 @@ ExportAssetResult AssetExporters::ExportSkinnedModel(ExportAssetContext& context
|
||||
auto i0 = vertexStart + *t++;
|
||||
auto i1 = vertexStart + *t++;
|
||||
auto i2 = vertexStart + *t++;
|
||||
output->WriteTextFormatted("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n", i0, i1, i2);
|
||||
output->WriteText(StringAnsi::Format("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n", i0, i1, i2));
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -213,7 +213,7 @@ ExportAssetResult AssetExporters::ExportSkinnedModel(ExportAssetContext& context
|
||||
auto i0 = vertexStart + *t++;
|
||||
auto i1 = vertexStart + *t++;
|
||||
auto i2 = vertexStart + *t++;
|
||||
output->WriteTextFormatted("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n", i0, i1, i2);
|
||||
output->WriteText(StringAnsi::Format("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n", i0, i1, i2));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
#include "Engine/Serialization/FileWriteStream.h"
|
||||
#include "Engine/Serialization/MemoryWriteStream.h"
|
||||
#include "Engine/Debug/Exceptions/Exceptions.h"
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#endif
|
||||
#include <iostream>
|
||||
|
||||
#define LOG_ENABLE_FILE (!PLATFORM_SWITCH)
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "Object.h"
|
||||
#include "ObjectsRemovalService.h"
|
||||
|
||||
RemovableObject::~RemovableObject()
|
||||
{
|
||||
#if BUILD_DEBUG
|
||||
// Prevent removing object that is still reverenced by the removal service
|
||||
ASSERT(!ObjectsRemovalService::IsInPool(this));
|
||||
#endif
|
||||
}
|
||||
|
||||
void RemovableObject::DeleteObjectNow()
|
||||
{
|
||||
ObjectsRemovalService::Dereference(this);
|
||||
|
||||
OnDeleteObject();
|
||||
}
|
||||
|
||||
void RemovableObject::DeleteObject(float timeToLive, bool useGameTime)
|
||||
{
|
||||
// Add to deferred remove (or just update timeout but don't remove object here)
|
||||
ObjectsRemovalService::Add(this, timeToLive, useGameTime);
|
||||
}
|
||||
@@ -37,30 +37,13 @@ public:
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="Object"/> class.
|
||||
/// </summary>
|
||||
virtual ~Object()
|
||||
{
|
||||
}
|
||||
virtual ~Object();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the string representation of this object.
|
||||
/// </summary>
|
||||
/// <returns>The string.</returns>
|
||||
virtual String ToString() const = 0;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Interface for removable Engine objects.
|
||||
/// </summary>
|
||||
/// <seealso cref="Object" />
|
||||
class FLAXENGINE_API RemovableObject : public Object
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Virtual destructor but protected. Removable objects should be deleted using `DeleteObject` which supports deferred delete.
|
||||
/// Note: it's unsafe to delete object using destructor it it has been marked for deferred delete.
|
||||
/// </summary>
|
||||
virtual ~RemovableObject();
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the object without queueing it to the ObjectsRemovalService.
|
||||
@@ -68,7 +51,7 @@ public:
|
||||
void DeleteObjectNow();
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the object.
|
||||
/// Deletes the object (deferred).
|
||||
/// </summary>
|
||||
/// <param name="timeToLive">The time to live (in seconds). Use zero to kill it now.</param>
|
||||
/// <param name="useGameTime">True if unscaled game time for the object life timeout, otherwise false to use absolute time.</param>
|
||||
@@ -82,3 +65,6 @@ public:
|
||||
Delete(this);
|
||||
}
|
||||
};
|
||||
|
||||
// [Deprecated on 5.01.2022, expires on 5.01.2024]
|
||||
typedef Object RemovableObject;
|
||||
|
||||
@@ -16,8 +16,8 @@ namespace ObjectsRemovalServiceImpl
|
||||
CriticalSection NewItemsLocker;
|
||||
DateTime LastUpdate;
|
||||
float LastUpdateGameTime;
|
||||
Dictionary<RemovableObject*, float> Pool(8192);
|
||||
Dictionary<RemovableObject*, float> NewItemsPool(2048);
|
||||
Dictionary<Object*, float> Pool(8192);
|
||||
Dictionary<Object*, float> NewItemsPool(2048);
|
||||
}
|
||||
|
||||
using namespace ObjectsRemovalServiceImpl;
|
||||
@@ -38,7 +38,7 @@ public:
|
||||
|
||||
ObjectsRemovalServiceService ObjectsRemovalServiceServiceInstance;
|
||||
|
||||
bool ObjectsRemovalService::IsInPool(RemovableObject* obj)
|
||||
bool ObjectsRemovalService::IsInPool(Object* obj)
|
||||
{
|
||||
if (!IsReady)
|
||||
return false;
|
||||
@@ -67,7 +67,7 @@ bool ObjectsRemovalService::HasNewItemsForFlush()
|
||||
return result;
|
||||
}
|
||||
|
||||
void ObjectsRemovalService::Dereference(RemovableObject* obj)
|
||||
void ObjectsRemovalService::Dereference(Object* obj)
|
||||
{
|
||||
if (!IsReady)
|
||||
return;
|
||||
@@ -81,7 +81,7 @@ void ObjectsRemovalService::Dereference(RemovableObject* obj)
|
||||
PoolLocker.Unlock();
|
||||
}
|
||||
|
||||
void ObjectsRemovalService::Add(RemovableObject* obj, float timeToLive, bool useGameTime)
|
||||
void ObjectsRemovalService::Add(Object* obj, float timeToLive, bool useGameTime)
|
||||
{
|
||||
ScopeLock lock(NewItemsLocker);
|
||||
|
||||
@@ -213,3 +213,24 @@ void ObjectsRemovalServiceService::Dispose()
|
||||
|
||||
IsReady = false;
|
||||
}
|
||||
|
||||
Object::~Object()
|
||||
{
|
||||
#if BUILD_DEBUG
|
||||
// Prevent removing object that is still reverenced by the removal service
|
||||
ASSERT(!ObjectsRemovalService::IsInPool(this));
|
||||
#endif
|
||||
}
|
||||
|
||||
void Object::DeleteObjectNow()
|
||||
{
|
||||
ObjectsRemovalService::Dereference(this);
|
||||
|
||||
OnDeleteObject();
|
||||
}
|
||||
|
||||
void Object::DeleteObject(float timeToLive, bool useGameTime)
|
||||
{
|
||||
// Add to deferred remove (or just update timeout but don't remove object here)
|
||||
ObjectsRemovalService::Add(this, timeToLive, useGameTime);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ public:
|
||||
/// </summary>
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <returns>True if object has been registered in the pool for the removing, otherwise false.</returns>
|
||||
static bool IsInPool(RemovableObject* obj);
|
||||
static bool IsInPool(Object* obj);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether any object has been registered to be removed from pool (requests are flushed on Flush call).
|
||||
@@ -28,7 +28,7 @@ public:
|
||||
/// Removes the specified object from the dead pool (clears the reference to it).
|
||||
/// </summary>
|
||||
/// <param name="obj">The object.</param>
|
||||
static void Dereference(RemovableObject* obj);
|
||||
static void Dereference(Object* obj);
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified object to the dead pool.
|
||||
@@ -36,7 +36,7 @@ public:
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <param name="timeToLive">The time to live (in seconds).</param>
|
||||
/// <param name="useGameTime">True if unscaled game time for the object life timeout, otherwise false to use absolute time.</param>
|
||||
static void Add(RemovableObject* obj, float timeToLive = 1.0f, bool useGameTime = false);
|
||||
static void Add(Object* obj, float timeToLive = 1.0f, bool useGameTime = false);
|
||||
|
||||
/// <summary>
|
||||
/// Flushes the objects pool removing objects marked to remove now (with negative or zero time to live).
|
||||
|
||||
@@ -337,6 +337,8 @@ public:
|
||||
void ReserveSpace(int32 length)
|
||||
{
|
||||
ASSERT(length >= 0);
|
||||
if (length == _length)
|
||||
return;
|
||||
Platform::Free(_data);
|
||||
if (length != 0)
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "Foliage.h"
|
||||
|
||||
FoliageType::FoliageType()
|
||||
: PersistentScriptingObject(SpawnParams(Guid::New(), TypeInitializer))
|
||||
: ScriptingObject(SpawnParams(Guid::New(), TypeInitializer))
|
||||
, Foliage(nullptr)
|
||||
, Index(-1)
|
||||
{
|
||||
|
||||
@@ -41,7 +41,7 @@ API_ENUM() enum class FoliageScalingModes
|
||||
/// <summary>
|
||||
/// Foliage mesh instances type descriptor. Defines the shared properties of the spawned mesh instances.
|
||||
/// </summary>
|
||||
API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API FoliageType : public PersistentScriptingObject, public ISerializable
|
||||
API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API FoliageType : public ScriptingObject, public ISerializable
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_NO_SPAWN(FoliageType);
|
||||
friend Foliage;
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
/// <summary>
|
||||
/// Interface for GPU device adapter.
|
||||
/// </summary>
|
||||
API_CLASS(NoSpawn, Attributes="HideInEditor") class FLAXENGINE_API GPUAdapter : public PersistentScriptingObject
|
||||
API_CLASS(NoSpawn, Attributes="HideInEditor") class FLAXENGINE_API GPUAdapter : public ScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_NO_SPAWN(GPUDevice);
|
||||
public:
|
||||
|
||||
GPUAdapter()
|
||||
: PersistentScriptingObject(SpawnParams(Guid::New(), TypeInitializer))
|
||||
: ScriptingObject(SpawnParams(Guid::New(), TypeInitializer))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include "Textures/GPUTexture.h"
|
||||
|
||||
GPUContext::GPUContext(GPUDevice* device)
|
||||
: PersistentScriptingObject(ScriptingObjectSpawnParams(Guid::New(), TypeInitializer))
|
||||
: ScriptingObject(ScriptingObjectSpawnParams(Guid::New(), TypeInitializer))
|
||||
, _device(device)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(GPUDrawIndexedIndirectArgs);
|
||||
/// <summary>
|
||||
/// Interface for GPU device context that can record and send graphics commands to the GPU in a sequence.
|
||||
/// </summary>
|
||||
API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API GPUContext : public PersistentScriptingObject
|
||||
API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API GPUContext : public ScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_NO_SPAWN(GPUContext);
|
||||
private:
|
||||
|
||||
@@ -173,12 +173,12 @@ GPUPipelineState::Description GPUPipelineState::Description::DefaultFullscreenTr
|
||||
};
|
||||
|
||||
GPUResource::GPUResource()
|
||||
: PersistentScriptingObject(SpawnParams(Guid::New(), GPUResource::TypeInitializer))
|
||||
: ScriptingObject(SpawnParams(Guid::New(), GPUResource::TypeInitializer))
|
||||
{
|
||||
}
|
||||
|
||||
GPUResource::GPUResource(const SpawnParams& params)
|
||||
: PersistentScriptingObject(params)
|
||||
: ScriptingObject(params)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -241,7 +241,7 @@ void GPUResource::OnDeleteObject()
|
||||
{
|
||||
ReleaseGPU();
|
||||
|
||||
PersistentScriptingObject::OnDeleteObject();
|
||||
ScriptingObject::OnDeleteObject();
|
||||
}
|
||||
|
||||
double GPUResourceView::DummyLastRenderTime = -1;
|
||||
@@ -262,7 +262,7 @@ struct GPUDevice::PrivateData
|
||||
GPUDevice* GPUDevice::Instance = nullptr;
|
||||
|
||||
GPUDevice::GPUDevice(RendererType type, ShaderProfile profile)
|
||||
: PersistentScriptingObject(SpawnParams(Guid::New(), TypeInitializer))
|
||||
: ScriptingObject(SpawnParams(Guid::New(), TypeInitializer))
|
||||
, _state(DeviceState::Missing)
|
||||
, _isRendering(false)
|
||||
, _wasVSyncUsed(false)
|
||||
|
||||
@@ -29,7 +29,7 @@ class MaterialBase;
|
||||
/// <summary>
|
||||
/// Graphics device object for rendering on GPU.
|
||||
/// </summary>
|
||||
API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API GPUDevice : public PersistentScriptingObject
|
||||
API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API GPUDevice : public ScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_NO_SPAWN(GPUDevice);
|
||||
public:
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
/// <summary>
|
||||
/// The base class for all GPU resources.
|
||||
/// </summary>
|
||||
API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API GPUResource : public PersistentScriptingObject
|
||||
API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API GPUResource : public ScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_NO_SPAWN(GPUResource);
|
||||
public:
|
||||
@@ -108,7 +108,7 @@ protected:
|
||||
|
||||
public:
|
||||
|
||||
// [PersistentScriptingObject]
|
||||
// [ScriptingObject]
|
||||
String ToString() const override;
|
||||
void OnDeleteObject() override;
|
||||
};
|
||||
@@ -186,14 +186,14 @@ public:
|
||||
/// <summary>
|
||||
/// Interface for GPU resources views. Shared base class for texture and buffer views.
|
||||
/// </summary>
|
||||
API_CLASS(Abstract, NoSpawn, Attributes="HideInEditor") class FLAXENGINE_API GPUResourceView : public PersistentScriptingObject
|
||||
API_CLASS(Abstract, NoSpawn, Attributes="HideInEditor") class FLAXENGINE_API GPUResourceView : public ScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_NO_SPAWN(GPUResourceView);
|
||||
protected:
|
||||
static double DummyLastRenderTime;
|
||||
|
||||
explicit GPUResourceView(const SpawnParams& params)
|
||||
: PersistentScriptingObject(params)
|
||||
: ScriptingObject(params)
|
||||
, LastRenderTime(&DummyLastRenderTime)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -167,9 +167,9 @@ struct SerializedMaterialParam
|
||||
/// <summary>
|
||||
/// Material variable object. Allows to modify material parameter value at runtime.
|
||||
/// </summary>
|
||||
API_CLASS(NoSpawn) class FLAXENGINE_API MaterialParameter : public PersistentScriptingObject
|
||||
API_CLASS(NoSpawn) class FLAXENGINE_API MaterialParameter : public ScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(MaterialParameter, PersistentScriptingObject);
|
||||
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(MaterialParameter, ScriptingObject);
|
||||
friend MaterialParams;
|
||||
friend MaterialInstance;
|
||||
private:
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
/// <summary>
|
||||
/// The material slot descriptor that specifies how to render geometry using it.
|
||||
/// </summary>
|
||||
API_CLASS(NoSpawn) class FLAXENGINE_API MaterialSlot : public PersistentScriptingObject
|
||||
API_CLASS(NoSpawn) class FLAXENGINE_API MaterialSlot : public ScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(MaterialSlot, PersistentScriptingObject);
|
||||
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(MaterialSlot, ScriptingObject);
|
||||
|
||||
/// <summary>
|
||||
/// The material to use for rendering.
|
||||
|
||||
@@ -14,7 +14,7 @@ class ModelBase;
|
||||
/// <summary>
|
||||
/// Base class for model resources meshes.
|
||||
/// </summary>
|
||||
API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API MeshBase : public PersistentScriptingObject
|
||||
API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API MeshBase : public ScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(MeshBase);
|
||||
protected:
|
||||
@@ -30,7 +30,7 @@ protected:
|
||||
bool _use16BitIndexBuffer;
|
||||
|
||||
explicit MeshBase(const SpawnParams& params)
|
||||
: PersistentScriptingObject(params)
|
||||
: ScriptingObject(params)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@ class MemoryReadStream;
|
||||
/// <summary>
|
||||
/// Represents single Level Of Detail for the model. Contains a collection of the meshes.
|
||||
/// </summary>
|
||||
API_CLASS(NoSpawn) class FLAXENGINE_API ModelLOD : public PersistentScriptingObject
|
||||
API_CLASS(NoSpawn) class FLAXENGINE_API ModelLOD : public ScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(ModelLOD, PersistentScriptingObject);
|
||||
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(ModelLOD, ScriptingObject);
|
||||
friend Model;
|
||||
friend Mesh;
|
||||
private:
|
||||
|
||||
@@ -10,9 +10,9 @@ class MemoryReadStream;
|
||||
/// <summary>
|
||||
/// Represents single Level Of Detail for the skinned model. Contains a collection of the meshes.
|
||||
/// </summary>
|
||||
API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedModelLOD : public PersistentScriptingObject
|
||||
API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedModelLOD : public ScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(SkinnedModelLOD, PersistentScriptingObject);
|
||||
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(SkinnedModelLOD, ScriptingObject);
|
||||
friend SkinnedModel;
|
||||
private:
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include "Engine/Engine/Engine.h"
|
||||
|
||||
RenderBuffers::RenderBuffers(const SpawnParams& params)
|
||||
: PersistentScriptingObject(params)
|
||||
: ScriptingObject(params)
|
||||
{
|
||||
#define CREATE_TEXTURE(name) name = GPUDevice::Instance->CreateTexture(TEXT(#name)); _resources.Add(name)
|
||||
CREATE_TEXTURE(DepthBuffer);
|
||||
@@ -183,8 +183,3 @@ void RenderBuffers::Release()
|
||||
UPDATE_LAZY_KEEP_RT(LuminanceMap);
|
||||
#undef UPDATE_LAZY_KEEP_RT
|
||||
}
|
||||
|
||||
String RenderBuffers::ToString() const
|
||||
{
|
||||
return TEXT("RenderBuffers");
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
/// <summary>
|
||||
/// The scene rendering buffers container.
|
||||
/// </summary>
|
||||
API_CLASS() class FLAXENGINE_API RenderBuffers : public PersistentScriptingObject
|
||||
API_CLASS() class FLAXENGINE_API RenderBuffers : public ScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE(RenderBuffers);
|
||||
protected:
|
||||
@@ -175,9 +175,4 @@ public:
|
||||
/// Release the buffers data.
|
||||
/// </summary>
|
||||
API_FUNCTION() void Release();
|
||||
|
||||
public:
|
||||
|
||||
// [PersistentScriptingObject]
|
||||
String ToString() const override;
|
||||
};
|
||||
|
||||
@@ -82,7 +82,7 @@ void RenderTask::DrawAll()
|
||||
}
|
||||
|
||||
RenderTask::RenderTask(const SpawnParams& params)
|
||||
: PersistentScriptingObject(params)
|
||||
: ScriptingObject(params)
|
||||
{
|
||||
// Register
|
||||
TasksLocker.Lock();
|
||||
|
||||
@@ -24,7 +24,7 @@ class Actor;
|
||||
/// <summary>
|
||||
/// Allows to perform custom rendering using graphics pipeline.
|
||||
/// </summary>
|
||||
API_CLASS() class FLAXENGINE_API RenderTask : public PersistentScriptingObject
|
||||
API_CLASS() class FLAXENGINE_API RenderTask : public ScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE(RenderTask);
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
/// <summary>
|
||||
/// Base class for all input device objects.
|
||||
/// </summary>
|
||||
API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API InputDevice : public PersistentScriptingObject
|
||||
API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API InputDevice : public ScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_NO_SPAWN(InputDevice);
|
||||
public:
|
||||
@@ -86,7 +86,7 @@ protected:
|
||||
EventQueue _queue;
|
||||
|
||||
explicit InputDevice(const SpawnParams& params, const StringView& name)
|
||||
: PersistentScriptingObject(params)
|
||||
: ScriptingObject(params)
|
||||
, _name(name)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -971,7 +971,7 @@ private:
|
||||
|
||||
public:
|
||||
|
||||
// [PersistentScriptingObject]
|
||||
// [ScriptingObject]
|
||||
String ToString() const override;
|
||||
void OnDeleteObject() override;
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include "Editor/Editor.h"
|
||||
#include "Engine/Platform/MessageBox.h"
|
||||
#include "Engine/Engine/CommandLine.h"
|
||||
#include "Engine/Serialization/JsonSerializer.h"
|
||||
#endif
|
||||
|
||||
bool LayersMask::HasLayer(const StringView& layerName) const
|
||||
@@ -74,11 +75,25 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
struct ScriptsReloadObject
|
||||
{
|
||||
StringAnsi TypeName;
|
||||
ScriptingObject** Object;
|
||||
Array<byte> Data;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
namespace LevelImpl
|
||||
{
|
||||
Array<SceneAction*> _sceneActions;
|
||||
CriticalSection _sceneActionsLocker;
|
||||
DateTime _lastSceneLoadTime(0);
|
||||
#if USE_EDITOR
|
||||
Array<ScriptsReloadObject> ScriptsReloadObjects;
|
||||
#endif
|
||||
|
||||
void CallSceneEvent(SceneEventType eventType, Scene* scene, Guid sceneId);
|
||||
|
||||
@@ -128,9 +143,11 @@ Delegate<Scene*, const Guid&> Level::SceneLoaded;
|
||||
Delegate<Scene*, const Guid&> Level::SceneLoadError;
|
||||
Delegate<Scene*, const Guid&> Level::SceneUnloading;
|
||||
Delegate<Scene*, const Guid&> Level::SceneUnloaded;
|
||||
#if USE_EDITOR
|
||||
Action Level::ScriptsReloadStart;
|
||||
Action Level::ScriptsReload;
|
||||
Action Level::ScriptsReloadEnd;
|
||||
#endif
|
||||
Array<String> Level::Tags;
|
||||
String Level::Layers[32];
|
||||
|
||||
@@ -559,6 +576,24 @@ public:
|
||||
Level::ScriptsReload();
|
||||
Scripting::Reload();
|
||||
|
||||
// Restore objects
|
||||
for (auto& e : ScriptsReloadObjects)
|
||||
{
|
||||
const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(e.TypeName);
|
||||
*e.Object = ScriptingObject::NewObject(typeHandle);
|
||||
if (!*e.Object)
|
||||
{
|
||||
LOG(Warning, "Failed to restore hot-reloaded object of type {0}.", String(e.TypeName));
|
||||
continue;
|
||||
}
|
||||
auto* serializable = ScriptingObject::ToInterface<ISerializable>(*e.Object);
|
||||
if (serializable && e.Data.HasItems())
|
||||
{
|
||||
JsonSerializer::LoadFromBytes(serializable, e.Data, FLAXENGINE_VERSION_BUILD);
|
||||
}
|
||||
}
|
||||
ScriptsReloadObjects.Clear();
|
||||
|
||||
// Restore scenes (from memory)
|
||||
for (int32 i = 0; i < scenesCount; i++)
|
||||
{
|
||||
@@ -595,6 +630,20 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
void Level::ScriptsReloadRegisterObject(ScriptingObject*& obj)
|
||||
{
|
||||
if (!obj)
|
||||
return;
|
||||
auto& e = ScriptsReloadObjects.AddOne();
|
||||
e.Object = &obj;
|
||||
e.TypeName = obj->GetType().Fullname;
|
||||
if (auto* serializable = ScriptingObject::ToInterface<ISerializable>(obj))
|
||||
e.Data = JsonSerializer::SaveToBytes(serializable);
|
||||
ScriptingObject* o = obj;
|
||||
obj = nullptr;
|
||||
o->DeleteObjectNow();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
class SpawnActorAction : public SceneAction
|
||||
@@ -605,9 +654,9 @@ public:
|
||||
ScriptingObjectReference<Actor> ParentActor;
|
||||
|
||||
SpawnActorAction(Actor* actor, Actor* parent)
|
||||
: TargetActor(actor)
|
||||
, ParentActor(parent)
|
||||
{
|
||||
TargetActor = actor;
|
||||
ParentActor = parent;
|
||||
}
|
||||
|
||||
bool Do() const override
|
||||
@@ -623,8 +672,8 @@ public:
|
||||
ScriptingObjectReference<Actor> TargetActor;
|
||||
|
||||
DeleteActorAction(Actor* actor)
|
||||
: TargetActor(actor)
|
||||
{
|
||||
TargetActor = actor;
|
||||
}
|
||||
|
||||
bool Do() const override
|
||||
|
||||
@@ -215,6 +215,8 @@ public:
|
||||
/// </summary>
|
||||
API_EVENT() static Delegate<Scene*, const Guid&> SceneUnloaded;
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
/// <summary>
|
||||
/// Fired when scene starts reloading scripts.
|
||||
/// </summary>
|
||||
@@ -230,6 +232,14 @@ public:
|
||||
/// </summary>
|
||||
API_EVENT() static Action ScriptsReloadEnd;
|
||||
|
||||
/// <summary>
|
||||
/// Adds object to preserve during scripts reload. Called during ScriptsReloadStart event to serialize and destroy the object that should be restored when scripts reload ends.
|
||||
/// </summary>
|
||||
/// <param name="obj">Reference to the object to preserve during the scripting reload.</param>
|
||||
static void ScriptsReloadRegisterObject(ScriptingObject*& obj);
|
||||
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -55,7 +55,7 @@ typedef Dictionary<Guid, Actor*, HeapAllocation> ActorsLookup;
|
||||
/// <summary>
|
||||
/// Base class for objects that are parts of the scene (actors and scripts).
|
||||
/// </summary>
|
||||
API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API SceneObject : public PersistentScriptingObject, public ISerializable
|
||||
API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API SceneObject : public ScriptingObject, public ISerializable
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_NO_SPAWN(SceneObject);
|
||||
friend PrefabInstanceData;
|
||||
@@ -64,7 +64,7 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(SceneObject);
|
||||
friend ScriptsFactory;
|
||||
friend SceneTicking;
|
||||
public:
|
||||
typedef PersistentScriptingObject Base;
|
||||
typedef ScriptingObject Base;
|
||||
|
||||
// Scene Object lifetime flow:
|
||||
// - Create
|
||||
|
||||
@@ -51,7 +51,7 @@ void SendPacketToPeer(ENetPeer* peer, const NetworkChannelType channelType, cons
|
||||
}
|
||||
|
||||
ENetDriver::ENetDriver(const SpawnParams& params)
|
||||
: PersistentScriptingObject(params)
|
||||
: ScriptingObject(params)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
/// <summary>
|
||||
/// Low-level network transport interface implementation based on ENet library.
|
||||
/// </summary>
|
||||
API_CLASS(Namespace="FlaxEngine.Networking", Sealed) class FLAXENGINE_API ENetDriver : public PersistentScriptingObject, public INetworkDriver
|
||||
API_CLASS(Namespace="FlaxEngine.Networking", Sealed) class FLAXENGINE_API ENetDriver : public ScriptingObject, public INetworkDriver
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE(ENetDriver);
|
||||
public:
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
/// <summary>
|
||||
/// Low-level network peer class. Provides server-client communication functions, message processing and sending.
|
||||
/// </summary>
|
||||
API_CLASS(sealed, NoSpawn, Namespace = "FlaxEngine.Networking") class FLAXENGINE_API NetworkPeer final : public PersistentScriptingObject
|
||||
API_CLASS(sealed, NoSpawn, Namespace = "FlaxEngine.Networking") class FLAXENGINE_API NetworkPeer final : public ScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_NO_SPAWN(NetworkPeer);
|
||||
friend class NetworkManager;
|
||||
@@ -30,7 +30,7 @@ public:
|
||||
/// Initializes a new instance of the <see cref="NetworkPeer"/> class.
|
||||
/// </summary>
|
||||
NetworkPeer()
|
||||
: PersistentScriptingObject(SpawnParams(Guid::New(), TypeInitializer))
|
||||
: ScriptingObject(SpawnParams(Guid::New(), TypeInitializer))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
/// <summary>
|
||||
/// Particle system parameter.
|
||||
/// </summary>
|
||||
API_CLASS(NoSpawn) class ParticleEffectParameter : public PersistentScriptingObject
|
||||
API_CLASS(NoSpawn) class ParticleEffectParameter : public ScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_NO_SPAWN(ParticleEffectParameter);
|
||||
friend ParticleEffect;
|
||||
@@ -30,7 +30,7 @@ public:
|
||||
/// Initializes a new instance of the <see cref="ParticleEffectParameter"/> class.
|
||||
/// </summary>
|
||||
ParticleEffectParameter()
|
||||
: PersistentScriptingObject(SpawnParams(Guid::New(), TypeInitializer))
|
||||
: ScriptingObject(SpawnParams(Guid::New(), TypeInitializer))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -173,6 +173,7 @@ CharacterController::CollisionFlags CharacterController::Move(const Vector3& dis
|
||||
filters.mFilterData = (PxFilterData*)&_filterData;
|
||||
filters.mFilterCallback = Physics::GetCharacterQueryFilterCallback();
|
||||
filters.mFilterFlags = PxQueryFlag::eDYNAMIC | PxQueryFlag::eSTATIC | PxQueryFlag::ePREFILTER;
|
||||
filters.mCCTFilterCallback = Physics::GetCharacterControllerFilterCallback();
|
||||
|
||||
result = (CollisionFlags)(byte)_controller->move(C2P(displacement), _minMoveDistance, deltaTime, filters);
|
||||
_lastFlags = result;
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include "Actors/PhysicsColliderActor.h"
|
||||
#include <ThirdParty/PhysX/PxScene.h>
|
||||
#include <ThirdParty/PhysX/PxQueryFiltering.h>
|
||||
#include <ThirdParty/PhysX/PxRigidDynamic.h>
|
||||
#include <ThirdParty/PhysX/characterkinematic/PxController.h>
|
||||
|
||||
// Temporary result buffer size
|
||||
#define HIT_BUFFER_SIZE 128
|
||||
@@ -217,6 +219,52 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class CharacterControllerFilter : public PxControllerFilterCallback
|
||||
{
|
||||
private:
|
||||
|
||||
PxShape* getShape(const PxController& controller)
|
||||
{
|
||||
PxRigidDynamic* actor = controller.getActor();
|
||||
|
||||
// Early out if no actor or no shapes
|
||||
if (!actor || actor->getNbShapes() < 1)
|
||||
return nullptr;
|
||||
|
||||
// Get first shape only.
|
||||
PxShape* shape = nullptr;
|
||||
actor->getShapes(&shape, 1);
|
||||
|
||||
return shape;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
bool filter(const PxController& a, const PxController& b) override
|
||||
{
|
||||
// Early out to avoid crashing
|
||||
PxShape* shapeA = getShape(a);
|
||||
if (!shapeA)
|
||||
return false;
|
||||
|
||||
PxShape* shapeB = getShape(b);
|
||||
if (!shapeB)
|
||||
return false;
|
||||
|
||||
// Let triggers through
|
||||
if (PxFilterObjectIsTrigger(shapeB->getFlags()))
|
||||
return false;
|
||||
|
||||
// Trigger the contact callback for pairs (A,B) where the filtermask of A contains the ID of B and vice versa
|
||||
const PxFilterData shapeFilterA = shapeA->getQueryFilterData();
|
||||
const PxFilterData shapeFilterB = shapeB->getQueryFilterData();
|
||||
if (shapeFilterA.word0 & shapeFilterB.word1)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
PxQueryFilterCallback* Physics::GetQueryFilterCallback()
|
||||
{
|
||||
static QueryFilter Filter;
|
||||
@@ -229,6 +277,12 @@ PxQueryFilterCallback* Physics::GetCharacterQueryFilterCallback()
|
||||
return &Filter;
|
||||
}
|
||||
|
||||
PxControllerFilterCallback* Physics::GetCharacterControllerFilterCallback()
|
||||
{
|
||||
static CharacterControllerFilter Filter;
|
||||
return &Filter;
|
||||
}
|
||||
|
||||
bool Physics::RayCast(const Vector3& origin, const Vector3& direction, const float maxDistance, uint32 layerMask, bool hitTriggers)
|
||||
{
|
||||
// Prepare data
|
||||
|
||||
@@ -112,6 +112,11 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Physics);
|
||||
/// </summary>
|
||||
static PxQueryFilterCallback* GetCharacterQueryFilterCallback();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default controller filter callback used for the character controller collisions detection.
|
||||
/// </summary>
|
||||
static PxControllerFilterCallback* GetCharacterControllerFilterCallback();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default physical material.
|
||||
/// </summary>
|
||||
|
||||
@@ -39,6 +39,7 @@ namespace physx
|
||||
class PxController;
|
||||
class PxCapsuleController;
|
||||
class PxQueryFilterCallback;
|
||||
class PxControllerFilterCallback;
|
||||
class PxHeightField;
|
||||
struct PxFilterData;
|
||||
struct PxRaycastHit;
|
||||
|
||||
@@ -112,7 +112,7 @@ UserBase::UserBase(const String& name)
|
||||
}
|
||||
|
||||
UserBase::UserBase(const SpawnParams& params, const String& name)
|
||||
: PersistentScriptingObject(params)
|
||||
: ScriptingObject(params)
|
||||
, _name(name)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ API_INJECT_CPP_CODE("#include \"Engine/Platform/User.h\"");
|
||||
/// <summary>
|
||||
/// Native platform user object.
|
||||
/// </summary>
|
||||
API_CLASS(NoSpawn, NoConstructor, Sealed, Name="User") class FLAXENGINE_API UserBase : public PersistentScriptingObject
|
||||
API_CLASS(NoSpawn, NoConstructor, Sealed, Name="User") class FLAXENGINE_API UserBase : public ScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_NO_SPAWN(UserBase);
|
||||
protected:
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
#endif
|
||||
|
||||
WindowBase::WindowBase(const CreateWindowSettings& settings)
|
||||
: PersistentScriptingObject(SpawnParams(Guid::New(), TypeInitializer))
|
||||
: ScriptingObject(SpawnParams(Guid::New(), TypeInitializer))
|
||||
, _visible(false)
|
||||
, _minimized(false)
|
||||
, _maximized(false)
|
||||
@@ -190,7 +190,7 @@ void WindowBase::OnDeleteObject()
|
||||
SAFE_DELETE(_swapChain);
|
||||
|
||||
// Base
|
||||
PersistentScriptingObject::OnDeleteObject();
|
||||
ScriptingObject::OnDeleteObject();
|
||||
}
|
||||
|
||||
bool WindowBase::GetRenderingEnabled() const
|
||||
|
||||
@@ -269,7 +269,7 @@ API_INJECT_CPP_CODE("#include \"Engine/Platform/Window.h\"");
|
||||
/// Native platform window object.
|
||||
/// </summary>
|
||||
API_CLASS(NoSpawn, NoConstructor, Sealed, Name="Window")
|
||||
class FLAXENGINE_API WindowBase : public PersistentScriptingObject
|
||||
class FLAXENGINE_API WindowBase : public ScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_NO_SPAWN(WindowBase);
|
||||
friend GPUSwapChain;
|
||||
@@ -949,7 +949,7 @@ private:
|
||||
|
||||
public:
|
||||
|
||||
// [PersistentScriptingObject]
|
||||
// [ScriptingObject]
|
||||
String ToString() const override;
|
||||
void OnDeleteObject() override;
|
||||
};
|
||||
|
||||
@@ -369,7 +369,7 @@ bool DrawCallsList::IsEmpty() const
|
||||
}
|
||||
|
||||
RenderList::RenderList(const SpawnParams& params)
|
||||
: PersistentScriptingObject(params)
|
||||
: ScriptingObject(params)
|
||||
, DirectionalLights(4)
|
||||
, PointLights(32)
|
||||
, SpotLights(32)
|
||||
|
||||
@@ -322,7 +322,7 @@ struct DrawCallsList
|
||||
/// <summary>
|
||||
/// Rendering cache container object for the draw calls collecting, sorting and executing.
|
||||
/// </summary>
|
||||
API_CLASS(Sealed) class FLAXENGINE_API RenderList : public PersistentScriptingObject
|
||||
API_CLASS(Sealed) class FLAXENGINE_API RenderList : public ScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE(RenderList);
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ using System;
|
||||
namespace FlaxEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies a options for an type reference picker in the editor. Allows to customize view or provide custom value assign policy (eg/ restrict types to inherit from a given type).
|
||||
/// Specifies a options for an type reference picker in the editor. Allows to customize view or provide custom value assign policy (eg. restrict types to inherit from a given type).
|
||||
/// </summary>
|
||||
/// <seealso cref="System.Attribute" />
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)]
|
||||
|
||||
@@ -197,7 +197,7 @@ struct MConverter<T*, typename TEnableIf<TIsBaseOf<class ScriptingObject, T>::Va
|
||||
|
||||
// Converter for Scripting Objects (collection of values).
|
||||
template<typename T>
|
||||
struct MConverter<T, typename TEnableIf<TIsBaseOf<class PersistentScriptingObject, T>::Value>::Type>
|
||||
struct MConverter<T, typename TEnableIf<TIsBaseOf<class ScriptingObject, T>::Value>::Type>
|
||||
{
|
||||
MonoObject* Box(const T& data, MonoClass* klass)
|
||||
{
|
||||
|
||||
@@ -134,7 +134,7 @@ private:
|
||||
|
||||
public:
|
||||
|
||||
// [PersistentScriptingObject]
|
||||
// [ScriptingObject]
|
||||
String ToString() const override;
|
||||
void OnDeleteObject() override;
|
||||
|
||||
|
||||
@@ -38,20 +38,32 @@ ScriptingObject::ScriptingObject(const SpawnParams& params)
|
||||
|
||||
ScriptingObject::~ScriptingObject()
|
||||
{
|
||||
// Ensure that GC handle is empty
|
||||
ASSERT(_gcHandle == 0);
|
||||
|
||||
// Ensure that object has been already unregistered
|
||||
if (IsRegistered())
|
||||
UnregisterObject();
|
||||
|
||||
Deleted(this);
|
||||
|
||||
// Get rid of managed object
|
||||
ScriptingObject::DestroyManaged();
|
||||
ASSERT(_gcHandle == 0);
|
||||
|
||||
// Handle custom scripting objects removing
|
||||
if (Flags & ObjectFlags::IsCustomScriptingType)
|
||||
{
|
||||
_type.Module->OnObjectDeleted(this);
|
||||
}
|
||||
|
||||
// Ensure that object has been already unregistered
|
||||
if (IsRegistered())
|
||||
UnregisterObject();
|
||||
}
|
||||
|
||||
ScriptingObject* ScriptingObject::NewObject(const ScriptingTypeHandle& typeHandle)
|
||||
{
|
||||
if (!typeHandle)
|
||||
return nullptr;
|
||||
auto& type = typeHandle.GetType();
|
||||
if (type.Type != ScriptingTypes::Script)
|
||||
return nullptr;
|
||||
const ScriptingObjectSpawnParams params(Guid::New(), typeHandle);
|
||||
return type.Script.Spawn(params);
|
||||
}
|
||||
|
||||
MObject* ScriptingObject::GetManagedInstance() const
|
||||
@@ -207,18 +219,49 @@ void ScriptingObject::OnManagedInstanceDeleted()
|
||||
// Unregister object
|
||||
if (IsRegistered())
|
||||
UnregisterObject();
|
||||
|
||||
// Self destruct
|
||||
DeleteObject();
|
||||
}
|
||||
|
||||
void ScriptingObject::OnScriptingDispose()
|
||||
{
|
||||
// Delete C# object
|
||||
if (IsRegistered())
|
||||
UnregisterObject();
|
||||
DestroyManaged();
|
||||
}
|
||||
|
||||
// Delete C++ object
|
||||
DeleteObject();
|
||||
bool ScriptingObject::CreateManaged()
|
||||
{
|
||||
#if USE_MONO
|
||||
MonoObject* managedInstance = CreateManagedInternal();
|
||||
if (!managedInstance)
|
||||
return true;
|
||||
|
||||
// Prevent form object GC destruction
|
||||
auto handle = mono_gchandle_new(managedInstance, false);
|
||||
auto oldHandle = Platform::InterlockedCompareExchange((int32*)&_gcHandle, *(int32*)&handle, 0);
|
||||
if (*(uint32*)&oldHandle != 0)
|
||||
{
|
||||
// Other thread already created the object before
|
||||
if (const auto monoClass = GetClass())
|
||||
{
|
||||
// Reset managed to unmanaged pointer
|
||||
const MField* monoUnmanagedPtrField = monoClass->GetField(ScriptingObject_unmanagedPtr);
|
||||
if (monoUnmanagedPtrField)
|
||||
{
|
||||
void* param = nullptr;
|
||||
monoUnmanagedPtrField->SetValue(managedInstance, ¶m);
|
||||
}
|
||||
}
|
||||
mono_gchandle_free(handle);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Ensure to be registered
|
||||
if (!IsRegistered())
|
||||
RegisterObject();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#if USE_MONO
|
||||
@@ -233,8 +276,16 @@ MonoObject* ScriptingObject::CreateManagedInternal()
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Ensure to have managed domain attached (this can be called from custom native thread, eg. content loader)
|
||||
auto domain = mono_domain_get();
|
||||
if (!domain)
|
||||
{
|
||||
MCore::AttachThread();
|
||||
domain = mono_domain_get();
|
||||
}
|
||||
|
||||
// Allocate managed instance
|
||||
MonoObject* managedInstance = mono_object_new(mono_domain_get(), monoClass->GetNative());
|
||||
MonoObject* managedInstance = mono_object_new(domain, monoClass->GetNative());
|
||||
if (managedInstance == nullptr)
|
||||
{
|
||||
LOG(Warning, "Failed to create new instance of the object of type {0}", String(monoClass->GetFullName()));
|
||||
@@ -358,7 +409,7 @@ void ScriptingObject::OnDeleteObject()
|
||||
UnregisterObject();
|
||||
|
||||
// Base
|
||||
RemovableObject::OnDeleteObject();
|
||||
Object::OnDeleteObject();
|
||||
}
|
||||
|
||||
String ScriptingObject::ToString() const
|
||||
@@ -371,6 +422,24 @@ ManagedScriptingObject::ManagedScriptingObject(const SpawnParams& params)
|
||||
{
|
||||
}
|
||||
|
||||
void ManagedScriptingObject::OnManagedInstanceDeleted()
|
||||
{
|
||||
// Base
|
||||
ScriptingObject::OnManagedInstanceDeleted();
|
||||
|
||||
// Self destruct
|
||||
DeleteObject();
|
||||
}
|
||||
|
||||
void ManagedScriptingObject::OnScriptingDispose()
|
||||
{
|
||||
// Base
|
||||
ScriptingObject::OnScriptingDispose();
|
||||
|
||||
// Self destruct
|
||||
DeleteObject();
|
||||
}
|
||||
|
||||
bool ManagedScriptingObject::CreateManaged()
|
||||
{
|
||||
#if USE_MONO
|
||||
@@ -411,70 +480,6 @@ PersistentScriptingObject::PersistentScriptingObject(const SpawnParams& params)
|
||||
{
|
||||
}
|
||||
|
||||
PersistentScriptingObject::~PersistentScriptingObject()
|
||||
{
|
||||
PersistentScriptingObject::DestroyManaged();
|
||||
}
|
||||
|
||||
void PersistentScriptingObject::OnManagedInstanceDeleted()
|
||||
{
|
||||
// Cleanup
|
||||
if (_gcHandle)
|
||||
{
|
||||
#if USE_MONO
|
||||
mono_gchandle_free(_gcHandle);
|
||||
#endif
|
||||
_gcHandle = 0;
|
||||
}
|
||||
|
||||
// But do not delete itself
|
||||
}
|
||||
|
||||
void PersistentScriptingObject::OnScriptingDispose()
|
||||
{
|
||||
// Delete C# object
|
||||
if (IsRegistered())
|
||||
UnregisterObject();
|
||||
DestroyManaged();
|
||||
|
||||
// Don't delete C++ object
|
||||
}
|
||||
|
||||
bool PersistentScriptingObject::CreateManaged()
|
||||
{
|
||||
#if USE_MONO
|
||||
MonoObject* managedInstance = CreateManagedInternal();
|
||||
if (!managedInstance)
|
||||
return true;
|
||||
|
||||
// Prevent form object GC destruction
|
||||
auto handle = mono_gchandle_new(managedInstance, false);
|
||||
auto oldHandle = Platform::InterlockedCompareExchange((int32*)&_gcHandle, *(int32*)&handle, 0);
|
||||
if (*(uint32*)&oldHandle != 0)
|
||||
{
|
||||
// Other thread already created the object before
|
||||
if (const auto monoClass = GetClass())
|
||||
{
|
||||
// Reset managed to unmanaged pointer
|
||||
const MField* monoUnmanagedPtrField = monoClass->GetField(ScriptingObject_unmanagedPtr);
|
||||
if (monoUnmanagedPtrField)
|
||||
{
|
||||
void* param = nullptr;
|
||||
monoUnmanagedPtrField->SetValue(managedInstance, ¶m);
|
||||
}
|
||||
}
|
||||
mono_gchandle_free(handle);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Ensure to be registered
|
||||
if (!IsRegistered())
|
||||
RegisterObject();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
class ScriptingObjectInternal
|
||||
{
|
||||
public:
|
||||
@@ -746,7 +751,7 @@ public:
|
||||
|
||||
static ScriptingObject* Spawn(const ScriptingObjectSpawnParams& params)
|
||||
{
|
||||
return New<PersistentScriptingObject>(params);
|
||||
return New<ScriptingObject>(params);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
#include "ManagedCLR/MTypes.h"
|
||||
|
||||
/// <summary>
|
||||
/// Represents object from unmanaged memory that can use accessed via C# scripting.
|
||||
/// Represents object from unmanaged memory that can use accessed via scripting.
|
||||
/// </summary>
|
||||
API_CLASS(InBuild) class FLAXENGINE_API ScriptingObject : public RemovableObject
|
||||
API_CLASS(InBuild) class FLAXENGINE_API ScriptingObject : public Object
|
||||
{
|
||||
friend class Scripting;
|
||||
friend class BinaryModule;
|
||||
@@ -40,14 +40,23 @@ public:
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Spawns a new objects of the given type.
|
||||
/// </summary>
|
||||
// Spawns a new objects of the given type.
|
||||
static ScriptingObject* NewObject(const ScriptingTypeHandle& typeHandle);
|
||||
template<typename T>
|
||||
static T* Spawn()
|
||||
static T* NewObject()
|
||||
{
|
||||
const SpawnParams params(Guid::New(), T::TypeInitializer);
|
||||
return T::New(params);
|
||||
return (T*)NewObject(T::TypeInitializer);
|
||||
}
|
||||
template<typename T>
|
||||
static T* NewObject(const ScriptingTypeHandle& typeHandle)
|
||||
{
|
||||
auto obj = NewObject(typeHandle);
|
||||
if (obj && !obj->Is<T>())
|
||||
{
|
||||
Delete(obj);
|
||||
obj = nullptr;
|
||||
}
|
||||
return (T*)obj;
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -182,7 +191,7 @@ public:
|
||||
virtual void OnManagedInstanceDeleted();
|
||||
virtual void OnScriptingDispose();
|
||||
|
||||
virtual bool CreateManaged() = 0;
|
||||
virtual bool CreateManaged();
|
||||
virtual void DestroyManaged();
|
||||
|
||||
public:
|
||||
@@ -216,7 +225,7 @@ protected:
|
||||
|
||||
public:
|
||||
|
||||
// [RemovableObject]
|
||||
// [Object]
|
||||
void OnDeleteObject() override;
|
||||
String ToString() const override;
|
||||
};
|
||||
@@ -239,32 +248,17 @@ public:
|
||||
public:
|
||||
|
||||
// [ScriptingObject]
|
||||
bool CreateManaged() override;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Managed object that uses pinned GC handle to prevent collecting and moving.
|
||||
/// Used by the objects that lifetime is controlled by the C++ side.
|
||||
/// </summary>
|
||||
API_CLASS(InBuild) class FLAXENGINE_API PersistentScriptingObject : public ScriptingObject
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PersistentScriptingObject"/> class.
|
||||
/// </summary>
|
||||
/// <param name="params">The object initialization parameters.</param>
|
||||
explicit PersistentScriptingObject(const SpawnParams& params);
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="PersistentScriptingObject"/> class.
|
||||
/// </summary>
|
||||
~PersistentScriptingObject();
|
||||
|
||||
public:
|
||||
|
||||
// [ManagedScriptingObject]
|
||||
void OnManagedInstanceDeleted() override;
|
||||
void OnScriptingDispose() override;
|
||||
bool CreateManaged() override;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Use ScriptingObject instead.
|
||||
/// [Deprecated on 5.01.2022, expires on 5.01.2024]
|
||||
/// </summary>
|
||||
API_CLASS(InBuild) class FLAXENGINE_API PersistentScriptingObject : public ScriptingObject
|
||||
{
|
||||
public:
|
||||
PersistentScriptingObject(const SpawnParams& params);
|
||||
};
|
||||
|
||||
@@ -15,10 +15,7 @@ using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace FlaxEngine.Json
|
||||
{
|
||||
/// <summary>
|
||||
/// Objects serialization tool (json format).
|
||||
/// </summary>
|
||||
public static class JsonSerializer
|
||||
partial class JsonSerializer
|
||||
{
|
||||
internal class SerializerCache
|
||||
{
|
||||
|
||||
41
Source/Engine/Serialization/JsonSerializer.h
Normal file
41
Source/Engine/Serialization/JsonSerializer.h
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/ISerializable.h"
|
||||
#include "Engine/Core/Types/Span.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
|
||||
/// <summary>
|
||||
/// Objects serialization tool (json format).
|
||||
/// </summary>
|
||||
API_CLASS(Static, Namespace="FlaxEngine.Json") class FLAXENGINE_API JsonSerializer
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(JsonSerializer);
|
||||
|
||||
/// <summary>
|
||||
/// Performs object Json serialization to the raw bytes.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to serialize (can be null).</param>
|
||||
/// <returns>The output data.</returns>
|
||||
API_FUNCTION() static Array<byte> SaveToBytes(ISerializable* obj);
|
||||
|
||||
/// <summary>
|
||||
/// Performs object Json deserialization from the raw bytes.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to deserialize (can be null).</param>
|
||||
/// <param name="data">The source data to read from.</param>
|
||||
/// <param name="engineBuild">The engine build number of the saved data. Used to resolve old object formats when loading deprecated data.</param>
|
||||
FORCE_INLINE static void LoadFromBytes(ISerializable* obj, const Array<byte>& data, int32 engineBuild)
|
||||
{
|
||||
LoadFromBytes(obj, Span<byte>(data.Get(), data.Count()), engineBuild);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs object Json deserialization from the raw bytes.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to deserialize (can be null).</param>
|
||||
/// <param name="data">The source data to read from.</param>
|
||||
/// <param name="engineBuild">The engine build number of the saved data. Used to resolve old object formats when loading deprecated data.</param>
|
||||
API_FUNCTION() static void LoadFromBytes(ISerializable* obj, const Span<byte>& data, int32 engineBuild);
|
||||
};
|
||||
@@ -3,6 +3,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "ReadStream.h"
|
||||
#include "Engine/Platform/Platform.h"
|
||||
|
||||
/// <summary>
|
||||
/// Super fast advanced data reading from raw bytes without any overhead at all
|
||||
@@ -33,8 +34,8 @@ public:
|
||||
/// Init
|
||||
/// </summary>
|
||||
/// <param name="data">Array with data to read from</param>
|
||||
template<typename T>
|
||||
MemoryReadStream(const Array<T>& data)
|
||||
template<typename T, typename AllocationType = HeapAllocation>
|
||||
MemoryReadStream(const Array<T, AllocationType>& data)
|
||||
: MemoryReadStream(data.Get(), data.Count() * sizeof(T))
|
||||
{
|
||||
}
|
||||
@@ -52,8 +53,8 @@ public:
|
||||
/// Init stream to the custom buffer location
|
||||
/// </summary>
|
||||
/// <param name="data">Array with data to read from</param>
|
||||
template<typename T>
|
||||
FORCE_INLINE void Init(const Array<T>& data)
|
||||
template<typename T, typename AllocationType = HeapAllocation>
|
||||
FORCE_INLINE void Init(const Array<T, AllocationType>& data)
|
||||
{
|
||||
Init(data.Get(), data.Count() * sizeof(T));
|
||||
}
|
||||
|
||||
@@ -2,27 +2,19 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Stream.h"
|
||||
#include "Engine/Core/Templates.h"
|
||||
|
||||
struct CommonValue;
|
||||
struct Variant;
|
||||
struct VariantType;
|
||||
class ISerializable;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all data read streams
|
||||
/// </summary>
|
||||
class FLAXENGINE_API ReadStream : public Stream
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Virtual destructor
|
||||
/// </summary>
|
||||
virtual ~ReadStream()
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
@@ -32,8 +24,6 @@ public:
|
||||
/// <param name="bytes">Amount of bytes to read</param>
|
||||
virtual void ReadBytes(void* data, uint32 bytes) = 0;
|
||||
|
||||
public:
|
||||
|
||||
template<typename T>
|
||||
FORCE_INLINE void Read(T* data)
|
||||
{
|
||||
@@ -193,9 +183,10 @@ public:
|
||||
/// Read data array
|
||||
/// </summary>
|
||||
/// <param name="data">Array to read</param>
|
||||
template<typename T>
|
||||
void ReadArray(Array<T>* data)
|
||||
template<typename T, typename AllocationType = HeapAllocation>
|
||||
void ReadArray(Array<T, AllocationType>* data)
|
||||
{
|
||||
static_assert(TIsPODType<T>::Value, "Only POD types are valid for ReadArray.");
|
||||
int32 size;
|
||||
ReadInt32(&size);
|
||||
data->Resize(size, false);
|
||||
@@ -203,6 +194,13 @@ public:
|
||||
ReadBytes(data->Get(), size * sizeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes object from Json by reading it as a raw data (ver+length+bytes).
|
||||
/// </summary>
|
||||
/// <remarks>Reads version number, data length and actual data bytes from the stream.</remarks>
|
||||
/// <param name="obj">The object to deserialize.</param>
|
||||
void ReadJson(ISerializable* obj);
|
||||
|
||||
public:
|
||||
|
||||
// [Stream]
|
||||
|
||||
@@ -3,11 +3,16 @@
|
||||
#include "ReadStream.h"
|
||||
#include "WriteStream.h"
|
||||
#include "JsonWriters.h"
|
||||
#include "JsonSerializer.h"
|
||||
#include "MemoryReadStream.h"
|
||||
#include "Engine/Core/Types/CommonValue.h"
|
||||
#include "Engine/Core/Types/Variant.h"
|
||||
#include "Engine/Core/Collections/Dictionary.h"
|
||||
#include "Engine/Content/Asset.h"
|
||||
#include "Engine/Core/Cache.h"
|
||||
#include "Engine/Debug/DebugLog.h"
|
||||
#include "Engine/Debug/Exceptions/JsonParseException.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Scripting/ManagedSerialization.h"
|
||||
#include "Engine/Scripting/Scripting.h"
|
||||
#include "Engine/Scripting/ScriptingObject.h"
|
||||
@@ -489,12 +494,41 @@ void ReadStream::ReadVariant(Variant* data)
|
||||
}
|
||||
}
|
||||
|
||||
void ReadStream::ReadJson(ISerializable* obj)
|
||||
{
|
||||
int32 engineBuild, size;
|
||||
ReadInt32(&engineBuild);
|
||||
ReadInt32(&size);
|
||||
if (obj)
|
||||
{
|
||||
if (const auto memoryStream = dynamic_cast<MemoryReadStream*>(this))
|
||||
{
|
||||
JsonSerializer::LoadFromBytes(obj, Span<byte>((byte*)memoryStream->Read(size), size), engineBuild);
|
||||
}
|
||||
else
|
||||
{
|
||||
void* data = Allocator::Allocate(size);
|
||||
ReadBytes(data, size);
|
||||
JsonSerializer::LoadFromBytes(obj, Span<byte>((byte*)data, size), engineBuild);
|
||||
Allocator::Free(data);
|
||||
}
|
||||
}
|
||||
else
|
||||
SetPosition(GetPosition() + size);
|
||||
}
|
||||
|
||||
void WriteStream::WriteText(const StringView& text)
|
||||
{
|
||||
for (int32 i = 0; i < text.Length(); i++)
|
||||
WriteChar(text[i]);
|
||||
}
|
||||
|
||||
void WriteStream::WriteText(const StringAnsiView& text)
|
||||
{
|
||||
for (int32 i = 0; i < text.Length(); i++)
|
||||
WriteChar(text[i]);
|
||||
}
|
||||
|
||||
void WriteStream::WriteString(const StringView& data)
|
||||
{
|
||||
const int32 length = data.Length();
|
||||
@@ -519,7 +553,7 @@ void WriteStream::WriteStringAnsi(const StringAnsiView& data)
|
||||
Write(data.Get(), length);
|
||||
}
|
||||
|
||||
void WriteStream::WriteStringAnsi(const StringAnsiView& data, int16 lock)
|
||||
void WriteStream::WriteStringAnsi(const StringAnsiView& data, int8 lock)
|
||||
{
|
||||
const int32 length = data.Length();
|
||||
ASSERT(length < STREAM_MAX_STRING_LENGTH);
|
||||
@@ -734,3 +768,57 @@ void WriteStream::WriteVariant(const Variant& data)
|
||||
CRASH;
|
||||
}
|
||||
}
|
||||
|
||||
void WriteStream::WriteJson(ISerializable* obj, const void* otherObj)
|
||||
{
|
||||
WriteInt32(FLAXENGINE_VERSION_BUILD);
|
||||
if (obj)
|
||||
{
|
||||
rapidjson_flax::StringBuffer buffer;
|
||||
CompactJsonWriter writer(buffer);
|
||||
writer.StartObject();
|
||||
obj->Serialize(writer, otherObj);
|
||||
writer.EndObject();
|
||||
|
||||
WriteInt32((int32)buffer.GetSize());
|
||||
WriteBytes((byte*)buffer.GetString(), (int32)buffer.GetSize());
|
||||
}
|
||||
else
|
||||
WriteInt32(0);
|
||||
}
|
||||
|
||||
Array<byte> JsonSerializer::SaveToBytes(ISerializable* obj)
|
||||
{
|
||||
Array<byte> result;
|
||||
if (obj)
|
||||
{
|
||||
rapidjson_flax::StringBuffer buffer;
|
||||
CompactJsonWriter writer(buffer);
|
||||
writer.StartObject();
|
||||
obj->Serialize(writer, nullptr);
|
||||
writer.EndObject();
|
||||
result.Set((byte*)buffer.GetString(), (int32)buffer.GetSize());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void JsonSerializer::LoadFromBytes(ISerializable* obj, const Span<byte>& data, int32 engineBuild)
|
||||
{
|
||||
if (!obj || data.Length() == 0)
|
||||
return;
|
||||
|
||||
ISerializable::SerializeDocument document;
|
||||
{
|
||||
PROFILE_CPU_NAMED("Json.Parse");
|
||||
document.Parse((const char*)data.Get(), data.Length());
|
||||
}
|
||||
if (document.HasParseError())
|
||||
{
|
||||
Log::JsonParseException(document.GetParseError(), document.GetErrorOffset());
|
||||
return;
|
||||
}
|
||||
|
||||
auto modifier = Cache::ISerializeModifier.Get();
|
||||
modifier->EngineBuild = engineBuild;
|
||||
obj->Deserialize(document, modifier.Value);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Core.h"
|
||||
#include "Engine/Core/Types/BaseTypes.h"
|
||||
|
||||
#define FILESTREAM_BUFFER_SIZE 4096
|
||||
|
||||
@@ -2,28 +2,19 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Core/Formatting.h"
|
||||
#include "Stream.h"
|
||||
#include "Engine/Core/Templates.h"
|
||||
|
||||
struct CommonValue;
|
||||
struct Variant;
|
||||
struct VariantType;
|
||||
class ISerializable;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all data write streams
|
||||
/// </summary>
|
||||
class FLAXENGINE_API WriteStream : public Stream
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Virtual destructor
|
||||
/// </summary>
|
||||
virtual ~WriteStream()
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
@@ -33,8 +24,6 @@ public:
|
||||
/// <param name="bytes">Amount of bytes to write</param>
|
||||
virtual void WriteBytes(const void* data, uint32 bytes) = 0;
|
||||
|
||||
public:
|
||||
|
||||
template<typename T>
|
||||
FORCE_INLINE void Write(const T* data)
|
||||
{
|
||||
@@ -165,24 +154,6 @@ public:
|
||||
WriteBytes((const void*)text, sizeof(Char) * length);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
void WriteTextFormatted(const char* format, const Args& ... args)
|
||||
{
|
||||
fmt_flax::allocator_ansi allocator;
|
||||
fmt_flax::memory_buffer_ansi buffer(allocator);
|
||||
fmt_flax::format(buffer, format, args...);
|
||||
WriteText(buffer.data(), (int32)buffer.size());
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
void WriteTextFormatted(const Char* format, const Args& ... args)
|
||||
{
|
||||
fmt_flax::allocator allocator;
|
||||
fmt_flax::memory_buffer buffer(allocator);
|
||||
fmt_flax::format(buffer, format, args...);
|
||||
WriteText(buffer.data(), (int32)buffer.size());
|
||||
}
|
||||
|
||||
// Write UTF BOM character sequence
|
||||
void WriteBOM()
|
||||
{
|
||||
@@ -194,6 +165,7 @@ public:
|
||||
// Writes text to the stream
|
||||
// @param data Text to write
|
||||
void WriteText(const StringView& text);
|
||||
void WriteText(const StringAnsiView& text);
|
||||
|
||||
// Writes String to the stream
|
||||
// @param data Data to write
|
||||
@@ -211,7 +183,7 @@ public:
|
||||
// Writes Ansi String to the stream
|
||||
// @param data Data to write
|
||||
// @param lock Characters pass in the stream
|
||||
void WriteStringAnsi(const StringAnsiView& data, int16 lock);
|
||||
void WriteStringAnsi(const StringAnsiView& data, int8 lock);
|
||||
|
||||
public:
|
||||
|
||||
@@ -231,8 +203,8 @@ public:
|
||||
/// Write data array
|
||||
/// </summary>
|
||||
/// <param name="data">Array to write</param>
|
||||
template<typename T>
|
||||
void WriteArray(const Array<T>& data)
|
||||
template<typename T, typename AllocationType = HeapAllocation>
|
||||
void WriteArray(const Array<T, AllocationType>& data)
|
||||
{
|
||||
static_assert(TIsPODType<T>::Value, "Only POD types are valid for WriteArray.");
|
||||
const int32 size = data.Count();
|
||||
@@ -241,6 +213,14 @@ public:
|
||||
WriteBytes(data.Get(), size * sizeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes object to Json and writes it as a raw data (ver+length+bytes).
|
||||
/// </summary>
|
||||
/// <remarks>Writes version number, data length and actual data bytes to the stream.</remarks>
|
||||
/// <param name="obj">The object to serialize.</param>
|
||||
/// <param name="otherObj">The instance of the object to compare with and serialize only the modified properties. If null, then serialize all properties.</param>
|
||||
void WriteJson(ISerializable* obj, const void* otherObj = nullptr);
|
||||
|
||||
public:
|
||||
|
||||
// [Stream]
|
||||
|
||||
@@ -18,7 +18,7 @@ DECLARE_ENUM_EX_6(TaskState, int64, 0, Created, Failed, Canceled, Queued, Runnin
|
||||
/// <summary>
|
||||
/// Represents an asynchronous operation.
|
||||
/// </summary>
|
||||
class FLAXENGINE_API Task : public RemovableObject, public NonCopyable
|
||||
class FLAXENGINE_API Task : public Object, public NonCopyable
|
||||
{
|
||||
//
|
||||
// Tasks execution and states flow:
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace
|
||||
}
|
||||
|
||||
TaskGraphSystem::TaskGraphSystem(const SpawnParams& params)
|
||||
: PersistentScriptingObject(params)
|
||||
: ScriptingObject(params)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ void TaskGraphSystem::PostExecute(TaskGraph* graph)
|
||||
}
|
||||
|
||||
TaskGraph::TaskGraph(const SpawnParams& params)
|
||||
: PersistentScriptingObject(params)
|
||||
: ScriptingObject(params)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ class TaskGraph;
|
||||
/// <summary>
|
||||
/// System that can generate work into Task Graph for asynchronous execution.
|
||||
/// </summary>
|
||||
API_CLASS(Abstract) class FLAXENGINE_API TaskGraphSystem : public PersistentScriptingObject
|
||||
API_CLASS(Abstract) class FLAXENGINE_API TaskGraphSystem : public ScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE(TaskGraphSystem);
|
||||
friend TaskGraph;
|
||||
@@ -52,7 +52,7 @@ public:
|
||||
/// <summary>
|
||||
/// Graph-based asynchronous tasks scheduler for high-performance computing and processing.
|
||||
/// </summary>
|
||||
API_CLASS() class FLAXENGINE_API TaskGraph : public PersistentScriptingObject
|
||||
API_CLASS() class FLAXENGINE_API TaskGraph : public ScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE(TaskGraph);
|
||||
private:
|
||||
|
||||
@@ -720,9 +720,9 @@ bool ProcessMesh(ImportedModelData& result, OpenFbxImporterData& data, const ofb
|
||||
continue;
|
||||
const ofbx::Shape* shape = channel->getShape(targetShapeCount - 1);
|
||||
|
||||
if (shape->getVertexCount() != vertexCount)
|
||||
if (shape->getVertexCount() != aGeometry->getVertexCount())
|
||||
{
|
||||
LOG(Error, "Blend shape '{0}' in mesh '{1}' has different amount of vertices ({2}) than mesh ({3})", String(shape->name), mesh.Name, shape->getVertexCount(), vertexCount);
|
||||
LOG(Error, "Blend shape '{0}' in mesh '{1}' has different amount of vertices ({2}) than mesh ({3})", String(shape->name), mesh.Name, shape->getVertexCount(), aGeometry->getVertexCount());
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -730,21 +730,21 @@ bool ProcessMesh(ImportedModelData& result, OpenFbxImporterData& data, const ofb
|
||||
blendShapeData.Name = shape->name;
|
||||
blendShapeData.Weight = channel->getShapeCount() > 1 ? (float)(channel->getDeformPercent() / 100.0) : 1.0f;
|
||||
|
||||
blendShapeData.Vertices.Resize(shape->getVertexCount());
|
||||
blendShapeData.Vertices.Resize(vertexCount);
|
||||
for (int32 i = 0; i < blendShapeData.Vertices.Count(); i++)
|
||||
blendShapeData.Vertices.Get()[i].VertexIndex = i;
|
||||
|
||||
auto shapeVertices = shape->getVertices();
|
||||
for (int32 i = 0; i < blendShapeData.Vertices.Count(); i++)
|
||||
{
|
||||
auto delta = ToVector3(shapeVertices[i]) - mesh.Positions.Get()[i];
|
||||
auto delta = ToVector3(shapeVertices[i + firstVertexOffset]) - mesh.Positions.Get()[i];
|
||||
blendShapeData.Vertices.Get()[i].PositionDelta = delta;
|
||||
}
|
||||
|
||||
auto shapeNormals = shape->getNormals();
|
||||
for (int32 i = 0; i < blendShapeData.Vertices.Count(); i++)
|
||||
{
|
||||
/*auto delta = ToVector3(shapeNormals[i]) - mesh.Normals[i];
|
||||
/*auto delta = ToVector3(shapeNormals[i + firstVertexOffset]) - mesh.Normals[i];
|
||||
auto length = delta.Length();
|
||||
if (length > ZeroTolerance)
|
||||
delta /= length;*/
|
||||
|
||||
@@ -357,6 +357,26 @@ namespace FlaxEngine
|
||||
return new Matrix(stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes object from Json by reading it as a raw data (ver+length+bytes).
|
||||
/// </summary>
|
||||
/// <remarks>Reads version number, data length and actual data bytes from the stream.</remarks>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="obj">The object to deserialize.</param>
|
||||
public static void ReadJson(this BinaryReader stream, ISerializable obj)
|
||||
{
|
||||
// ReadStream::ReadJson
|
||||
var engineBuild = stream.ReadInt32();
|
||||
var size = stream.ReadInt32();
|
||||
if (obj != null)
|
||||
{
|
||||
var data = stream.ReadBytes(size);
|
||||
Json.JsonSerializer.LoadFromBytes(obj, data, engineBuild);
|
||||
}
|
||||
else
|
||||
stream.BaseStream.Seek(size, SeekOrigin.Current);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the color to the binary stream.
|
||||
/// </summary>
|
||||
@@ -548,5 +568,25 @@ namespace FlaxEngine
|
||||
stream.Write(value.M43);
|
||||
stream.Write(value.M44);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes object to Json and writes it as a raw data (ver+length+bytes).
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <remarks>Writes version number, data length and actual data bytes to the stream.</remarks>
|
||||
/// <param name="obj">The object to serialize.</param>
|
||||
public static void WriteJson(this BinaryWriter stream, ISerializable obj)
|
||||
{
|
||||
// WriteStream::WriteJson
|
||||
stream.Write(Globals.EngineBuildNumber);
|
||||
if (obj != null)
|
||||
{
|
||||
var bytes = Json.JsonSerializer.SaveToBytes(obj);
|
||||
stream.Write(bytes.Length);
|
||||
stream.Write(bytes);
|
||||
}
|
||||
else
|
||||
stream.Write(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,9 +39,9 @@ API_ENUM() enum class ChannelMask
|
||||
/// <summary>
|
||||
/// Represents a parameter in the Graph.
|
||||
/// </summary>
|
||||
API_CLASS() class FLAXENGINE_API GraphParameter : public PersistentScriptingObject
|
||||
API_CLASS() class FLAXENGINE_API GraphParameter : public ScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(GraphParameter, PersistentScriptingObject);
|
||||
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(GraphParameter, ScriptingObject);
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user