diff --git a/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs b/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs
index 408b7161f..74c7c435b 100644
--- a/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs
+++ b/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs
@@ -246,6 +246,14 @@ namespace FlaxEditor.Viewport.Previews
}
}
+ ///
+ protected override void OnDebugDraw(GPUContext context, ref RenderContext renderContext)
+ {
+ base.OnDebugDraw(context, ref renderContext);
+
+ _previewEffect.OnDebugDraw();
+ }
+
///
public override void Draw()
{
@@ -295,7 +303,8 @@ namespace FlaxEditor.Viewport.Previews
///
public override void OnDestroy()
{
- // Cleanup objects
+ if (IsDisposing)
+ return;
_previewEffect.ParticleSystem = null;
Object.Destroy(ref _previewEffect);
Object.Destroy(ref _boundsModel);
diff --git a/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs b/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs
index 6513ac9e0..92cf6e61a 100644
--- a/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs
+++ b/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs
@@ -127,6 +127,8 @@ namespace FlaxEditor.Windows.Assets
PlaySimulation = true,
Parent = _split2.Panel1
};
+ _preview.PreviewActor.ShowDebugDraw = true;
+ _preview.ShowDebugDraw = true;
// Asset properties proxy
_properties = new PropertiesProxy();
diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp
index ad6381809..bb1e977e4 100644
--- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp
+++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp
@@ -2,8 +2,16 @@
#include "ParticleEmitterGraph.CPU.h"
#include "Engine/Core/Random.h"
+#include "Engine/Core/Math/Vector2.h"
+#include "Engine/Core/Math/Vector3.h"
+#include "Engine/Core/Math/Vector4.h"
+#include "Engine/Core/Math/Matrix.h"
+#include "Engine/Core/Math/Quaternion.h"
+#include "Engine/Core/Math/BoundingBox.h"
+#include "Engine/Core/Math/BoundingSphere.h"
+#include "Engine/Core/Math/OrientedBoundingBox.h"
#include "Engine/Utilities/Noise.h"
-#include "Engine/Core/Types/CommonValue.h"
+#include "Engine/Debug/DebugDraw.h"
// ReSharper disable CppCStyleCast
// ReSharper disable CppClangTidyClangDiagnosticCastAlign
@@ -1468,3 +1476,89 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode*
#undef COLLISION_LOGIC
}
}
+
+#if USE_EDITOR
+
+void ParticleEmitterGraphCPUExecutor::DebugDrawModule(ParticleEmitterGraphCPUNode* node, const Transform& transform)
+{
+ // Skip modules that rely on particle data
+ if (node->UsePerParticleDataResolve())
+ return;
+
+ const Color color = Color::White;
+ switch (node->TypeID)
+ {
+ case 202: // Position (sphere surface)
+ case 211: // Position (sphere volume)
+ {
+ const Float3 center = transform.LocalToWorld((Float3)GetValue(node->GetBox(0), 2));
+ const float radius = (float)GetValue(node->GetBox(1), 3);
+ DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(center, radius), color, 0.0f, true);
+ break;
+ }
+ case 203: // Position (plane)
+ {
+ const Float3 center = (Float3)GetValue(node->GetBox(0), 2);
+ const Float2 size = (Float2)GetValue(node->GetBox(1), 3);
+ const Float3 halfExtent = Float3(size.X * 0.5f, 0.0f, size.Y * 0.5f);
+ OrientedBoundingBox box(halfExtent, Transform(center));
+ box.Transform(transform);
+ DEBUG_DRAW_WIRE_BOX(box, color, 0.0f, true);
+ break;
+ }
+ case 204: // Position (circle)
+ case 205: // Position (disc)
+ {
+ const Float3 center = transform.LocalToWorld((Float3)GetValue(node->GetBox(0), 2));
+ const float radius = (float)GetValue(node->GetBox(1), 3);
+ DEBUG_DRAW_WIRE_CYLINDER(center, transform.Orientation * Quaternion::Euler(90, 0, 0), radius, 0.0f, color, 0.0f, true);
+ break;
+ }
+ case 206: // Position (box surface)
+ case 207: // Position (box volume)
+ {
+ const Float3 center = (Float3)GetValue(node->GetBox(0), 2);
+ const Float3 size = (Float3)GetValue(node->GetBox(1), 3);
+ OrientedBoundingBox box(size * 0.5f, Transform(center));
+ box.Transform(transform);
+ DEBUG_DRAW_WIRE_BOX(box, color, 0.0f, true);
+ break;
+ }
+ // Position (cylinder)
+ case 208:
+ {
+ const float height = (float)GetValue(node->GetBox(2), 4);
+ const Float3 center = transform.LocalToWorld((Float3)GetValue(node->GetBox(0), 2) + Float3(0, 0, height * 0.5f));
+ const float radius = (float)GetValue(node->GetBox(1), 3);
+ DEBUG_DRAW_WIRE_CYLINDER(center, transform.Orientation * Quaternion::Euler(90, 0, 0), radius, height, color, 0.0f, true);
+ break;
+ }
+ // Position (line)
+ case 209:
+ {
+ const Float3 start = transform.LocalToWorld((Float3)GetValue(node->GetBox(0), 2));
+ const Float3 end = transform.LocalToWorld((Float3)GetValue(node->GetBox(1), 3));
+ DEBUG_DRAW_LINE(start, end, color, 0.0f, true);
+ break;
+ }
+ // Position (torus)
+ case 210:
+ {
+ const Float3 center = transform.LocalToWorld((Float3)GetValue(node->GetBox(0), 2));
+ const float radius = Math::Max((float)GetValue(node->GetBox(1), 3), ZeroTolerance);
+ const float thickness = (float)GetValue(node->GetBox(2), 4);
+ DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(center, radius + thickness), color, 0.0f, true);
+ break;
+ }
+
+ // Position (spiral)
+ case 214:
+ {
+ const Float3 center = transform.LocalToWorld((Float3)GetValue(node->GetBox(0), 2));
+ DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(center, 5.0f), color, 0.0f, true);
+ break;
+ }
+ }
+}
+
+#endif
diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp
index 39f87d561..bdfdf1956 100644
--- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp
+++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp
@@ -7,6 +7,7 @@
#include "Engine/Particles/ParticleEffect.h"
#include "Engine/Engine/Time.h"
#include "Engine/Profiler/ProfilerCPU.h"
+#include "Engine/Debug/DebugDraw.h"
ThreadLocal ParticleEmitterGraphCPUExecutor::Context;
@@ -423,6 +424,23 @@ void ParticleEmitterGraphCPUExecutor::Draw(ParticleEmitter* emitter, ParticleEff
}
}
+#if USE_EDITOR
+
+void ParticleEmitterGraphCPUExecutor::DrawDebug(ParticleEmitter* emitter, ParticleEffect* effect, ParticleEmitterInstance& data)
+{
+ // Prepare graph data
+ Init(emitter, effect, data);
+ Transform transform = emitter->SimulationSpace == ParticlesSimulationSpace::Local ? effect->GetTransform() : Transform::Identity;
+
+ // Draw modules
+ for (auto module : emitter->Graph.SpawnModules)
+ DebugDrawModule(module, transform);
+ for (auto module : emitter->Graph.InitModules)
+ DebugDrawModule(module, transform);
+}
+
+#endif
+
void ParticleEmitterGraphCPUExecutor::Update(ParticleEmitter* emitter, ParticleEffect* effect, ParticleEmitterInstance& data, float dt, bool canSpawn)
{
// Prepare data
diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h
index a31917cf8..82b7a1bef 100644
--- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h
+++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h
@@ -162,6 +162,16 @@ public:
/// The effect transform matrix.
void Draw(ParticleEmitter* emitter, ParticleEffect* effect, ParticleEmitterInstance& data, RenderContext& renderContext, Matrix& transform);
+#if USE_EDITOR
+ ///
+ /// Draws the particles debug shapes.
+ ///
+ /// The owning emitter.
+ /// The instance effect.
+ /// The instance data.
+ void DrawDebug(ParticleEmitter* emitter, ParticleEffect* effect, ParticleEmitterInstance& data);
+#endif
+
///
/// Updates the particles simulation (the CPU simulation).
///
@@ -195,6 +205,9 @@ private:
int32 ProcessSpawnModule(int32 index);
void ProcessModule(ParticleEmitterGraphCPUNode* node, int32 particlesStart, int32 particlesEnd);
+#if USE_EDITOR
+ void DebugDrawModule(ParticleEmitterGraphCPUNode* node, const Transform& transform);
+#endif
FORCE_INLINE Value GetValue(Box* box, int32 defaultValueBoxIndex)
{
diff --git a/Source/Engine/Particles/ParticleEffect.cpp b/Source/Engine/Particles/ParticleEffect.cpp
index c1031f4ac..f29c87207 100644
--- a/Source/Engine/Particles/ParticleEffect.cpp
+++ b/Source/Engine/Particles/ParticleEffect.cpp
@@ -581,10 +581,19 @@ void ParticleEffect::OnDebugDrawSelected()
{
DEBUG_DRAW_WIRE_BOX(_box, Color::Violet * 0.7f, 0, true);
- // Base
Actor::OnDebugDrawSelected();
}
+void ParticleEffect::OnDebugDraw()
+{
+ if (ShowDebugDraw)
+ {
+ Particles::DebugDraw(this);
+ }
+
+ Actor::OnDebugDraw();
+}
+
#endif
void ParticleEffect::OnLayerChanged()
diff --git a/Source/Engine/Particles/ParticleEffect.h b/Source/Engine/Particles/ParticleEffect.h
index 9be31c4c7..65600f570 100644
--- a/Source/Engine/Particles/ParticleEffect.h
+++ b/Source/Engine/Particles/ParticleEffect.h
@@ -244,6 +244,13 @@ public:
API_FIELD(Attributes="EditorDisplay(\"Particle Effect\"), EditorOrder(80), DefaultValue(0)")
int8 SortOrder = 0;
+#if USE_EDITOR
+ ///
+ /// If checked, the particle emitter debug shapes will be shawn during debug drawing. This includes particle spawn location shapes display.
+ ///
+ API_FIELD(Attributes = "EditorDisplay(\"Particle Effect\"), EditorOrder(200)") bool ShowDebugDraw = false;
+#endif
+
public:
///
/// Gets the effect parameters collection. Those parameters are instanced from the that contains a linear list of emitters and every emitter has a list of own parameters.
@@ -394,6 +401,7 @@ public:
void Draw(RenderContext& renderContext) override;
#if USE_EDITOR
void OnDebugDrawSelected() override;
+ void OnDebugDraw() override;
#endif
void OnLayerChanged() override;
void Serialize(SerializeStream& stream, const void* otherObj) override;
diff --git a/Source/Engine/Particles/Particles.cpp b/Source/Engine/Particles/Particles.cpp
index e895e0b6d..216f5953c 100644
--- a/Source/Engine/Particles/Particles.cpp
+++ b/Source/Engine/Particles/Particles.cpp
@@ -933,6 +933,7 @@ void Particles::DrawParticles(RenderContext& renderContext, ParticleEffect* effe
const DrawPass drawModes = view.Pass & effect->DrawModes;
if (drawModes == DrawPass::None || SpriteRenderer.Init())
return;
+ ConcurrentSystemLocker::ReadScope systemScope(SystemLocker);
Matrix worlds[2];
Matrix::Translation(-renderContext.View.Origin, worlds[0]); // World
renderContext.View.GetWorldMatrix(effect->GetTransform(), worlds[1]); // Local
@@ -1065,6 +1066,28 @@ void Particles::DrawParticles(RenderContext& renderContext, ParticleEffect* effe
}
}
+#if USE_EDITOR
+
+void Particles::DebugDraw(ParticleEffect* effect)
+{
+ PROFILE_CPU_NAMED("Particles.DrawDebug");
+ ConcurrentSystemLocker::ReadScope systemScope(SystemLocker);
+
+ // Draw all emitters
+ for (auto& emitterData : effect->Instance.Emitters)
+ {
+ const auto buffer = emitterData.Buffer;
+ if (!buffer)
+ continue;
+ auto emitter = buffer->Emitter;
+ if (!emitter || !emitter->IsLoaded())
+ continue;
+ emitter->GraphExecutorCPU.DrawDebug(emitter, effect, emitterData);
+ }
+}
+
+#endif
+
#if COMPILE_WITH_GPU_PARTICLES
void UpdateGPU(RenderTask* task, GPUContext* context)
diff --git a/Source/Engine/Particles/Particles.h b/Source/Engine/Particles/Particles.h
index 69d0f9dab..77f651cfa 100644
--- a/Source/Engine/Particles/Particles.h
+++ b/Source/Engine/Particles/Particles.h
@@ -52,6 +52,14 @@ public:
/// The owning actor.
static void DrawParticles(RenderContext& renderContext, ParticleEffect* effect);
+#if USE_EDITOR
+ ///
+ /// Draws the particles debug shapes.
+ ///
+ /// The owning actor.
+ static void DebugDraw(ParticleEffect* effect);
+#endif
+
public:
///
/// Enables or disables particle buffer pooling.