// 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
{
///
/// Particle System asset preview editor viewport.
///
///
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;
///
/// Gets or sets the particle system asset to preview.
///
public ParticleSystem System
{
get => _previewEffect.ParticleSystem;
set => _previewEffect.ParticleSystem = value;
}
///
/// Gets the particle effect actor used to preview selected asset.
///
public ParticleEffect PreviewActor => _previewEffect;
///
/// Gets or sets a value indicating whether to play the particles simulation in editor.
///
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;
}
}
///
/// Occurs when particles simulation playback state gets changed.
///
public event Action PlaySimulationChanged;
///
/// Gets or sets a value indicating whether to show particle effect bounding box.
///
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("Editor/Gizmo/WireBox")
};
_boundsModel.Model.WaitForLoaded();
_boundsModel.SetMaterial(0, FlaxEngine.Content.LoadAsyncInternal("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;
}
}
///
/// Gets or sets a value indicating whether to show particle effect origin point.
///
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("Editor/Primitives/Sphere"),
Position = _previewEffect.Position,
Scale = new Vector3(0.1f)
};
_originModel.Model.WaitForLoaded();
_originModel.SetMaterial(0, FlaxEngine.Content.LoadAsyncInternal("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;
}
}
///
/// Gets or sets a value indicating whether to show spawned particles counter (for CPU particles only).
///
public bool ShowParticlesCounter
{
get => _showParticlesCounter;
set
{
if (value == _showParticlesCounter)
return;
_showParticlesCounter = value;
if (_showParticleCounterButton != null)
_showParticleCounterButton.Checked = value;
}
}
///
/// Initializes a new instance of the class.
///
/// if set to true use widgets.
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;
}
///
/// Fits the particle system into view (scales the emitter based on the current bounds of the system).
///
/// The target size of the effect.
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);
}
///
public override bool HasLoadedAssets => _previewEffect.HasContentLoaded && base.HasLoadedAssets;
///
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();
}
}
///
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);
}
}
///
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);
}
///
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();
}
}
}