// NeL - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #include "std3d.h" #include "nel/3d/particle_system.h" #include "nel/3d/particle_system_shape.h" #include "nel/3d/particle_system_model.h" #include "nel/3d/scene.h" #include "nel/3d/driver.h" #include "nel/3d/skeleton_model.h" #include "nel/3d/texture_file.h" #include "nel/3d/ps_allocator.h" #include "nel/misc/file.h" #include "nel/misc/mem_stream.h" #include "nel/misc/hierarchical_timer.h" #include "nel/misc/contiguous_block_allocator.h" // tmp #include "nel/3d/ps_face_look_at.h" #include "nel/3d/ps_force.h" namespace NL3D { using NLMISC::CIFile; namespace { NLMISC::CMutex s_PSSMutex; } /* anonymous namespace */ // private usage : macro to check the memory integrity #if defined(NL_DEBUG) && defined(NL_OS_WINDOWS) // #define PARTICLES_CHECK_MEM NLMEMORY::CheckHeap(true); #define PARTICLES_CHECK_MEM #else #define PARTICLES_CHECK_MEM #endif // *************************************************************************** // A singleton to define the TextureCategory of Particle system class CPSTextureCategory { public: static NLMISC::CSmartPtr &get() { if(!_Instance) _Instance= new CPSTextureCategory(); return _Instance->_TextureCategory; } // release memory static void releaseInstance() { if( _Instance ) delete _Instance; _Instance = NULL; } private: NLMISC::CSmartPtr _TextureCategory; CPSTextureCategory() { _TextureCategory= new ITexture::CTextureCategory("PARTICLE SYSTEM"); } static CPSTextureCategory *_Instance; }; CPSTextureCategory *CPSTextureCategory::_Instance= NULL; ///=========================================================================== void CParticleSystemShape::releaseInstance() { CPSTextureCategory::releaseInstance(); } ///=========================================================================== CParticleSystemShape::CParticleSystemShape() : _MaxViewDist(100.f), _DestroyWhenOutOfFrustum(false), _DestroyModelWhenOutOfRange(false), _UsePrecomputedBBox(false), _Sharing(false), _NumBytesWanted(0) { /* *********************************************** * WARNING: This Class/Method must be thread-safe (ctor/dtor/serial): no static access for instance * It can be loaded/called through CAsyncFileManager for instance * ***********************************************/ for (uint k = 0; k < 4; ++k) { _UserParamDefaultTrack[k].setDefaultValue(0); } _DefaultPos.setDefaultValue(CVector::Null); _DefaultScale.setDefaultValue( CVector(1, 1, 1) ); _DefaultRotQuat.setDefaultValue(CQuat()); _DefaultTriggerTrack.setDefaultValue(true); // by default, system start as soon as they are instanciated } ///=========================================================================== void CParticleSystemShape::serial(NLMISC::IStream &f) throw(NLMISC::EStream) { /* *********************************************** * WARNING: This Class/Method must be thread-safe (ctor/dtor/serial): no static access for instance * It can be loaded/called through CAsyncFileManager for instance * ***********************************************/ sint ver = f.serialVersion(6); /// version 6 : added sharing flag //NLMISC::CVector8 &buf = _ParticleSystemProto.bufferAsVector(); //f.serialCont(buf); if (f.isReading ()) { std::vector buf; f.serialCont(buf); _ParticleSystemProto.fill(&buf[0], (uint32)buf.size()); } else { f.serialBufferWithSize ((uint8*)_ParticleSystemProto.buffer(), _ParticleSystemProto.length()); } if (ver > 1) { // serial default tracks for (uint k = 0; k < 4; ++k) { f.serial(_UserParamDefaultTrack[k]); } } if ( ver > 2) { f.serial (_DefaultPos); f.serial (_DefaultScale); f.serial (_DefaultRotQuat); } if ( ver > 3) { f.serial(_MaxViewDist); f.serial(_DestroyWhenOutOfFrustum); f.serial(_DestroyModelWhenOutOfRange); } if ( ver > 4) { f.serial(_UsePrecomputedBBox); if (_UsePrecomputedBBox) { f.serial(_PrecomputedBBox); } } if ( ver > 5) { f.serial(_Sharing); } } ///=========================================================================== void CParticleSystemShape::buildFromPS(const CParticleSystem &ps) { // must be sure that we are writing in the stream if (_ParticleSystemProto.isReading()) { _ParticleSystemProto.invert(); } // to have const correctness in the prototype, we must do this... CParticleSystem *myPs = const_cast(&ps); nlassert(myPs); // build the prototype _ParticleSystemProto.serialPtr(myPs); // mirror some system values _MaxViewDist = myPs->getMaxViewDist(); _DestroyWhenOutOfFrustum = myPs->doesDestroyWhenOutOfFrustum(); _DestroyModelWhenOutOfRange = myPs->getDestroyModelWhenOutOfRange(); if (!myPs->getAutoComputeBBox()) { _UsePrecomputedBBox = true; myPs->computeBBox(_PrecomputedBBox); } else { _UsePrecomputedBBox = false; } _Sharing = myPs->isSharingEnabled(); } ///=========================================================================== void CParticleSystemShape::getAABBox(NLMISC::CAABBox &bbox) const { if (!_UsePrecomputedBBox) { bbox.setCenter(NLMISC::CVector::Null); bbox.setHalfSize(NLMISC::CVector(1, 1, 1)); } else { bbox = _PrecomputedBBox; } } ///=========================================================================== CParticleSystem *CParticleSystemShape::instanciatePS(CScene &scene, NLMISC::CContiguousBlockAllocator *blockAllocator /*= NULL*/) { if (_Sharing && _SharedSystem != NULL) // is sharing enabled, and is a system already instanciated { return _SharedSystem; } // avoid prb with concurrent thread (may happen if an instance group containing ps is loaded in background) s_PSSMutex.enter(); #ifdef PS_FAST_ALLOC nlassert(PSBlockAllocator == NULL); if (blockAllocator) { // set new allocator for particle system memory PSBlockAllocator = blockAllocator; blockAllocator->init(_NumBytesWanted); // if size wanted is already known, set it } #endif //NLMISC::TTicks start = NLMISC::CTime::getPerformanceTime(); // copy the datas CParticleSystem *myInstance = NULL; // serialize from the memory stream if (!_ParticleSystemProto.isReading()) // we must be sure that we are reading the stream { _ParticleSystemProto.invert(); } _ParticleSystemProto.resetPtrTable(); _ParticleSystemProto.seek(0, NLMISC::IStream::begin); // NLMISC::TTicks start = NLMISC::CTime::getPerformanceTime(); _ParticleSystemProto.serialPtr(myInstance); // instanciate the system /* NLMISC::TTicks end = NLMISC::CTime::getPerformanceTime(); nlinfo("instanciation time = %.2f", (float) (1000 * NLMISC::CTime::ticksToSecond(end - start))); */ myInstance->setScene(&scene); if (_CachedTex.empty() && scene.getDriver()) { //nlinfo("flushing texs"); // load && cache textures myInstance->enumTexs(_CachedTex, *scene.getDriver()); for(uint k = 0; k < _CachedTex.size(); ++k) { if (_CachedTex[k]) { _CachedTex[k]->setTextureCategory(CPSTextureCategory::get()); scene.getDriver()->setupTexture (*(ITexture *)_CachedTex[k]); } } } else { /* for(uint k = 0; k < _CachedTex.size(); ++k) { nlinfo(_CachedTex[k]->getShareName().c_str()); } */ } // tmp if (_Sharing) { _SharedSystem = myInstance; // set this as the first shared instance } #ifdef PS_FAST_ALLOC if (blockAllocator) { _NumBytesWanted = blockAllocator->getNumAllocatedBytes(); // now we know the number of wanted bytes, subsequent alloc can be much faster PSBlockAllocator = NULL; } #endif s_PSSMutex.leave(); /*NLMISC::TTicks end = NLMISC::CTime::getPerformanceTime(); nlinfo("instanciation time = %.2f", (float) (1000 * NLMISC::CTime::ticksToSecond(end - start))); */ return myInstance; } ///=========================================================================== CTransformShape *CParticleSystemShape::createInstance(CScene &scene) { CParticleSystemModel *psm = NLMISC::safe_cast(scene.createModel(NL3D::ParticleSystemModelId) ); psm->Shape = this; psm->_Scene = &scene; // the model needs the scene to recreate the particle system he holds // by default, we don't instanciate the system. It will be instanciated only if visible and triggered // psm->_ParticleSystem = instanciatePS(scene); // Setup position with the default value psm->ITransformable::setPos( _DefaultPos.getDefaultValue() ); psm->ITransformable::setRotQuat( _DefaultRotQuat.getDefaultValue() ); psm->ITransformable::setScale( _DefaultScale.getDefaultValue() ); // ParticleSystems are added to the "Fx" Load Balancing Group. psm->setLoadBalancingGroup("Fx"); return psm; } ///=========================================================================== void CParticleSystemShape::render(IDriver *drv, CTransformShape *trans, bool passOpaque) { H_AUTO ( NL3D_Particles_Render ); nlassert(drv); CParticleSystemModel *psm = NLMISC::safe_cast(trans); if (psm->_Invalidated) return; CParticleSystem *ps = psm->getPS(); /// has the system been triggered yet ? if (!ps) return; TAnimationTime delay = psm->getEllapsedTime(); nlassert(ps->getScene()); /////////////////////// // render particles // /////////////////////// /// if sharing is enabled, we should resetup the system matrix if (ps->isSharingEnabled()) { ps->setSysMat(&(psm->getWorldMatrix())); ps->setUserMatrix(&(psm->getUserMatrix())); } // Setup the matrix. /// drv->setupModelMatrix(trans->getWorldMatrix()); ps->setDriver(drv); // draw particle PARTICLES_CHECK_MEM; if (passOpaque) { PSLookAtRenderTime = 0; //NLMISC::TTicks start = NLMISC::CTime::getPerformanceTime(); ps->step(CParticleSystem::SolidRender, delay, *this, *psm); /*NLMISC::TTicks end = NLMISC::CTime::getPerformanceTime(); nlinfo("Solid render time time = %.2f", (float) (1000 * NLMISC::CTime::ticksToSecond(end - start))); nlinfo("LookAt Render time = %.2f", (float) (1000 * NLMISC::CTime::ticksToSecond(PSLookAtRenderTime))); */ } else { //PSLookAtRenderTime = 0; //NLMISC::TTicks start = NLMISC::CTime::getPerformanceTime(); ps->step(CParticleSystem::BlendRender, delay, *this, *psm); /*NLMISC::TTicks end = NLMISC::CTime::getPerformanceTime(); nlinfo("Blend render time time = %.2f", (float) (1000 * NLMISC::CTime::ticksToSecond(end - start))); nlinfo("LookAt Render time = %.2f", (float) (1000 * NLMISC::CTime::ticksToSecond(PSLookAtRenderTime))); */ } PARTICLES_CHECK_MEM; if (psm->isToolDisplayEnabled()) { ps->step(CParticleSystem::ToolRender, delay, *this, *psm); PARTICLES_CHECK_MEM; } } ///=========================================================================== void CParticleSystemShape::flushTextures(IDriver &driver, uint selectedTexture) { // if textures are already flushed, no-op if (!_CachedTex.empty()) return; if (_SharedSystem) { _SharedSystem->enumTexs(_CachedTex, driver); } else { s_PSSMutex.enter(); // must create an instance just to flush the textures CParticleSystem *myInstance = NULL; #ifdef PS_FAST_ALLOC nlassert(PSBlockAllocator == NULL); NLMISC::CContiguousBlockAllocator blockAllocator; PSBlockAllocator = &blockAllocator; blockAllocator.init(300000); // we release memory just after, and we don't want to fragment the memory, so provide large enough mem #endif // serialize from the memory stream if (!_ParticleSystemProto.isReading()) // we must be sure that we are reading the stream { _ParticleSystemProto.invert(); } _ParticleSystemProto.resetPtrTable(); _ParticleSystemProto.seek(0, NLMISC::IStream::begin); _ParticleSystemProto.serialPtr(myInstance); // instanciate the system #ifdef PS_FAST_ALLOC _NumBytesWanted = blockAllocator.getNumAllocatedBytes(); // next allocation will be fast because we know how much memory to allocate #endif myInstance->enumTexs(_CachedTex, driver); // tmp /* #ifdef NL_DEBUG for(uint k = 0; k < myInstance->getNbProcess(); ++k) { CPSLocated *loc = (CPSLocated *) myInstance->getProcess(k); for(uint l = 0; l < loc->getNbBoundObjects(); ++l) { if (dynamic_cast(loc->getBoundObject(l))) { nlwarning("PS %s uses central gravity", myInstance->getName().c_str()); break; } } } #endif */ // sort the process inside the fx myInstance->getSortingByEmitterPrecedence(_ProcessOrder); delete myInstance; #ifdef PS_FAST_ALLOC PSBlockAllocator = NULL; #endif s_PSSMutex.leave(); } for(uint k = 0; k < _CachedTex.size(); ++k) { //nlinfo(_CachedTex[k]->getShareName().c_str()); if (_CachedTex[k]) { _CachedTex[k]->setTextureCategory(CPSTextureCategory::get()); driver.setupTexture(*_CachedTex[k]); } } } } // NL3D