| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464 |
- // Copyright (C) 2009-2021, Panagiotis Christopoulos Charitos and contributors.
- // All rights reserved.
- // Code licensed under the BSD License.
- // http://www.anki3d.org/LICENSE
- #include <AnKi/Scene/Components/ParticleEmitterComponent.h>
- #include <AnKi/Scene/SceneGraph.h>
- #include <AnKi/Scene/SceneNode.h>
- #include <AnKi/Scene/Components/RenderComponent.h>
- #include <AnKi/Resource/ParticleEmitterResource.h>
- #include <AnKi/Resource/ResourceManager.h>
- #include <AnKi/Physics/PhysicsBody.h>
- #include <AnKi/Physics/PhysicsCollisionShape.h>
- #include <AnKi/Physics/PhysicsWorld.h>
- #include <AnKi/Math.h>
- #include <AnKi/Renderer/RenderQueue.h>
- namespace anki
- {
- ANKI_SCENE_COMPONENT_STATICS(ParticleEmitterComponent)
- static Vec3 getRandom(const Vec3& min, const Vec3& max)
- {
- Vec3 out;
- out.x() = mix(min.x(), max.x(), getRandomRange(0.0f, 1.0f));
- out.y() = mix(min.y(), max.y(), getRandomRange(0.0f, 1.0f));
- out.z() = mix(min.z(), max.z(), getRandomRange(0.0f, 1.0f));
- return out;
- }
- /// Particle base
- class ParticleEmitterComponent::ParticleBase
- {
- public:
- Second m_timeOfBirth; ///< Keep the time of birth for nice effects
- Second m_timeOfDeath = -1.0; ///< Time of death. If < 0.0 then dead
- F32 m_initialSize;
- F32 m_finalSize;
- F32 m_crntSize;
- F32 m_initialAlpha;
- F32 m_finalAlpha;
- F32 m_crntAlpha;
- Vec3 m_crntPosition;
- Bool isDead() const
- {
- return m_timeOfDeath < 0.0;
- }
- /// Kill the particle
- void killCommon()
- {
- ANKI_ASSERT(m_timeOfDeath > 0.0);
- m_timeOfDeath = -1.0;
- }
- /// Revive the particle
- void reviveCommon(const ParticleEmitterProperties& props, const Transform& trf, Second prevUpdateTime,
- Second crntTime)
- {
- ANKI_ASSERT(isDead());
- // life
- m_timeOfDeath = crntTime + getRandomRange(props.m_particle.m_minLife, props.m_particle.m_maxLife);
- m_timeOfBirth = crntTime;
- // Size
- m_initialSize = getRandomRange(props.m_particle.m_minInitialSize, props.m_particle.m_maxInitialSize);
- m_finalSize = getRandomRange(props.m_particle.m_minFinalSize, props.m_particle.m_maxFinalSize);
- // Alpha
- m_initialAlpha = getRandomRange(props.m_particle.m_minInitialAlpha, props.m_particle.m_maxInitialAlpha);
- m_finalAlpha = getRandomRange(props.m_particle.m_minFinalAlpha, props.m_particle.m_maxFinalAlpha);
- }
- /// Common sumulation code
- void simulateCommon(Second prevUpdateTime, Second crntTime)
- {
- const F32 lifeFactor = F32((crntTime - m_timeOfBirth) / (m_timeOfDeath - m_timeOfBirth));
- m_crntSize = mix(m_initialSize, m_finalSize, lifeFactor);
- m_crntAlpha = mix(m_initialAlpha, m_finalAlpha, lifeFactor);
- }
- };
- /// Simple particle for simple simulation
- class ParticleEmitterComponent::SimpleParticle : public ParticleEmitterComponent::ParticleBase
- {
- public:
- Vec3 m_velocity = Vec3(0.0f);
- Vec3 m_acceleration = Vec3(0.0f);
- void kill()
- {
- killCommon();
- }
- void revive(const ParticleEmitterProperties& props, const Transform& trf, Second prevUpdateTime, Second crntTime)
- {
- reviveCommon(props, trf, prevUpdateTime, crntTime);
- m_velocity = Vec3(0.0f);
- m_acceleration = getRandom(props.m_particle.m_minGravity, props.m_particle.m_maxGravity);
- // Set the initial position
- m_crntPosition = getRandom(props.m_particle.m_minStartingPosition, props.m_particle.m_maxStartingPosition);
- m_crntPosition += trf.getOrigin().xyz();
- }
- void simulate(Second prevUpdateTime, Second crntTime)
- {
- simulateCommon(prevUpdateTime, crntTime);
- const F32 dt = F32(crntTime - prevUpdateTime);
- const Vec3 xp = m_crntPosition;
- const Vec3 xc = m_acceleration * (dt * dt) + m_velocity * dt + xp;
- m_crntPosition = xc;
- m_velocity += m_acceleration * dt;
- }
- };
- /// Particle for bullet simulations
- class ParticleEmitterComponent::PhysicsParticle : public ParticleEmitterComponent::ParticleBase
- {
- public:
- PhysicsBodyPtr m_body;
- PhysicsParticle(const PhysicsBodyInitInfo& init, SceneNode* node, ParticleEmitterComponent* component)
- {
- m_body = node->getSceneGraph().getPhysicsWorld().newInstance<PhysicsBody>(init);
- m_body->setUserData(component);
- m_body->activate(false);
- m_body->setMaterialGroup(PhysicsMaterialBit::PARTICLE);
- m_body->setMaterialMask(PhysicsMaterialBit::STATIC_GEOMETRY);
- m_body->setAngularFactor(Vec3(0.0f));
- }
- void kill()
- {
- killCommon();
- m_body->activate(false);
- }
- void revive(const ParticleEmitterProperties& props, const Transform& trf, Second prevUpdateTime, Second crntTime)
- {
- reviveCommon(props, trf, prevUpdateTime, crntTime);
- // pre calculate
- const Bool forceFlag = props.forceEnabled();
- const Bool worldGravFlag = props.wordGravityEnabled();
- // Activate it
- m_body->activate(true);
- m_body->setLinearVelocity(Vec3(0.0f));
- m_body->setAngularVelocity(Vec3(0.0f));
- m_body->clearForces();
- // force
- if(forceFlag)
- {
- Vec3 forceDir = getRandom(props.m_particle.m_minForceDirection, props.m_particle.m_maxForceDirection);
- forceDir.normalize();
- // The forceDir depends on the particle emitter rotation
- forceDir = trf.getRotation().getRotationPart() * forceDir;
- const F32 forceMag =
- getRandomRange(props.m_particle.m_minForceMagnitude, props.m_particle.m_maxForceMagnitude);
- m_body->applyForce(forceDir * forceMag, Vec3(0.0f));
- }
- // gravity
- if(!worldGravFlag)
- {
- m_body->setGravity(getRandom(props.m_particle.m_minGravity, props.m_particle.m_maxGravity));
- }
- // Starting pos. In local space
- Vec3 pos = getRandom(props.m_particle.m_minStartingPosition, props.m_particle.m_maxStartingPosition);
- pos = trf.transform(pos);
- m_body->setTransform(Transform(pos.xyz0(), trf.getRotation(), 1.0f));
- m_crntPosition = pos;
- }
- void simulate(Second prevUpdateTime, Second crntTime)
- {
- simulateCommon(prevUpdateTime, crntTime);
- m_crntPosition = m_body->getTransform().getOrigin().xyz();
- }
- };
- ParticleEmitterComponent::ParticleEmitterComponent(SceneNode* node)
- : SceneComponent(node, getStaticClassId())
- , m_node(node)
- {
- }
- ParticleEmitterComponent::~ParticleEmitterComponent()
- {
- m_simpleParticles.destroy(m_node->getAllocator());
- m_physicsParticles.destroy(m_node->getAllocator());
- }
- Error ParticleEmitterComponent::loadParticleEmitterResource(CString filename)
- {
- // Create the debug drawer
- if(!m_dbgImage.isCreated())
- {
- ANKI_CHECK(m_node->getSceneGraph().getResourceManager().loadResource("EngineAssets/ParticleEmitter.ankitex",
- m_dbgImage));
- }
- // Load
- ANKI_CHECK(m_node->getSceneGraph().getResourceManager().loadResource(filename, m_particleEmitterResource));
- m_props = m_particleEmitterResource->getProperties();
- // Cleanup
- m_simpleParticles.destroy(m_node->getAllocator());
- m_physicsParticles.destroy(m_node->getAllocator());
- // Init particles
- m_simulationType = (m_props.m_usePhysicsEngine) ? SimulationType::PHYSICS_ENGINE : SimulationType::SIMPLE;
- if(m_simulationType == SimulationType::PHYSICS_ENGINE)
- {
- PhysicsCollisionShapePtr collisionShape = m_node->getSceneGraph().getPhysicsWorld().newInstance<PhysicsSphere>(
- m_props.m_particle.m_minInitialSize / 2.0f);
- PhysicsBodyInitInfo binit;
- binit.m_shape = collisionShape;
- m_physicsParticles.resizeStorage(m_node->getAllocator(), m_props.m_maxNumOfParticles);
- for(U32 i = 0; i < m_props.m_maxNumOfParticles; i++)
- {
- binit.m_mass = getRandomRange(m_props.m_particle.m_minMass, m_props.m_particle.m_maxMass);
- m_physicsParticles.emplaceBack(m_node->getAllocator(), binit, m_node, this);
- }
- }
- else
- {
- m_simpleParticles.create(m_node->getAllocator(), m_props.m_maxNumOfParticles);
- }
- m_vertBuffSize = m_props.m_maxNumOfParticles * VERTEX_SIZE;
- return Error::NONE;
- }
- Error ParticleEmitterComponent::update(SceneNode& node, Second prevTime, Second crntTime, Bool& updated)
- {
- if(ANKI_UNLIKELY(!m_particleEmitterResource.isCreated()))
- {
- updated = false;
- return Error::NONE;
- }
- updated = true;
- if(m_simulationType == SimulationType::SIMPLE)
- {
- simulate(prevTime, crntTime, WeakArray<SimpleParticle>(m_simpleParticles));
- }
- else
- {
- ANKI_ASSERT(m_simulationType == SimulationType::PHYSICS_ENGINE);
- simulate(prevTime, crntTime, WeakArray<PhysicsParticle>(m_physicsParticles));
- }
- return Error::NONE;
- }
- template<typename TParticle>
- void ParticleEmitterComponent::simulate(Second prevUpdateTime, Second crntTime, WeakArray<TParticle> particles)
- {
- // - Deactivate the dead particles
- // - Calc the AABB
- // - Calc the instancing stuff
- Vec3 aabbMin(MAX_F32);
- Vec3 aabbMax(MIN_F32);
- m_aliveParticleCount = 0;
- F32* verts = reinterpret_cast<F32*>(m_node->getFrameAllocator().allocate(m_vertBuffSize));
- m_verts = verts;
- F32 maxParticleSize = -1.0f;
- for(TParticle& particle : particles)
- {
- if(particle.isDead())
- {
- // if its already dead so dont deactivate it again
- continue;
- }
- if(particle.m_timeOfDeath < crntTime)
- {
- // Just died
- particle.kill();
- }
- else
- {
- // It's alive
- // Do checks
- ANKI_ASSERT((ptrToNumber(verts) + VERTEX_SIZE - ptrToNumber(m_verts)) <= m_vertBuffSize);
- // This will calculate a new world transformation
- particle.simulate(prevUpdateTime, crntTime);
- const Vec3& origin = particle.m_crntPosition;
- aabbMin = aabbMin.min(origin);
- aabbMax = aabbMax.max(origin);
- verts[0] = origin.x();
- verts[1] = origin.y();
- verts[2] = origin.z();
- verts[3] = particle.m_crntSize;
- maxParticleSize = max(maxParticleSize, particle.m_crntSize);
- verts[4] = clamp(particle.m_crntAlpha, 0.0f, 1.0f);
- ++m_aliveParticleCount;
- verts += 5;
- }
- }
- // AABB
- if(m_aliveParticleCount != 0)
- {
- ANKI_ASSERT(maxParticleSize > 0.0f);
- const Vec3 min = aabbMin - maxParticleSize;
- const Vec3 max = aabbMax + maxParticleSize;
- m_worldBoundingVolume = Aabb(min, max);
- }
- else
- {
- m_worldBoundingVolume = Aabb(Vec3(0.0f), Vec3(0.001f));
- m_verts = nullptr;
- }
- //
- // Emit new particles
- //
- if(m_timeLeftForNextEmission <= 0.0)
- {
- U particleCount = 0; // How many particles I am allowed to emmit
- for(TParticle& particle : particles)
- {
- if(!particle.isDead())
- {
- // its alive so skip it
- continue;
- }
- particle.revive(m_props, m_transform, prevUpdateTime, crntTime);
- // do the rest
- ++particleCount;
- if(particleCount >= m_props.m_particlesPerEmission)
- {
- break;
- }
- } // end for all particles
- m_timeLeftForNextEmission = m_props.m_emissionPeriod;
- } // end if can emit
- else
- {
- m_timeLeftForNextEmission -= crntTime - prevUpdateTime;
- }
- }
- void ParticleEmitterComponent::draw(RenderQueueDrawContext& ctx) const
- {
- // Early exit
- if(ANKI_UNLIKELY(m_aliveParticleCount == 0))
- {
- return;
- }
- CommandBufferPtr& cmdb = ctx.m_commandBuffer;
- if(!ctx.m_debugDraw)
- {
- // Load verts
- StagingGpuMemoryToken token;
- void* gpuStorage = ctx.m_stagingGpuAllocator->allocateFrame(m_aliveParticleCount * VERTEX_SIZE,
- StagingGpuMemoryType::VERTEX, token);
- memcpy(gpuStorage, m_verts, m_aliveParticleCount * VERTEX_SIZE);
- // Program
- ShaderProgramPtr prog;
- m_particleEmitterResource->getRenderingInfo(ctx.m_key, prog);
- cmdb->bindShaderProgram(prog);
- // Vertex attribs
- cmdb->setVertexAttribute(U32(VertexAttributeId::POSITION), 0, Format::R32G32B32_SFLOAT, 0);
- cmdb->setVertexAttribute(U32(VertexAttributeId::SCALE), 0, Format::R32_SFLOAT, sizeof(Vec3));
- cmdb->setVertexAttribute(U32(VertexAttributeId::ALPHA), 0, Format::R32_SFLOAT, sizeof(Vec3) + sizeof(F32));
- // Vertex buff
- cmdb->bindVertexBuffer(0, token.m_buffer, token.m_offset, VERTEX_SIZE, VertexStepRate::INSTANCE);
- // Uniforms
- Array<Mat4, 1> trf = {Mat4::getIdentity()};
- RenderComponent::allocateAndSetupUniforms(m_particleEmitterResource->getMaterial(), ctx, trf, trf,
- *ctx.m_stagingGpuAllocator);
- // Draw
- cmdb->drawArrays(PrimitiveTopology::TRIANGLE_STRIP, 4, m_aliveParticleCount, 0, 0);
- }
- else
- {
- const Vec4 tsl = (m_worldBoundingVolume.getMin() + m_worldBoundingVolume.getMax()) / 2.0f;
- const Vec4 scale = (m_worldBoundingVolume.getMax() - m_worldBoundingVolume.getMin()) / 2.0f;
- // Set non uniform scale. Add a margin to avoid flickering
- Mat3 nonUniScale = Mat3::getZero();
- nonUniScale(0, 0) = scale.x();
- nonUniScale(1, 1) = scale.y();
- nonUniScale(2, 2) = scale.z();
- const Mat4 mvp = ctx.m_viewProjectionMatrix * Mat4(tsl.xyz1(), Mat3::getIdentity() * nonUniScale, 1.0f);
- const Bool enableDepthTest = ctx.m_debugDrawFlags.get(RenderQueueDebugDrawFlag::DEPTH_TEST_ON);
- if(enableDepthTest)
- {
- cmdb->setDepthCompareOperation(CompareOperation::LESS);
- }
- else
- {
- cmdb->setDepthCompareOperation(CompareOperation::ALWAYS);
- }
- m_node->getSceneGraph().getDebugDrawer().drawCubes(
- ConstWeakArray<Mat4>(&mvp, 1), Vec4(1.0f, 0.0f, 1.0f, 1.0f), 2.0f,
- ctx.m_debugDrawFlags.get(RenderQueueDebugDrawFlag::DITHERED_DEPTH_TEST_ON), 2.0f,
- *ctx.m_stagingGpuAllocator, cmdb);
- const Vec3 pos = m_transform.getOrigin().xyz();
- m_node->getSceneGraph().getDebugDrawer().drawBillboardTextures(
- ctx.m_projectionMatrix, ctx.m_viewMatrix, ConstWeakArray<Vec3>(&pos, 1), Vec4(1.0f),
- ctx.m_debugDrawFlags.get(RenderQueueDebugDrawFlag::DITHERED_DEPTH_TEST_ON), m_dbgImage->getTextureView(),
- ctx.m_sampler, Vec2(0.75f), *ctx.m_stagingGpuAllocator, ctx.m_commandBuffer);
- // Restore state
- if(!enableDepthTest)
- {
- cmdb->setDepthCompareOperation(CompareOperation::LESS);
- }
- }
- }
- } // end namespace anki
|