The null-conditional operator checks for reference equality of the Object, but doesn't check the validity of the unmanaged pointer. This check is corrected in cases where the object was not immediately returned from the bindings layer and may have been destroyed earlier.
312 lines
11 KiB
C#
312 lines
11 KiB
C#
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
|
|
|
using FlaxEditor.GUI.ContextMenu;
|
|
using FlaxEditor.Viewport.Cameras;
|
|
using FlaxEditor.Viewport.Widgets;
|
|
using FlaxEngine;
|
|
using FlaxEngine.GUI;
|
|
using System;
|
|
using Object = FlaxEngine.Object;
|
|
|
|
namespace FlaxEditor.Viewport.Previews
|
|
{
|
|
/// <summary>
|
|
/// Particle System asset preview editor viewport.
|
|
/// </summary>
|
|
/// <seealso cref="AssetPreview" />
|
|
public class ParticleSystemPreview : AssetPreview
|
|
{
|
|
private bool _playSimulation = false;
|
|
private ParticleEffect _previewEffect;
|
|
private ContextMenuButton _showBoundsButton;
|
|
private ContextMenuButton _showOriginButton;
|
|
private ContextMenuButton _showParticleCounterButton;
|
|
private ViewportWidgetButton _playPauseButton;
|
|
private StaticModel _boundsModel;
|
|
private StaticModel _originModel;
|
|
private bool _showParticlesCounter;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the particle system asset to preview.
|
|
/// </summary>
|
|
public ParticleSystem System
|
|
{
|
|
get => _previewEffect.ParticleSystem;
|
|
set => _previewEffect.ParticleSystem = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the particle effect actor used to preview selected asset.
|
|
/// </summary>
|
|
public ParticleEffect PreviewActor => _previewEffect;
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether to play the particles simulation in editor.
|
|
/// </summary>
|
|
public bool PlaySimulation
|
|
{
|
|
get => _playSimulation;
|
|
set
|
|
{
|
|
if (_playSimulation == value)
|
|
return;
|
|
_playSimulation = value;
|
|
PlaySimulationChanged?.Invoke();
|
|
|
|
if (_playPauseButton != null)
|
|
_playPauseButton.Icon = value ? Editor.Instance.Icons.Pause64 : Editor.Instance.Icons.Play64;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Occurs when particles simulation playback state gets changed.
|
|
/// </summary>
|
|
public event Action PlaySimulationChanged;
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether to show particle effect bounding box.
|
|
/// </summary>
|
|
public bool ShowBounds
|
|
{
|
|
get => _boundsModel != null ? _boundsModel.IsActive : false;
|
|
set
|
|
{
|
|
if (value == ShowBounds)
|
|
return;
|
|
|
|
if (value)
|
|
{
|
|
if (!_boundsModel)
|
|
{
|
|
_boundsModel = new StaticModel
|
|
{
|
|
Model = FlaxEngine.Content.LoadAsyncInternal<Model>("Editor/Gizmo/WireBox")
|
|
};
|
|
_boundsModel.Model.WaitForLoaded();
|
|
_boundsModel.SetMaterial(0, FlaxEngine.Content.LoadAsyncInternal<MaterialBase>("Editor/Gizmo/MaterialWireFocus"));
|
|
Task.AddCustomActor(_boundsModel);
|
|
}
|
|
else if (!_boundsModel.IsActive)
|
|
{
|
|
_boundsModel.IsActive = true;
|
|
Task.AddCustomActor(_boundsModel);
|
|
}
|
|
|
|
UpdateBoundsModel();
|
|
}
|
|
else
|
|
{
|
|
_boundsModel.IsActive = false;
|
|
Task.RemoveCustomActor(_boundsModel);
|
|
}
|
|
|
|
if (_showBoundsButton != null)
|
|
_showBoundsButton.Checked = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether to show particle effect origin point.
|
|
/// </summary>
|
|
public bool ShowOrigin
|
|
{
|
|
get => _originModel != null ? _originModel.IsActive : false;
|
|
set
|
|
{
|
|
if (value == ShowOrigin)
|
|
return;
|
|
|
|
if (value)
|
|
{
|
|
if (!_originModel)
|
|
{
|
|
_originModel = new StaticModel
|
|
{
|
|
Model = FlaxEngine.Content.LoadAsyncInternal<Model>("Editor/Primitives/Sphere"),
|
|
Position = _previewEffect.Position,
|
|
Scale = new Vector3(0.1f)
|
|
};
|
|
_originModel.Model.WaitForLoaded();
|
|
_originModel.SetMaterial(0, FlaxEngine.Content.LoadAsyncInternal<MaterialBase>("Editor/Gizmo/MaterialAxisFocus"));
|
|
Task.AddCustomActor(_originModel);
|
|
}
|
|
else if (!_originModel.IsActive)
|
|
{
|
|
_originModel.IsActive = true;
|
|
Task.AddCustomActor(_originModel);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_originModel.IsActive = false;
|
|
Task.RemoveCustomActor(_originModel);
|
|
}
|
|
|
|
if (_showOriginButton != null)
|
|
_showOriginButton.Checked = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether to show spawned particles counter (for CPU particles only).
|
|
/// </summary>
|
|
public bool ShowParticlesCounter
|
|
{
|
|
get => _showParticlesCounter;
|
|
set
|
|
{
|
|
if (value == _showParticlesCounter)
|
|
return;
|
|
|
|
_showParticlesCounter = value;
|
|
|
|
if (_showParticleCounterButton != null)
|
|
_showParticleCounterButton.Checked = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="ParticleSystemPreview"/> class.
|
|
/// </summary>
|
|
/// <param name="useWidgets">if set to <c>true</c> use widgets.</param>
|
|
public ParticleSystemPreview(bool useWidgets)
|
|
: base(useWidgets, new FPSCamera())
|
|
{
|
|
// Setup preview scene
|
|
_previewEffect = new ParticleEffect
|
|
{
|
|
UseTimeScale = false,
|
|
IsLooping = true,
|
|
CustomViewRenderTask = Task
|
|
};
|
|
|
|
// Link actors for rendering
|
|
Task.AddCustomActor(_previewEffect);
|
|
|
|
if (!useWidgets)
|
|
return;
|
|
_showBoundsButton = ViewWidgetShowMenu.AddButton("Bounds", () => ShowBounds = !ShowBounds);
|
|
_showBoundsButton.CloseMenuOnClick = false;
|
|
_showOriginButton = ViewWidgetShowMenu.AddButton("Origin", () => ShowOrigin = !ShowOrigin);
|
|
_showOriginButton.CloseMenuOnClick = false;
|
|
_showParticleCounterButton = ViewWidgetShowMenu.AddButton("Particles Counter", () => ShowParticlesCounter = !ShowParticlesCounter);
|
|
_showParticleCounterButton.CloseMenuOnClick = false;
|
|
|
|
// Play/Pause widget
|
|
{
|
|
var playPauseWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
|
_playPauseButton = new ViewportWidgetButton(null, Editor.Instance.Icons.Pause64)
|
|
{
|
|
TooltipText = "Simulation playback play (F5) or pause (F6)",
|
|
Parent = playPauseWidget,
|
|
};
|
|
_playPauseButton.Clicked += button => PlaySimulation = !PlaySimulation;
|
|
playPauseWidget.Parent = this;
|
|
}
|
|
}
|
|
|
|
private void UpdateBoundsModel()
|
|
{
|
|
var bounds = _previewEffect.Box;
|
|
Transform t = Transform.Identity;
|
|
t.Translation = bounds.Center;
|
|
t.Scale = bounds.Size;
|
|
_boundsModel.Transform = t;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fits the particle system into view (scales the emitter based on the current bounds of the system).
|
|
/// </summary>
|
|
/// <param name="targetSize">The target size of the effect.</param>
|
|
public void FitIntoView(float targetSize = 300.0f)
|
|
{
|
|
_previewEffect.Scale = Float3.One;
|
|
float maxSize = Mathf.Max(0.001f, (float)_previewEffect.Box.Size.MaxValue);
|
|
_previewEffect.Scale = new Float3(targetSize / maxSize);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override bool HasLoadedAssets => _previewEffect.HasContentLoaded && base.HasLoadedAssets;
|
|
|
|
/// <inheritdoc />
|
|
public override void Update(float deltaTime)
|
|
{
|
|
base.Update(deltaTime);
|
|
|
|
// Manually update simulation
|
|
if (PlaySimulation)
|
|
{
|
|
_previewEffect.UpdateSimulation(true);
|
|
}
|
|
|
|
// Keep bounds matching the model
|
|
if (_boundsModel && _boundsModel.IsActive)
|
|
{
|
|
UpdateBoundsModel();
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void Draw()
|
|
{
|
|
base.Draw();
|
|
|
|
if (_showParticlesCounter)
|
|
{
|
|
var count = _previewEffect.ParticlesCount;
|
|
Render2D.DrawText(
|
|
Style.Current.FontSmall,
|
|
"Particles: " + count,
|
|
new Rectangle(Float2.Zero, Size),
|
|
Color.Wheat,
|
|
TextAlignment.Near,
|
|
TextAlignment.Far);
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override bool OnKeyDown(KeyboardKeys key)
|
|
{
|
|
switch (key)
|
|
{
|
|
case KeyboardKeys.F:
|
|
ViewportCamera.SetArcBallView(_previewEffect.Box);
|
|
return true;
|
|
case KeyboardKeys.Spacebar:
|
|
PlaySimulation = !PlaySimulation;
|
|
return true;
|
|
}
|
|
|
|
var inputOptions = Editor.Instance.Options.Options.Input;
|
|
if (inputOptions.Play.Process(this, key))
|
|
{
|
|
PlaySimulation = true;
|
|
return true;
|
|
}
|
|
if (inputOptions.Pause.Process(this, key))
|
|
{
|
|
PlaySimulation = false;
|
|
return true;
|
|
}
|
|
|
|
return base.OnKeyDown(key);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void OnDestroy()
|
|
{
|
|
// Cleanup objects
|
|
_previewEffect.ParticleSystem = null;
|
|
Object.Destroy(ref _previewEffect);
|
|
Object.Destroy(ref _boundsModel);
|
|
Object.Destroy(ref _originModel);
|
|
_showBoundsButton = null;
|
|
_showOriginButton = null;
|
|
_showParticleCounterButton = null;
|
|
_playPauseButton = null;
|
|
|
|
base.OnDestroy();
|
|
}
|
|
}
|
|
}
|