// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #pragma once #include "Engine/Visject/Graph.h" #include "Engine/Content/Content.h" #include "Engine/Animations/Curve.h" #include "Engine/Particles/Types.h" #include "Engine/Particles/ParticlesSimulation.h" #include "Engine/Particles/ParticlesData.h" class ParticleEffect; // The root node type identifier #define PARTICLE_EMITTER_ROOT_NODE_TYPE GRAPH_NODE_MAKE_TYPE(14, 1) // The maximum amount of particle modules used per context #define PARTICLE_EMITTER_MAX_MODULES 32 // The maximum amount of used particles attributes per graph node #define PARTICLE_EMITTER_MAX_ATTRIBUTES_REFS_PER_NODE 4 // The maximum amount of used asset references per graph node #define PARTICLE_EMITTER_MAX_ASSET_REFS_PER_NODE 8 template class ParticleEmitterGraphNode : public Base { public: /// /// True if node is used by the particles graph (is connected to the any module input or its a enabled module). /// bool Used = false; /// /// Flag valid for used particle nodes that need per-particle data to evaluate its value (including dependant nodes linked to input boxes). Used to skip per-particle graph evaluation if graph uses the same value for all particles (eg. is not using per-particle seed or position node). /// bool UsesParticleData; /// /// Flag valid for used particle nodes that result in constant data (nothing random nor particle data). /// bool IsConstant; /// /// The cached particle attribute indices used by the simulation graph to access particle properties. /// int32 Attributes[PARTICLE_EMITTER_MAX_ATTRIBUTES_REFS_PER_NODE]; }; /// /// The Particle Emitter Graph used to simulate particles. /// template class ParticleEmitterGraph : public Base { public: typedef ValueType Value; /// /// The particle emitter module types. /// enum class ModuleType { /// /// The spawn module. /// Spawn, /// /// The init module. /// Initialize, /// /// The update module. /// Update, /// /// The render module. /// Render, }; protected: // Attributes cache int32 _attrPosition; int32 _attrVelocity; int32 _attrRotation; int32 _attrAngularVelocity; int32 _attrAge; int32 _attrLifetime; int32 _attrSpriteSize; int32 _attrScale; int32 _attrMass; int32 _attrRibbonWidth; int32 _attrColor; public: /// /// The Particle Emitter Graph data version number. Used to sync the Particle Emitter Graph data with the instances state. Handles graph reloads to enure data is valid. /// uint32 Version = 0; /// /// The cached root node. /// NodeType* Root = nullptr; /// /// The particle layout. /// ParticleLayout Layout; /// /// The particle emitter capacity (max particles limit for the simulation). /// int32 Capacity; /// /// The particles simulation space. /// ParticlesSimulationSpace SimulationSpace; /// /// The particle layout attributes default values. /// Array> AttributesDefaults; public: /// /// The particles modules for Spawn context. /// Array> SpawnModules; /// /// The particles modules for Initialize context. /// Array> InitModules; /// /// The particles modules for Update context. /// Array> UpdateModules; /// /// The particles modules for Render context. /// Array> RenderModules; /// /// The particles modules for lights rendering. /// Array> LightModules; /// /// The particles modules for sorting particles. /// Array> SortModules; /// /// The particles modules for ribbon particles rendering. /// Array> RibbonRenderingModules; protected: virtual void InitializeNode(NodeType* node) { // Skip if already initialized if (node->Used) return; node->Used = true; node->UsesParticleData = false; node->IsConstant = true; #define USE_ATTRIBUTE(name, valueType, slot) \ { \ const StringView name(TEXT(#name)); \ auto idx = Layout.FindAttribute(name, ParticleAttribute::ValueTypes::valueType); \ if (idx == -1) \ idx = Layout.AddAttribute(name, ParticleAttribute::ValueTypes::valueType); \ static_assert(slot < ARRAY_COUNT(NodeType::Attributes), "Invalid attribute index."); \ node->Attributes[slot] = idx; \ } switch (node->Type) { // == Tools == // Get Gameplay Global case GRAPH_NODE_MAKE_TYPE(7, 16): { node->Assets[0] = Content::LoadAsync((Guid)node->Values[0]); break; } // === Particles === // Particle Attribute case GRAPH_NODE_MAKE_TYPE(14, 100): case GRAPH_NODE_MAKE_TYPE(14, 303): { node->UsesParticleData = true; const StringView name(node->Values[0]); const ParticleAttribute::ValueTypes valueType = (ParticleAttribute::ValueTypes)node->Values[1].AsUint64; auto idx = Layout.FindAttribute(name, valueType); if (idx == -1) idx = Layout.AddAttribute(name, valueType); node->Attributes[0] = idx; node->Attributes[1] = (int32)valueType; static_assert(PARTICLE_EMITTER_MAX_ATTRIBUTES_REFS_PER_NODE >= 2, "Invalid node attributes count. Need more space for some data here."); break; } // Particle Position case GRAPH_NODE_MAKE_TYPE(14, 101): case GRAPH_NODE_MAKE_TYPE(14, 212): { node->UsesParticleData = true; USE_ATTRIBUTE(Position, Vector3, 0); break; } // Particle Lifetime case GRAPH_NODE_MAKE_TYPE(14, 102): { node->UsesParticleData = true; USE_ATTRIBUTE(Lifetime, Float, 0); break; } // Particle Age case GRAPH_NODE_MAKE_TYPE(14, 103): { node->UsesParticleData = true; USE_ATTRIBUTE(Age, Float, 0); break; } // Particle Color case GRAPH_NODE_MAKE_TYPE(14, 104): { node->UsesParticleData = true; USE_ATTRIBUTE(Color, Vector4, 0); break; } // Particle Velocity case GRAPH_NODE_MAKE_TYPE(14, 105): { node->UsesParticleData = true; USE_ATTRIBUTE(Velocity, Vector3, 0); break; } // Particle Sprite Size case GRAPH_NODE_MAKE_TYPE(14, 106): { node->UsesParticleData = true; USE_ATTRIBUTE(SpriteSize, Vector2, 0); break; } // Particle Mass case GRAPH_NODE_MAKE_TYPE(14, 107): { node->UsesParticleData = true; USE_ATTRIBUTE(Mass, Float, 0); break; } // Particle Rotation case GRAPH_NODE_MAKE_TYPE(14, 108): { node->UsesParticleData = true; USE_ATTRIBUTE(Rotation, Vector3, 0); break; } // Particle Angular Velocity case GRAPH_NODE_MAKE_TYPE(14, 109): { USE_ATTRIBUTE(AngularVelocity, Vector3, 0); break; } // Particle Normalized Age case GRAPH_NODE_MAKE_TYPE(14, 110): { node->UsesParticleData = true; USE_ATTRIBUTE(Age, Float, 0); USE_ATTRIBUTE(Lifetime, Float, 1); break; } // Random case GRAPH_NODE_MAKE_TYPE(14, 208): case GRAPH_NODE_MAKE_TYPE(14, 209): case GRAPH_NODE_MAKE_TYPE(14, 210): case GRAPH_NODE_MAKE_TYPE(14, 211): case GRAPH_NODE_MAKE_TYPE(14, 213): case GRAPH_NODE_MAKE_TYPE(14, 214): case GRAPH_NODE_MAKE_TYPE(14, 215): case GRAPH_NODE_MAKE_TYPE(14, 216): { node->IsConstant = false; break; } // Particle Emitter Function case GRAPH_NODE_MAKE_TYPE(14, 300): { node->Assets[0] = Content::LoadAsync((Guid)node->Values[0]); break; } // Particle Index case GRAPH_NODE_MAKE_TYPE(14, 301): node->UsesParticleData = true; break; // === Particle Modules === // Orient Sprite case GRAPH_NODE_MAKE_TYPE(15, 201): case GRAPH_NODE_MAKE_TYPE(15, 303): { USE_ATTRIBUTE(SpriteFacingMode, Int, 0); if (((ParticleSpriteFacingMode)node->Values[2].AsInt) == ParticleSpriteFacingMode::CustomFacingVector || ((ParticleSpriteFacingMode)node->Values[2].AsInt) == ParticleSpriteFacingMode::FixedAxis) { USE_ATTRIBUTE(SpriteFacingVector, Vector3, 1); } break; } // Orient Model case GRAPH_NODE_MAKE_TYPE(15, 213): case GRAPH_NODE_MAKE_TYPE(15, 309): { USE_ATTRIBUTE(ModelFacingMode, Int, 0); break; } // Update Age case GRAPH_NODE_MAKE_TYPE(15, 300): { USE_ATTRIBUTE(Age, Float, 0); break; } // Gravity/Force case GRAPH_NODE_MAKE_TYPE(15, 301): case GRAPH_NODE_MAKE_TYPE(15, 304): { USE_ATTRIBUTE(Velocity, Vector3, 0); break; } // Linear Drag case GRAPH_NODE_MAKE_TYPE(15, 310): { USE_ATTRIBUTE(Velocity, Vector3, 0); USE_ATTRIBUTE(Mass, Float, 1); if (node->Values[3].AsBool) { USE_ATTRIBUTE(SpriteSize, Vector2, 2); } break; } // Turbulence case GRAPH_NODE_MAKE_TYPE(15, 311): { USE_ATTRIBUTE(Position, Vector3, 0); USE_ATTRIBUTE(Velocity, Vector3, 1); USE_ATTRIBUTE(Mass, Float, 2); break; } // Position (plane/box surface/box volume/cylinder/line/sphere/circle/disc/torus) case GRAPH_NODE_MAKE_TYPE(15, 202): case GRAPH_NODE_MAKE_TYPE(15, 203): case GRAPH_NODE_MAKE_TYPE(15, 204): case GRAPH_NODE_MAKE_TYPE(15, 205): case GRAPH_NODE_MAKE_TYPE(15, 206): case GRAPH_NODE_MAKE_TYPE(15, 207): case GRAPH_NODE_MAKE_TYPE(15, 208): case GRAPH_NODE_MAKE_TYPE(15, 209): case GRAPH_NODE_MAKE_TYPE(15, 210): case GRAPH_NODE_MAKE_TYPE(15, 211): { USE_ATTRIBUTE(Position, Vector3, 0); break; } // Position (depth) case GRAPH_NODE_MAKE_TYPE(15, 212): { USE_ATTRIBUTE(Position, Vector3, 0); USE_ATTRIBUTE(Lifetime, Float, 1); break; } // Position (spiral) case GRAPH_NODE_MAKE_TYPE(15, 214): { USE_ATTRIBUTE(Position, Vector3, 0); USE_ATTRIBUTE(Velocity, Vector3, 1); break; } // Set Attribute case GRAPH_NODE_MAKE_TYPE(15, 200): case GRAPH_NODE_MAKE_TYPE(15, 302): { const StringView name(node->Values[2]); const ParticleAttribute::ValueTypes valueType = (ParticleAttribute::ValueTypes)node->Values[3].AsInt; auto idx = Layout.FindAttribute(name, valueType); if (idx == -1) idx = Layout.AddAttribute(name, valueType); node->Attributes[0] = idx; break; } // Set Position/Lifetime/Age/.. #define CASE_SET_PARTICLE_ATTRIBUTE(id0, id1, name, type) case GRAPH_NODE_MAKE_TYPE(15, id0): case GRAPH_NODE_MAKE_TYPE(15, id1): USE_ATTRIBUTE(name, type, 0); break CASE_SET_PARTICLE_ATTRIBUTE(250, 350, Position, Vector3); CASE_SET_PARTICLE_ATTRIBUTE(251, 351, Lifetime, Float); CASE_SET_PARTICLE_ATTRIBUTE(252, 352, Age, Float); CASE_SET_PARTICLE_ATTRIBUTE(253, 353, Color, Vector4); CASE_SET_PARTICLE_ATTRIBUTE(254, 354, Velocity, Vector3); CASE_SET_PARTICLE_ATTRIBUTE(255, 355, SpriteSize, Vector2); CASE_SET_PARTICLE_ATTRIBUTE(256, 356, Mass, Float); CASE_SET_PARTICLE_ATTRIBUTE(257, 357, Rotation, Vector3); CASE_SET_PARTICLE_ATTRIBUTE(258, 358, AngularVelocity, Vector3); CASE_SET_PARTICLE_ATTRIBUTE(259, 359, Scale, Vector3); CASE_SET_PARTICLE_ATTRIBUTE(260, 360, RibbonWidth, Float); CASE_SET_PARTICLE_ATTRIBUTE(261, 361, RibbonTwist, Float); CASE_SET_PARTICLE_ATTRIBUTE(262, 362, RibbonFacingVector, Vector3); #undef CASE_SET_PARTICLE_ATTRIBUTE // Conform to Sphere case GRAPH_NODE_MAKE_TYPE(15, 305): { USE_ATTRIBUTE(Position, Vector3, 0); USE_ATTRIBUTE(Velocity, Vector3, 1); USE_ATTRIBUTE(Mass, Float, 2); break; } // Kill (sphere/box) case GRAPH_NODE_MAKE_TYPE(15, 306): case GRAPH_NODE_MAKE_TYPE(15, 307): { USE_ATTRIBUTE(Position, Vector3, 0); break; } // Collision (plane/sphere/box/cylinder/depth) case GRAPH_NODE_MAKE_TYPE(15, 330): case GRAPH_NODE_MAKE_TYPE(15, 331): case GRAPH_NODE_MAKE_TYPE(15, 332): case GRAPH_NODE_MAKE_TYPE(15, 333): case GRAPH_NODE_MAKE_TYPE(15, 334): { USE_ATTRIBUTE(Position, Vector3, 0); USE_ATTRIBUTE(Velocity, Vector3, 1); USE_ATTRIBUTE(Age, Float, 2); break; } // Sprite Rendering case GRAPH_NODE_MAKE_TYPE(15, 400): { node->Assets[0] = Content::LoadAsync((Guid)node->Values[2]); USE_ATTRIBUTE(Position, Vector3, 0); USE_ATTRIBUTE(Rotation, Vector3, 1); USE_ATTRIBUTE(SpriteSize, Vector2, 2); break; } // Sort case GRAPH_NODE_MAKE_TYPE(15, 402): { const auto sortMode = static_cast(node->Values[2].AsInt); if (sortMode == ParticleSortMode::CustomAscending || sortMode == ParticleSortMode::CustomDescending) { const StringView name(node->Values[3]); auto idx = Layout.FindAttribute(name); if (idx == -1) { LOG(Warning, "Particles sort module uses missing particle attribute {0}.", name.Get()); } else { switch (Layout.Attributes[idx].ValueType) { case ParticleAttribute::ValueTypes::Float: case ParticleAttribute::ValueTypes::Int: case ParticleAttribute::ValueTypes::Uint: break; default: LOG(Warning, "Particles sort module uses invalid particle attribute {0} of type {1}. It has to be a scalar value.", name.Get(), (int32)Layout.Attributes[idx].ValueType); } } node->Attributes[0] = idx; } break; } // Model Rendering case GRAPH_NODE_MAKE_TYPE(15, 403): { node->Assets[0] = Content::LoadAsync((Guid)node->Values[2]); node->Assets[1] = Content::LoadAsync((Guid)node->Values[3]); USE_ATTRIBUTE(Position, Vector3, 0); USE_ATTRIBUTE(Rotation, Vector3, 1); USE_ATTRIBUTE(Scale, Vector3, 2); break; } // Ribbon Rendering case GRAPH_NODE_MAKE_TYPE(15, 404): { node->Assets[0] = Content::LoadAsync((Guid)node->Values[2]); USE_ATTRIBUTE(Position, Vector3, 0); // TODO: add support for custom sorting key - not only by age USE_ATTRIBUTE(Age, Float, 1); break; } } // Check all input boxes for (int32 i = 0; i < node->Boxes.Count(); i++) { auto box = node->Boxes[i]; for (int32 j = 0; j < box.Connections.Count(); j++) { const auto other = box.Connections[j]->template GetParent(); InitializeNode(other); // Propagate UsesParticleData from the input boxes if (other->Used) { node->UsesParticleData |= other->UsesParticleData; node->IsConstant &= other->IsConstant; } } } #undef USE_ATTRIBUTE } public: // [Graph] void Clear() override { // Clear cached data Root = nullptr; Layout.Clear(); SpawnModules.Clear(); InitModules.Clear(); UpdateModules.Clear(); RenderModules.Clear(); LightModules.Clear(); SortModules.Clear(); RibbonRenderingModules.Clear(); // Base Base::Clear(); } bool Load(ReadStream* stream, bool loadMeta) override { // Bump up version on reload Version++; // Base if (Base::Load(stream, loadMeta)) return true; // Compute particle data layout and initialize used nodes (for only used nodes, start depth searching rom the modules) Layout.AddAttribute(TEXT("Position"), ParticleAttribute::ValueTypes::Vector3); #define PROCESS_MODULES(modules) for (int32 i = 0; i < modules.Count(); i++) { InitializeNode(modules[i]); } PROCESS_MODULES(SpawnModules); PROCESS_MODULES(InitModules); PROCESS_MODULES(UpdateModules); PROCESS_MODULES(RenderModules); #undef PROCESS_MODULES Layout.UpdateLayout(); AttributesDefaults.Resize(Layout.Attributes.Count()); // Ensure that spawn modules are not using particle data (not supported) for (int32 i = SpawnModules.Count() - 1; i >= 0; --i) { if (SpawnModules[i]->UsesParticleData) { LOG(Warning, "Particle spawn module uses particle data as an input which is invalid. Disabling spawn module."); SpawnModules.RemoveAtKeepOrder(i); } } // Peek the root node options Capacity = 0; if (Root && Root->Values.Count() > 3) { Capacity = Root->Values[0].AsInt; SimulationSpace = (ParticlesSimulationSpace)Root->Values[2].AsInt; } // Cache common attributes and initialize defaults for (int32 i = 0; i < AttributesDefaults.Count(); i++) AttributesDefaults[i] = Variant::Zero; int32 idx; #define SETUP_ATTRIBUTE(name, type, defaultValue) \ idx = Layout.FindAttribute(TEXT(MACRO_TO_STR(name)), ParticleAttribute::ValueTypes::type); \ _attr##name = idx; \ if (idx != -1) \ AttributesDefaults[idx] = defaultValue SETUP_ATTRIBUTE(Position, Vector3, Variant(Vector3::Zero)); SETUP_ATTRIBUTE(Velocity, Vector3, Variant(Vector3::Zero)); SETUP_ATTRIBUTE(Rotation, Vector3, Variant(Vector3::Zero)); SETUP_ATTRIBUTE(AngularVelocity, Vector3, Variant(Vector3::Zero)); SETUP_ATTRIBUTE(Age, Float, Variant::Zero); SETUP_ATTRIBUTE(Lifetime, Float, Variant(5.0f)); SETUP_ATTRIBUTE(SpriteSize, Vector2, Variant(Vector2(50.0f))); SETUP_ATTRIBUTE(Scale, Vector3, Variant(Vector3::One)); SETUP_ATTRIBUTE(Mass, Float, Variant(1.0f)); SETUP_ATTRIBUTE(RibbonWidth, Float, Variant(10.0f)); SETUP_ATTRIBUTE(Color, Vector4, Variant(Vector4(0.0f, 0.0f, 0.0f, 1.0f))); #undef SETUP_ATTRIBUTE return false; } bool onNodeLoaded(NodeType* n) override { // Root node if (n->Type == PARTICLE_EMITTER_ROOT_NODE_TYPE) { ASSERT(!Root); Root = n; } // Particle Modules (only if module is enabled) else if (n->GroupID == 15 && n->Values[0].AsBool) { const auto moduleType = static_cast(n->Values[1].AsInt); switch (moduleType) { case ModuleType::Spawn: SpawnModules.Add(n); break; case ModuleType::Initialize: InitModules.Add(n); break; case ModuleType::Update: UpdateModules.Add(n); break; case ModuleType::Render: RenderModules.Add(n); switch (n->TypeID) { case 401: LightModules.Add(n); break; case 402: if (static_cast(n->Values[2].AsInt) != ParticleSortMode::None) SortModules.Add(n); break; case 404: RibbonRenderingModules.Add(n); break; } break; } } return Base::onNodeLoaded(n); } };