diff --git a/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs b/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs
index 92cf6e61a..0c8653f5b 100644
--- a/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs
+++ b/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs
@@ -113,8 +113,55 @@ namespace FlaxEditor.Windows.Assets
}
}
+ private sealed class LayoutTabProxy
+ {
+ [EditorDisplay("Layout"), CustomEditor(typeof(Editor)), NoSerialize]
+ // ReSharper disable once UnusedAutoPropertyAccessor.Local
+ public ParticleEmitterWindow Window;
+
+ private class Editor : CustomEditor
+ {
+ public override DisplayStyle Style => DisplayStyle.InlineIntoParent;
+
+ public override void Initialize(LayoutElementsContainer layout)
+ {
+ var window = (ParticleEmitterWindow)Values[0];
+ var emitter = window.Preview.Emitter;
+ if (emitter == null || !emitter.IsLoaded)
+ return;
+ var attributes = emitter.Layout;
+ var size = 0;
+ var height = 14;
+ foreach (var attribute in attributes)
+ {
+ layout.Label($" - {GetAttributeType(attribute.Format)} {attribute.Name}").Label.Height = height;
+ size += PixelFormatExtensions.SizeInBytes(attribute.Format);
+ }
+ var capacity = 0;
+ if (window.Surface != null && window.Surface.RootNode != null && window.Surface.RootNode.Values.Length > 0)
+ capacity = (int)window.Surface.RootNode.Values[0];
+ layout.Space(10);
+ layout.Label($"Particle size: {size} bytes\nParticle buffer size: {Utilities.Utils.FormatBytesCount((ulong)(size * capacity))}").Label.Height = height * 2;
+ }
+
+ private static string GetAttributeType(PixelFormat format)
+ {
+ switch (format)
+ {
+ case PixelFormat.R32_Float: return "float";
+ case PixelFormat.R32G32_Float: return "Float2";
+ case PixelFormat.R32G32B32_Float: return "Float3";
+ case PixelFormat.R32G32B32A32_Float: return "Float4";
+ case PixelFormat.R32_SInt: return "int";
+ case PixelFormat.R32_UInt: return "uint";
+ default: return format.ToString();
+ }
+ }
+ }
+ }
+
private readonly PropertiesProxy _properties;
- private Tab _previewTab;
+ private Tab _previewTab, _layoutTab;
private ToolStripButton _showSourceCodeButton;
///
@@ -135,12 +182,14 @@ namespace FlaxEditor.Windows.Assets
// Preview properties editor
_previewTab = new Tab("Preview");
- _previewTab.Presenter.Select(new PreviewProxy
- {
- Window = this,
- });
+ _previewTab.Presenter.Select(new PreviewProxy { Window = this });
_tabs.AddTab(_previewTab);
+ // Particle data layout
+ _layoutTab = new Tab("Layout");
+ _layoutTab.Presenter.Select(new LayoutTabProxy { Window = this });
+ _tabs.AddTab(_layoutTab);
+
// Surface
_surface = new ParticleEmitterSurface(this, Save, _undo)
{
@@ -239,6 +288,7 @@ namespace FlaxEditor.Windows.Assets
_asset.WaitForLoaded();
_preview.PreviewActor.ResetSimulation();
_previewTab.Presenter.BuildLayoutOnUpdate();
+ _layoutTab.Presenter.BuildLayoutOnUpdate();
}
}
@@ -255,6 +305,7 @@ namespace FlaxEditor.Windows.Assets
// Init asset properties and parameters proxy
_properties.OnLoad(this);
_previewTab.Presenter.BuildLayoutOnUpdate();
+ _layoutTab.Presenter.BuildLayoutOnUpdate();
return false;
}
diff --git a/Source/Engine/Particles/ParticleEmitter.cpp b/Source/Engine/Particles/ParticleEmitter.cpp
index 558c0172f..08344e167 100644
--- a/Source/Engine/Particles/ParticleEmitter.cpp
+++ b/Source/Engine/Particles/ParticleEmitter.cpp
@@ -519,4 +519,39 @@ bool ParticleEmitter::HasShaderCode() const
return false;
}
+Array ParticleEmitter::GetLayout() const
+{
+ Array result;
+ ScopeLock lock(Locker);
+ result.Resize(Graph.Layout.Attributes.Count());
+ for (int32 i = 0; i < result.Count(); i++)
+ {
+ auto& dst = result[i];
+ const auto& src = Graph.Layout.Attributes[i];
+ dst.Name = src.Name;
+ switch (src.ValueType)
+ {
+ case ParticleAttribute::ValueTypes::Float:
+ dst.Format = PixelFormat::R32_Float;
+ break;
+ case ParticleAttribute::ValueTypes::Float2:
+ dst.Format = PixelFormat::R32G32_Float;
+ break;
+ case ParticleAttribute::ValueTypes::Float3:
+ dst.Format = PixelFormat::R32G32B32_Float;
+ break;
+ case ParticleAttribute::ValueTypes::Float4:
+ dst.Format = PixelFormat::R32G32B32A32_Float;
+ break;
+ case ParticleAttribute::ValueTypes::Int:
+ dst.Format = PixelFormat::R32_SInt;
+ break;
+ case ParticleAttribute::ValueTypes::Uint:
+ dst.Format = PixelFormat::R32_UInt;
+ break;
+ }
+ }
+ return result;
+}
+
#endif
diff --git a/Source/Engine/Particles/ParticleEmitter.h b/Source/Engine/Particles/ParticleEmitter.h
index 1398de5db..23ee83e21 100644
--- a/Source/Engine/Particles/ParticleEmitter.h
+++ b/Source/Engine/Particles/ParticleEmitter.h
@@ -177,10 +177,16 @@ public:
void GetReferences(Array& assets, Array& files) const override;
bool Save(const StringView& path = StringView::Empty) override;
- ///
- /// Checks if the particle emitter has valid shader code present.
- ///
- API_PROPERTY() bool HasShaderCode() const;
+ API_STRUCT(Internal) struct Attribute
+ {
+ DECLARE_SCRIPTING_TYPE_MINIMAL(Attribute);
+ API_FIELD() PixelFormat Format;
+ API_FIELD() String Name;
+ };
+
+private:
+ API_PROPERTY(Internal) bool HasShaderCode() const;
+ API_PROPERTY(Internal) Array GetLayout() const;
#endif
protected:
diff --git a/Source/Engine/Particles/ParticlesData.cpp b/Source/Engine/Particles/ParticlesData.cpp
index dcdef46d7..958ada7cf 100644
--- a/Source/Engine/Particles/ParticlesData.cpp
+++ b/Source/Engine/Particles/ParticlesData.cpp
@@ -6,6 +6,89 @@
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/DynamicBuffer.h"
+int32 ParticleAttribute::GetSize() const
+{
+ switch (ValueType)
+ {
+ case ValueTypes::Float2:
+ return 8;
+ case ValueTypes::Float3:
+ return 12;
+ case ValueTypes::Float4:
+ return 16;
+ case ValueTypes::Float:
+ case ValueTypes::Int:
+ case ValueTypes::Uint:
+ return 4;
+ default:
+ return 0;
+ }
+}
+
+void ParticleLayout::Clear()
+{
+ Size = 0;
+ Attributes.Clear();
+}
+
+void ParticleLayout::UpdateLayout()
+{
+ Size = 0;
+ for (int32 i = 0; i < Attributes.Count(); i++)
+ {
+ Attributes[i].Offset = Size;
+ Size += Attributes[i].GetSize();
+ }
+}
+
+int32 ParticleLayout::FindAttribute(const StringView& name) const
+{
+ for (int32 i = 0; i < Attributes.Count(); i++)
+ {
+ if (name == Attributes[i].Name)
+ return i;
+ }
+ return -1;
+}
+
+int32 ParticleLayout::FindAttribute(const StringView& name, ParticleAttribute::ValueTypes valueType) const
+{
+ for (int32 i = 0; i < Attributes.Count(); i++)
+ {
+ if (Attributes[i].ValueType == valueType && name == Attributes[i].Name)
+ return i;
+ }
+ return -1;
+}
+
+int32 ParticleLayout::FindAttributeOffset(const StringView& name, int32 fallbackValue) const
+{
+ for (int32 i = 0; i < Attributes.Count(); i++)
+ {
+ if (name == Attributes[i].Name)
+ return Attributes[i].Offset;
+ }
+ return fallbackValue;
+}
+
+int32 ParticleLayout::FindAttributeOffset(const StringView& name, ParticleAttribute::ValueTypes valueType, int32 fallbackValue) const
+{
+ for (int32 i = 0; i < Attributes.Count(); i++)
+ {
+ if (Attributes[i].ValueType == valueType && name == Attributes[i].Name)
+ return Attributes[i].Offset;
+ }
+ return fallbackValue;
+}
+
+int32 ParticleLayout::AddAttribute(const StringView& name, ParticleAttribute::ValueTypes valueType)
+{
+ auto& a = Attributes.AddOne();
+ a.Name = String(*name, name.Length());
+ a.ValueType = valueType;
+ return Attributes.Count() - 1;
+}
+
ParticleBuffer::ParticleBuffer()
{
}
diff --git a/Source/Engine/Particles/ParticlesData.h b/Source/Engine/Particles/ParticlesData.h
index 2d81d70f0..53f63826f 100644
--- a/Source/Engine/Particles/ParticlesData.h
+++ b/Source/Engine/Particles/ParticlesData.h
@@ -52,25 +52,7 @@ struct ParticleAttribute
///
/// Gets the size of the attribute (in bytes).
///
- /// The size (in bytes).
- int32 GetSize() const
- {
- switch (ValueType)
- {
- case ValueTypes::Float2:
- return 8;
- case ValueTypes::Float3:
- return 12;
- case ValueTypes::Float4:
- return 16;
- case ValueTypes::Float:
- case ValueTypes::Int:
- case ValueTypes::Uint:
- return 4;
- default:
- return 0;
- }
- }
+ int32 GetSize() const;
};
///
@@ -93,41 +75,19 @@ public:
///
/// Clears the layout data.
///
- void Clear()
- {
- Size = 0;
- Attributes.Clear();
- }
+ void Clear();
///
/// Updates the attributes layout (calculates offset) and updates the total size of the layout.
///
- void UpdateLayout()
- {
- Size = 0;
- for (int32 i = 0; i < Attributes.Count(); i++)
- {
- Attributes[i].Offset = Size;
- Size += Attributes[i].GetSize();
- }
- }
+ void UpdateLayout();
///
/// Finds the attribute by the name.
///
/// The name.
/// The attribute index or -1 if cannot find it.
- int32 FindAttribute(const StringView& name) const
- {
- for (int32 i = 0; i < Attributes.Count(); i++)
- {
- if (name == Attributes[i].Name)
- {
- return i;
- }
- }
- return -1;
- }
+ int32 FindAttribute(const StringView& name) const;
///
/// Finds the attribute by the name and type.
@@ -135,17 +95,7 @@ public:
/// The name.
/// The type.
/// The attribute index or -1 if cannot find it.
- int32 FindAttribute(const StringView& name, ParticleAttribute::ValueTypes valueType) const
- {
- for (int32 i = 0; i < Attributes.Count(); i++)
- {
- if (Attributes[i].ValueType == valueType && name == Attributes[i].Name)
- {
- return i;
- }
- }
- return -1;
- }
+ int32 FindAttribute(const StringView& name, ParticleAttribute::ValueTypes valueType) const;
///
/// Finds the attribute offset by the name.
@@ -153,17 +103,7 @@ public:
/// The name.
/// The fallback value to return if attribute is missing.
/// The attribute offset or fallback value if cannot find it.
- int32 FindAttributeOffset(const StringView& name, int32 fallbackValue = 0) const
- {
- for (int32 i = 0; i < Attributes.Count(); i++)
- {
- if (name == Attributes[i].Name)
- {
- return Attributes[i].Offset;
- }
- }
- return fallbackValue;
- }
+ int32 FindAttributeOffset(const StringView& name, int32 fallbackValue = 0) const;
///
/// Finds the attribute offset by the name.
@@ -172,17 +112,7 @@ public:
/// The type.
/// The fallback value to return if attribute is missing.
/// The attribute offset or fallback value if cannot find it.
- int32 FindAttributeOffset(const StringView& name, ParticleAttribute::ValueTypes valueType, int32 fallbackValue = 0) const
- {
- for (int32 i = 0; i < Attributes.Count(); i++)
- {
- if (Attributes[i].ValueType == valueType && name == Attributes[i].Name)
- {
- return Attributes[i].Offset;
- }
- }
- return fallbackValue;
- }
+ int32 FindAttributeOffset(const StringView& name, ParticleAttribute::ValueTypes valueType, int32 fallbackValue = 0) const;
///
/// Gets the attribute offset by the attribute index.
@@ -201,13 +131,7 @@ public:
/// The name.
/// The value type.
/// The attribute index or -1 if cannot find it.
- int32 AddAttribute(const StringView& name, ParticleAttribute::ValueTypes valueType)
- {
- auto& a = Attributes.AddOne();
- a.Name = String(*name, name.Length());
- a.ValueType = valueType;
- return Attributes.Count() - 1;
- }
+ int32 AddAttribute(const StringView& name, ParticleAttribute::ValueTypes valueType);
};
///