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.