// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Viewport.Cameras;
using FlaxEngine;
using FlaxEngine.GUI;
using Object = FlaxEngine.Object;
namespace FlaxEditor.Viewport.Previews
{
///
/// Particle System asset preview editor viewport.
///
///
public class ParticleSystemPreview : AssetPreview
{
private ParticleEffect _previewEffect;
private ContextMenuButton _showBoundsButton;
private ContextMenuButton _showOriginButton;
private ContextMenuButton _showParticleCounterButton;
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; set; } = false;
///
/// Gets or sets a value indicating whether to show particle effect bounding box.
///
public bool ShowBounds
{
get => _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?.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)
{
_showBoundsButton = ViewWidgetShowMenu.AddButton("Bounds", () => ShowBounds = !ShowBounds);
_showOriginButton = ViewWidgetShowMenu.AddButton("Origin", () => ShowOrigin = !ShowOrigin);
_showParticleCounterButton = ViewWidgetShowMenu.AddButton("Particles Counter", () => ShowParticlesCounter = !ShowParticlesCounter);
}
}
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 = Vector3.One;
float maxSize = Mathf.Max(0.001f, _previewEffect.Box.Size.MaxValue);
_previewEffect.Scale = new Vector3(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();
}
// 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(Vector2.Zero, Size),
Color.Wheat,
TextAlignment.Near,
TextAlignment.Far);
}
}
///
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;
base.OnDestroy();
}
}
}